JewelryMall/miniprogram/pages/order/detail.vue
2026-03-05 00:43:04 +08:00

453 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>
<view class="order-detail" v-if="order">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
<image class="custom-navbar__back" src="/static/ic_back.png" mode="aspectFit" @click="goBack" />
<text class="custom-navbar__title">订单详情</text>
<view class="custom-navbar__placeholder" />
</view>
</view>
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" />
<!-- 商品列表 -->
<view class="section product-section">
<view class="order-item" v-for="item in orderItems" :key="item.id">
<image class="order-item__img" :src="getItemImage(item)" mode="aspectFill" />
<view class="order-item__info">
<text class="order-item__name">{{ item.product.name }}</text>
<view class="order-item__specs">
<view class="spec-row">
<text v-if="item.specData.modelName">款号:{{ item.specData.modelName }}</text>
<text v-if="item.specData.fineness">商品型号:{{ item.specData.fineness }}</text>
</view>
<view class="spec-row">
<text v-if="item.specData.mainStone">成色:{{ item.specData.mainStone }}</text>
<text v-if="item.specData.subStone">副石:{{ item.specData.subStone }}</text>
</view>
<view class="spec-row">
<text v-if="item.specData.ringSize">主石:{{ item.specData.ringSize }}</text>
<text v-if="item.specData.goldTotalWeight">手寸:{{ item.specData.goldTotalWeight }}</text>
</view>
<view class="spec-row">
<text v-if="item.specData.goldNetWeight">金料总重:{{ item.specData.goldNetWeight }}</text>
</view>
</view>
<view class="order-item__bottom">
<text class="order-item__price">¥{{ item.unitPrice }}元</text>
<text class="order-item__qty">×{{ item.quantity }}</text>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="section">
<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">¥{{ order.totalPrice }}</text>
</view>
<view v-if="order.shippingCompany" class="info-row">
<text class="info-label">物流公司</text>
<text class="info-value">{{ order.shippingCompany }}</text>
</view>
<view v-if="order.shippingNo" class="info-row">
<text class="info-label">物流单号</text>
<view style="display:flex;align-items:center;gap:16rpx;">
<text class="info-value">{{ order.shippingNo }}</text>
<text class="copy-btn" @click="copyShippingNo(order.shippingNo)">复制</text>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="section tip-section">
<text class="tip-section__title">您已成功下单!</text>
<view class="tip-section__body">
<text>客服电话(微信): 15920028399</text>
<text>交易方式: 门店交易、微信、支付宝、银行卡转账</text>
<text>公司地址: 水贝二路贝丽花园21栋1楼叶生珠宝请</text>
</view>
<text class="tip-section__footer">请点击下面联系客服出货吧</text>
</view>
<!-- 取消订单 -->
<view class="section cancel-section" v-if="order.status === 'pending'">
<text class="cancel-btn" @click="handleCancel">取消订单</text>
</view>
<!-- 收货信息 -->
<view class="receiver-section">
<view class="receiver-row">
<image class="receiver-icon" src="/static/ic_address2.png" mode="aspectFit" />
<text>{{ order.receiverAddress || '我的地址' }}</text>
</view>
<view class="receiver-row">
<image class="receiver-icon" src="/static/ic_phone.png" mode="aspectFit" />
<text>{{ order.receiverPhone || '我的手机号' }}</text>
</view>
</view>
<!-- 底部栏 -->
<view class="bottom-bar">
<view class="bottom-bar__left">
<text class="bottom-bar__label">合计:</text>
<text class="bottom-bar__price">¥{{ order.totalPrice }}</text>
</view>
<view class="bottom-bar__btn" @click="contactService">联系客服</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// @ts-ignore
import { onShow } from '@dcloudio/uni-app'
import type { Order, OrderItem } from '../../types'
import { getOrderDetail, cancelOrder } from '../../api/order'
import { useOrderStore } from '../../store/order'
import { BASE_URL } from '../../utils/request'
const orderStore = useOrderStore()
const order = ref<Order | null>(null)
const orderItems = ref<OrderItem[]>([])
const orderId = ref(0)
const statusBarHeight = ref(20)
const navBarHeight = ref(44)
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 20
// #ifdef MP-WEIXIN
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = (menuBtn.top - (sysInfo.statusBarHeight || 20)) * 2 + menuBtn.height
// #endif
} catch { /* fallback */ }
function goBack() {
uni.navigateBack({ delta: 1 })
}
function fullUrl(path : string) : string {
if (!path) return ''
if (path.startsWith('http')) return path
return BASE_URL + path
}
function getItemImage(item : OrderItem) : string {
const imgs = item.product?.bannerImages
if (imgs && imgs.length > 0) return fullUrl(imgs[0])
const thumb = (item.product as any)?.thumb
if (thumb) return fullUrl(thumb)
return ''
}
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' })
}
}
},
})
}
function contactService() {
uni.makePhoneCall({ phoneNumber: '15920028399' })
}
function copyShippingNo(no: string) {
uni.setClipboardData({
data: no,
success() {
uni.showToast({ title: '物流单号已复制', icon: 'success' })
}
})
}
</script>
<style scoped>
.order-detail {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 130rpx;
}
/* 自定义导航栏 */
.custom-navbar {
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.custom-navbar__content {
display: flex;
align-items: center;
padding: 0 24rpx;
}
.custom-navbar__back {
width: 44rpx;
height: 44rpx;
}
.custom-navbar__title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.custom-navbar__placeholder {
width: 44rpx;
}
.section {
background: #fff;
margin: 20rpx 24rpx;
border-radius: 16rpx;
padding: 24rpx;
}
/* 商品卡片 */
.order-item {
display: flex;
padding: 16rpx 0;
}
.order-item+.order-item {
border-top: 1rpx solid #f0f0f0;
}
.order-item__img {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
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: 30rpx;
font-weight: bold;
color: #333;
}
.order-item__specs {
margin-top: 12rpx;
font-size: 24rpx;
color: #999;
}
.spec-row {
display: flex;
gap: 40rpx;
line-height: 1.8;
}
.spec-row text {
min-width: 200rpx;
}
.order-item__bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12rpx;
}
.order-item__price {
font-size: 32rpx;
color: #e4393c;
font-weight: bold;
}
.order-item__qty {
font-size: 26rpx;
color: #999;
}
/* 订单信息 */
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #333;
}
.info-value {
font-size: 28rpx;
color: #999;
}
.copy-btn {
color: #FF6D9B;
font-size: 22rpx;
padding: 4rpx 16rpx;
border: 1rpx solid #FF6D9B;
border-radius: 16rpx;
}
/* 提示信息 */
.tip-section {
margin-top: 20rpx;
}
.tip-section__title {
font-size: 30rpx;
color: #FF6D9B;
font-weight: bold;
display: block;
margin-bottom: 20rpx;
}
.tip-section__body {
display: flex;
flex-direction: column;
gap: 12rpx;
font-size: 26rpx;
color: #333;
line-height: 1.6;
}
.tip-section__footer {
display: block;
margin-top: 20rpx;
font-size: 26rpx;
color: #333;
}
/* 取消订单 */
.cancel-section {
margin-top: 20rpx;
text-align: center;
padding: 0;
}
.cancel-btn {
display: block;
padding: 28rpx 0;
font-size: 30rpx;
color: #333;
}
/* 收货信息 */
.receiver-section {
background: #fff;
margin: 20rpx 24rpx;
border-radius: 16rpx;
padding: 24rpx;
}
.receiver-row {
display: flex;
align-items: center;
gap: 12rpx;
padding: 12rpx 0;
font-size: 28rpx;
color: #333;
}
.receiver-icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
/* 底部栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 30rpx;
}
.bottom-bar__left {
display: flex;
align-items: baseline;
}
.bottom-bar__label {
font-size: 26rpx;
color: #333;
}
.bottom-bar__price {
font-size: 44rpx;
color: #FF6D9B;
font-weight: bold;
}
.bottom-bar__btn {
background: #FF6D9B;
color: #fff;
font-size: 30rpx;
font-weight: 500;
padding: 22rpx 56rpx;
border-radius: 44rpx;
border: none;
line-height: 1.4;
}
</style>