后端功能增强:全局异常处理、API控制器、JSP视图和单元测试
- 添加 GlobalExceptionHandler 全局异常处理 - 添加 ApiController REST API 控制器 - 更新 WebConfig 跨域配置和 ProductRepository 查询方法 - 新增 monitor/product-detail/profile JSP 视图页面 - 添加 FlashSaleServiceTest 秒杀服务单元测试 - 更新 application.yml 配置
This commit is contained in:
357
flash-sale-frontend/src/pages/order/detail.vue
Normal file
357
flash-sale-frontend/src/pages/order/detail.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<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>
|
||||
</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"
|
||||
>
|
||||
<img
|
||||
:src="item.productImage || '/default-product.png'"
|
||||
:alt="item.productName"
|
||||
class="w-24 h-24 object-cover rounded"
|
||||
@error="handleImageError"
|
||||
>
|
||||
<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">-¥{{ (order.totalAmount - order.paymentAmount).toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between pt-2 border-t">
|
||||
<span class="font-semibold">实付金额</span>
|
||||
<span class="text-xl font-bold 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 type { Order } from '@/types/api'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const order = ref<Order | null>(null)
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time: string) => {
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
// 处理图片错误
|
||||
const handleImageError = (e: Event) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.src = '/default-product.png'
|
||||
}
|
||||
|
||||
// 获取状态类型
|
||||
const getStatusType = (status: string) => {
|
||||
const map: Record<string, string> = {
|
||||
'PENDING': 'warning',
|
||||
'PAID': 'primary',
|
||||
'SHIPPED': 'primary',
|
||||
'COMPLETED': 'success',
|
||||
'CANCELLED': 'info',
|
||||
'REFUNDED': 'danger'
|
||||
}
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const map: Record<string, string> = {
|
||||
'PENDING': '待付款',
|
||||
'PAID': '待发货',
|
||||
'SHIPPED': '待收货',
|
||||
'COMPLETED': '已完成',
|
||||
'CANCELLED': '已取消',
|
||||
'REFUNDED': '已退款'
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
// 获取支付方式文本
|
||||
const getPaymentMethodText = (method: string) => {
|
||||
const map: Record<string, string> = {
|
||||
'ONLINE': '在线支付',
|
||||
'ALIPAY': '支付宝',
|
||||
'WECHAT': '微信支付',
|
||||
'CASH': '货到付款'
|
||||
}
|
||||
return map[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
|
||||
case 'CANCELLED':
|
||||
case 'REFUNDED': return -1
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
|
||||
// 加载订单详情
|
||||
const loadOrderDetail = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const id = Number(route.params.id)
|
||||
const res = await orderApi.getDetail(id)
|
||||
|
||||
if (res.success) {
|
||||
order.value = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单详情失败:', error)
|
||||
ElMessage.error('加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 复制订单号
|
||||
const copyOrderNo = () => {
|
||||
if (order.value) {
|
||||
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 = () => {
|
||||
ElMessage.info('评价功能开发中...')
|
||||
}
|
||||
|
||||
// 再次购买
|
||||
const handleRebuy = () => {
|
||||
ElMessage.success('商品已加入购物车')
|
||||
router.push('/cart')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOrderDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.order-detail-page {
|
||||
min-height: calc(100vh - 60px);
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user