283 lines
7.5 KiB
Vue
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>
|