JewelryMall/miniprogram/pages/order/detail.vue
2026-02-14 19:29:15 +08:00

283 lines
7.5 KiB
Vue

<template>
<view class="order-detail" v-if="order">
<!-- 订单状态 -->
<view class="status-bar" :class="`status-bar--${order.status}`">
<text class="status-bar__text">{{ statusLabel }}</text>
</view>
<!-- 订单商品列表 -->
<view class="section">
<view class="section__title">商品信息</view>
<view class="order-item" v-for="item in orderItems" :key="item.id">
<image class="order-item__img" :src="item.product.bannerImages[0] || ''" mode="aspectFill" />
<view class="order-item__info">
<text class="order-item__name">{{ item.product.name }}</text>
<text class="order-item__spec">{{ item.specData.modelName }} {{ item.specData.fineness }}</text>
<view class="order-item__bottom">
<text class="order-item__price">¥{{ item.unitPrice }}</text>
<text class="order-item__qty">x{{ item.quantity }}</text>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="section">
<view class="section__title">订单信息</view>
<view class="info-row">
<text class="info-label">订单号</text>
<text class="info-value">{{ order.orderNo }}</text>
</view>
<view class="info-row">
<text class="info-label">订单总价</text>
<text class="info-value price">¥{{ order.totalPrice }}</text>
</view>
<view class="info-row">
<text class="info-label">下单时间</text>
<text class="info-value">{{ order.createdAt }}</text>
</view>
<!-- 已支付:显示支付时间 -->
<view class="info-row" v-if="order.status === 'paid' || order.status === 'shipped'">
<text class="info-label">支付时间</text>
<text class="info-value">{{ order.paymentTime || '-' }}</text>
</view>
<!-- 已发货:显示物流信息 -->
<view v-if="order.status === 'shipped'">
<view class="info-row">
<text class="info-label">物流公司</text>
<text class="info-value">{{ order.shippingCompany || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">物流单号</text>
<text class="info-value">{{ order.shippingNo || '-' }}</text>
</view>
</view>
</view>
<!-- 收货信息 -->
<view class="section">
<view class="section__title">收货信息</view>
<view class="info-row">
<text class="info-label">收货人</text>
<text class="info-value">{{ order.receiverName }}</text>
</view>
<view class="info-row">
<text class="info-label">联系电话</text>
<text class="info-value">{{ order.receiverPhone }}</text>
</view>
<view class="info-row">
<text class="info-label">收货地址</text>
<text class="info-value">{{ order.receiverAddress }}</text>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="bottom-bar">
<view class="bottom-bar__btn bottom-bar__btn--outline" v-if="order.status === 'pending'" @click="handleCancel">
取消订单
</view>
<view class="bottom-bar__btn bottom-bar__btn--outline" @click="showQrCode = true">
添加客服好友
</view>
<button class="bottom-bar__btn bottom-bar__btn--primary" open-type="contact">
联系客服
</button>
</view>
<!-- 客服二维码弹窗 -->
<CustomerServiceBtn v-if="showQrCode" mode="qrcode" @close="showQrCode = false" />
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// @ts-ignore - uni-app 页面生命周期
import { onShow } from '@dcloudio/uni-app'
import type { Order, OrderItem } from '../../types'
import { getOrderDetail, cancelOrder } from '../../api/order'
import { useOrderStore } from '../../store/order'
import CustomerServiceBtn from '../../components/CustomerServiceBtn.vue'
const orderStore = useOrderStore()
const order = ref<Order | null>(null)
const orderItems = ref<OrderItem[]>([])
const showQrCode = ref(false)
const orderId = ref(0)
const STATUS_MAP: Record<string, string> = {
pending: '待支付',
paid: '已支付',
shipped: '已发货',
cancelled: '已取消',
}
const statusLabel = computed(() => (order.value ? STATUS_MAP[order.value.status] || order.value.status : ''))
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as { options?: { id?: string } }
orderId.value = Number(currentPage.options?.id)
if (orderId.value) {
loadOrder(orderId.value)
}
})
// 页面每次显示时刷新(后台更新状态后重新进入时同步)
onShow(() => {
if (orderId.value) {
loadOrder(orderId.value)
}
})
async function loadOrder(id: number) {
try {
const data = await getOrderDetail(id)
order.value = data
orderItems.value = data.items || []
orderStore.setCurrentOrder(data)
} catch {
uni.showToast({ title: '加载订单失败', icon: 'none' })
}
}
async function handleCancel() {
if (!order.value) return
uni.showModal({
title: '提示',
content: '确定要取消该订单吗?',
success: async (res) => {
if (res.confirm && order.value) {
try {
await cancelOrder(order.value.id)
order.value.status = 'cancelled'
orderStore.updateOrderStatus(order.value.id, 'cancelled')
uni.showToast({ title: '订单已取消', icon: 'success' })
} catch {
uni.showToast({ title: '取消订单失败', icon: 'none' })
}
}
},
})
}
</script>
<style scoped>
.order-detail {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 140rpx;
}
.status-bar {
padding: 32rpx 24rpx;
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
.status-bar--pending { background: #faad14; }
.status-bar--paid { background: #52c41a; }
.status-bar--shipped { background: #1890ff; }
.status-bar--cancelled { background: #999; }
.section {
background: #fff;
margin-bottom: 16rpx;
padding: 24rpx;
}
.section__title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.order-item {
display: flex;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.order-item__img {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
flex-shrink: 0;
background: #f5f5f5;
}
.order-item__info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.order-item__name {
font-size: 28rpx;
color: #333;
}
.order-item__spec {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.order-item__bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8rpx;
}
.order-item__price {
font-size: 28rpx;
color: #e4393c;
font-weight: bold;
}
.order-item__qty {
font-size: 24rpx;
color: #999;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
}
.info-label {
font-size: 26rpx;
color: #999;
}
.info-value {
font-size: 26rpx;
color: #333;
}
.info-value.price {
color: #e4393c;
font-weight: bold;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 16rpx 24rpx;
display: flex;
gap: 16rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.bottom-bar__btn {
flex: 1;
text-align: center;
padding: 20rpx 0;
border-radius: 44rpx;
font-size: 26rpx;
line-height: 1.4;
}
.bottom-bar__btn--outline {
border: 1rpx solid #ddd;
color: #666;
background: #fff;
}
.bottom-bar__btn--primary {
background: #e4393c;
color: #fff;
border: none;
}
</style>