Files
FlashSaleSystem/flash-sale-frontend/src/pages/order/detail.vue
YoVinchen c52d9c52e3 feat: 前端页面和组件全面完善
- 优化通用组件:导航栏、页脚、图片上传、搜索
- 完善业务组件:商品卡片、秒杀卡片
- 更新用户端页面:首页、商品、秒杀、订单、购物车、个人中心
- 新增用户收藏页面
- 完善管理后台:仪表盘、商品/订单/用户/秒杀管理
- 新增管理后台:收藏管理、评价管理、系统监控页面
2026-03-10 23:21:53 +08:00

207 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="order-detail-page">
<div class="container mx-auto px-4 py-8">
<el-breadcrumb separator="/" class="mb-6">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/orders' }">我的订单</el-breadcrumb-item>
<el-breadcrumb-item>订单详情</el-breadcrumb-item>
</el-breadcrumb>
<div v-if="loading" class="text-center py-12">
<el-icon :size="40" class="animate-spin"><Loading /></el-icon>
<p class="mt-2 text-gray-500">加载中...</p>
</div>
<div v-else-if="!order" class="text-center py-12">
<el-empty description="订单不存在" />
<el-button type="primary" @click="router.push('/orders')">返回订单列表</el-button>
</div>
<div v-else>
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">订单状态</h2>
<el-tag :type="getStatusType(order.status)" size="large">{{ getStatusText(order.status) }}</el-tag>
</div>
<el-steps :active="getActiveStep()" finish-status="success">
<el-step title="提交订单" :description="formatTime(order.createdAt)" />
<el-step title="支付订单" :description="order.paidAt ? formatTime(order.paidAt) : ''" />
<el-step title="商家发货" :description="order.shippedAt ? formatTime(order.shippedAt) : ''" />
<el-step title="确认收货" :description="order.completedAt ? formatTime(order.completedAt) : ''" />
</el-steps>
<div class="mt-6 flex gap-2">
<template v-if="order.status === 'PENDING'">
<el-button type="primary" @click="handlePay">立即付款</el-button>
<el-button @click="handleCancel">取消订单</el-button>
</template>
<template v-else-if="order.status === 'SHIPPED'">
<el-button type="primary" @click="handleConfirm">确认收货</el-button>
</template>
<template v-else-if="order.status === 'COMPLETED'">
<el-button @click="handleReview">评价</el-button>
<el-button @click="handleRebuy">再次购买</el-button>
<el-button text type="danger" @click="handleDelete">删除订单</el-button>
</template>
<template v-else-if="order.status === 'CANCELLED'">
<el-button text type="danger" @click="handleDelete">删除订单</el-button>
</template>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-semibold mb-4">收货信息</h3>
<div v-if="order.address" class="text-sm space-y-2">
<div><span class="text-gray-500">收货人</span><span>{{ order.address.name }} {{ order.address.phone }}</span></div>
<div><span class="text-gray-500">收货地址</span><span>{{ order.address.province }} {{ order.address.city }} {{ order.address.district }} {{ order.address.address }}</span></div>
</div>
<div v-else class="text-gray-500">暂无收货信息</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-semibold mb-4">商品信息</h3>
<div class="space-y-4">
<div v-for="item in order.items" :key="item.id" class="flex gap-4 pb-4 border-b last:border-0">
<SafeImage :src="item.productImage" :alt="item.productName" wrapper-class="w-24 h-24 rounded" img-class="w-24 h-24 object-cover rounded" />
<div class="flex-1">
<h4 class="font-semibold mb-2">{{ item.productName }}</h4>
<div class="text-sm text-gray-500">单价¥{{ item.price }} × {{ item.quantity }}</div>
</div>
<div class="text-right"><div class="font-semibold text-lg">¥{{ item.subtotal }}</div></div>
</div>
</div>
<div class="border-t pt-4 mt-4 space-y-2">
<div class="flex justify-between text-sm"><span class="text-gray-500">商品总额</span><span>¥{{ order.totalAmount }}</span></div>
<div class="flex justify-between text-sm"><span class="text-gray-500">运费</span><span>¥0.00</span></div>
<div class="flex justify-between text-sm"><span class="text-gray-500">优惠</span><span class="text-red-500">-¥0.00</span></div>
<div class="flex justify-between text-lg font-semibold pt-2 border-t"><span>实付金额</span><span class="text-red-500">¥{{ order.paymentAmount }}</span></div>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<h3 class="text-lg font-semibold mb-4">订单信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div><span class="text-gray-500">订单编号</span><span>{{ order.orderNo }}</span><el-button text type="primary" size="small" @click="copyOrderNo">复制</el-button></div>
<div><span class="text-gray-500">创建时间</span><span>{{ formatTime(order.createdAt) }}</span></div>
<div v-if="order.paidAt"><span class="text-gray-500">付款时间</span><span>{{ formatTime(order.paidAt) }}</span></div>
<div v-if="order.paymentMethod"><span class="text-gray-500">支付方式</span><span>{{ getPaymentMethodText(order.paymentMethod) }}</span></div>
<div v-if="order.shippedAt"><span class="text-gray-500">发货时间</span><span>{{ formatTime(order.shippedAt) }}</span></div>
<div v-if="order.completedAt"><span class="text-gray-500">完成时间</span><span>{{ formatTime(order.completedAt) }}</span></div>
<div v-if="order.remark" class="md:col-span-2"><span class="text-gray-500">订单备注</span><span>{{ order.remark }}</span></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { orderApi } from '@/api/modules/order'
import { reviewApi } from '@/api/modules/review'
import { useCartStore } from '@/stores/cart'
import type { Order } from '@/types/api'
import dayjs from 'dayjs'
import SafeImage from '@/components/common/SafeImage.vue'
const route = useRoute()
const router = useRouter()
const cartStore = useCartStore()
const loading = ref(false)
const order = ref<Order | null>(null)
const formatTime = (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss')
const getStatusType = (status: string) => ({ PENDING: 'warning', PAID: 'primary', SHIPPED: 'primary', COMPLETED: 'success', CANCELLED: 'info', REFUNDED: 'danger' }[status] || 'info')
const getStatusText = (status: string) => ({ PENDING: '待付款', PAID: '待发货', SHIPPED: '待收货', COMPLETED: '已完成', CANCELLED: '已取消', REFUNDED: '已退款' }[status] || status)
const getPaymentMethodText = (method: string) => ({ ONLINE: '在线支付', ALIPAY: '支付宝', WECHAT: '微信支付', CASH: '货到付款', default: '默认支付' }[method] || method)
const getActiveStep = () => {
if (!order.value) return 0
switch (order.value.status) {
case 'PENDING': return 1
case 'PAID': return 2
case 'SHIPPED': return 3
case 'COMPLETED': return 4
default: return 0
}
}
const loadOrderDetail = async () => {
loading.value = true
try {
const res = await orderApi.getDetail(Number(route.params.id))
if (res.success) order.value = res.data
} catch (error) {
console.error('加载订单详情失败:', error)
ElMessage.error('加载失败')
} finally {
loading.value = false
}
}
const copyOrderNo = () => {
if (!order.value) return
navigator.clipboard.writeText(order.value.orderNo)
ElMessage.success('订单号已复制')
}
const handlePay = async () => {
if (!order.value) return
await ElMessageBox.confirm(`订单金额:¥${order.value.paymentAmount},确认付款?`, '付款确认', { confirmButtonText: '确认付款', cancelButtonText: '取消', type: 'warning' })
try { await orderApi.pay(order.value.id, 'ONLINE'); ElMessage.success('付款成功'); loadOrderDetail() } catch (error) { console.error('付款失败:', error) }
}
const handleCancel = async () => {
if (!order.value) return
await ElMessageBox.confirm('确定要取消该订单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
try { await orderApi.cancel(order.value.id); ElMessage.success('订单已取消'); loadOrderDetail() } catch (error) { console.error('取消订单失败:', error) }
}
const handleConfirm = async () => {
if (!order.value) return
await ElMessageBox.confirm('确定已收到商品?', '确认收货', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
try { await orderApi.confirm(order.value.id); ElMessage.success('已确认收货'); loadOrderDetail() } catch (error) { console.error('确认收货失败:', error) }
}
const handleReview = async () => {
if (!order.value) return
const firstItem = order.value.items[0]
if (!firstItem) return
try {
const { value } = await ElMessageBox.prompt('请输入本次购物评价', '商品评价', { inputType: 'textarea', inputPlaceholder: '分享一下你的使用感受吧', confirmButtonText: '提交评价', cancelButtonText: '取消' })
await reviewApi.create({ orderId: order.value.id, productId: firstItem.productId, rating: 5, content: value })
ElMessage.success('评价提交成功')
} catch (error) {
if (error) console.error('提交评价失败:', error)
}
}
const handleRebuy = async () => {
if (!order.value) return
const firstItem = order.value.items[0]
if (!firstItem) return
await cartStore.addToCart(firstItem.productId, firstItem.quantity)
router.push('/cart')
}
const handleDelete = async () => {
if (!order.value) return
await ElMessageBox.confirm('确定删除该订单吗?删除后不可恢复。', '删除确认', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' })
try { await orderApi.delete(order.value.id); ElMessage.success('订单已删除'); router.push('/orders') } catch (error) { console.error('删除订单失败:', error) }
}
onMounted(() => { loadOrderDetail() })
</script>
<style scoped lang="scss">
.order-detail-page {
min-height: calc(100vh - 60px);
background-color: #f5f5f5;
}
</style>