Files
apple 079076a70e miao33: 从 main 同步 single_uniapp22miao,dart-sass 兼容修复,DEPLOY.md 更新
- 从 main 获取 single_uniapp22miao 子项目
- dart-sass: /deep/ -> ::v-deep,calc 运算符加空格
- DEPLOY.md 采用 shccd159 版本(4 子项目架构说明)

Made-with: Cursor
2026-03-16 11:16:42 +08:00

863 lines
21 KiB
Vue
Raw Permalink 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-page">
<!-- 橙色头部 -->
<view class="header-section">
<view class="header-content">
<text class="header-icon" @click="goBack"></text>
<text class="header-title">订单详情</text>
<view class="header-placeholder"></view>
</view>
</view>
<scroll-view class="content" scroll-y>
<!-- 状态和进度卡片 -->
<view class="card-section status-card">
<!-- 状态头部 -->
<view class="status-header">
<text class="status-text">{{ orderInfo.status_text }}</text>
<text class="order-no-text">订单号: {{ orderInfo.order_no }}</text>
</view>
<!-- 进度条 -->
<view class="progress-bar">
<view
v-for="(step, index) in progressSteps"
:key="index"
class="progress-step"
:class="{ 'active': index <= currentProgressIndex, 'current': index === currentProgressIndex }"
>
<view class="step-icon-wrap">
<view class="step-icon">
<text class="icon-text">{{ getStepIcon(index) }}</text>
</view>
<view v-if="index < progressSteps.length - 1" class="step-line"></view>
</view>
<text class="step-text">{{ step.name }}</text>
</view>
</view>
</view>
<!-- 收货信息 -->
<view class="card-section address-card">
<view class="address-icon">
<text class="icon-location">📍</text>
</view>
<view class="address-content">
<view class="address-row">
<text class="receiver-name">{{ orderInfo.receiver_name }}</text>
<text class="receiver-phone">{{ orderInfo.receiver_phone }}</text>
</view>
<text class="address-text">{{ orderInfo.receiver_address }}</text>
</view>
</view>
<!-- 商品信息 -->
<view class="card-section goods-card">
<view class="card-header">
<text class="card-title">商品信息</text>
</view>
<!-- 商品列表 -->
<view class="goods-list">
<view
v-for="(goods, index) in orderInfo.orderInfoList"
:key="index"
class="goods-item"
>
<text class="goods-name">{{ goods.storeName || goods.productName }}</text>
<view class="goods-price-row">
<text class="goods-price">{{ formatPoints(goods.integral || goods.vipPrice || goods.price) }}积分</text>
<text class="goods-qty">x{{ goods.cartNum }}</text>
</view>
</view>
<!-- 无商品时显示默认 -->
<view v-if="!orderInfo.orderInfoList || orderInfo.orderInfoList.length === 0" class="goods-item">
<text class="goods-name">{{ orderInfo.goods_name }}</text>
<view class="goods-price-row">
<text class="goods-price">{{ formatPoints(orderInfo.points) }}积分</text>
<text class="goods-qty">x{{ orderInfo.quantity }}</text>
</view>
</view>
</view>
<!-- 汇总信息 -->
<view class="goods-summary">
<view class="summary-row">
<text class="summary-label">商品数量</text>
<text class="summary-value">{{ orderInfo.quantity }}</text>
</view>
<view class="summary-row">
<text class="summary-label">总计</text>
<text class="summary-total">{{ formatPoints(orderInfo.points) }}积分</text>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="card-section order-info-card">
<view class="card-header">
<text class="card-title">订单信息</text>
</view>
<view class="info-content">
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value" @click="copyText(orderInfo.order_no)">{{ orderInfo.order_no }}</text>
</view>
<view class="info-row">
<text class="info-label">下单时间</text>
<text class="info-value">{{ orderInfo.created_at }}</text>
</view>
<view class="info-row">
<text class="info-label">订单状态</text>
<text class="info-value status-value">{{ orderInfo.status_text }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view v-if="showActions" class="bottom-actions">
<!-- 待兑换(未付款): 取消订单立即支付 -->
<template v-if="!orderInfo.paid">
<button class="action-btn cancel-btn" @click="cancelOrder">取消订单</button>
<button class="action-btn primary-btn" @click="goPay">立即兑换</button>
</template>
<!-- 待收货: 确认收货 -->
<template v-else-if="orderInfo.paid && orderInfo.status === 1">
<button class="action-btn primary-btn" @click="confirmReceipt">确认收货</button>
</template>
</view>
</view>
</template>
<script>
import { getOrderDetail, orderCancel, orderTake } from '@/api/order.js';
import { mapGetters } from 'vuex';
import { toLogin } from '@/libs/login.js';
export default {
data() {
return {
orderId: '',
orderInfo: {
id: '',
order_no: '',
goods_id: '',
goods_name: '',
goods_image: '',
quantity: 1,
points: 0,
status: 1,
paid: true,
status_text: '',
express_company: '',
express_no: '',
receiver_name: '',
receiver_phone: '',
receiver_address: '',
created_at: '',
pay_at: '',
ship_at: '',
finish_at: '',
orderInfoList: []
},
progressSteps: [
{ name: '待兑换', status: 0 },
{ name: '待发货', status: 1 },
{ name: '待收货', status: 2 },
{ name: '已完成', status: 3 }
],
loading: false
}
},
computed: {
...mapGetters(['isLogin']),
showActions() {
// 待兑换(未付款)显示取消按钮,待收货显示确认收货按钮
return (!this.orderInfo.paid) || (this.orderInfo.paid && this.orderInfo.status === 1)
},
// 计算总积分
totalPoints() {
return this.orderInfo.points || 0;
},
// 当前进度索引
currentProgressIndex() {
if (!this.orderInfo.paid) return 0; // 待兑换
// status: 0-待发货 1-待收货 2-待评价 3-已完成
if (this.orderInfo.status === 0) return 1; // 待发货
if (this.orderInfo.status === 1) return 2; // 待收货
if (this.orderInfo.status >= 2) return 3; // 已完成
return 0;
}
},
onLoad(options) {
if (options.id) {
this.orderId = options.id
this.loadOrderDetail()
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 格式化积分数字(添加千分位逗号)
formatPoints(points) {
if (!points && points !== 0) return '0'
return points.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
// 获取步骤图标
getStepIcon(index) {
const icons = ['⏱', '📦', '🚚', '✓'];
return icons[index] || '○';
},
// 加载订单详情
async loadOrderDetail() {
if (this.loading) return
// 检查登录状态
if (!this.isLogin) {
toLogin();
return;
}
this.loading = true
uni.showLoading({ title: '加载中...' })
try {
const res = await getOrderDetail(this.orderId);
if (res.data) {
const order = res.data;
const orderInfoList = order.orderInfoList || [];
// 获取第一个商品信息
let goodsName = '商品名称';
let goodsImage = '/static/images/default-goods.png';
let totalNum = order.totalNum || 1;
// 计算总积分优先使用payIntegral/useIntegral否则从商品列表计算
let totalPoints = 0;
// 优先使用订单级别的积分字段
if (order.payIntegral !== undefined && order.payIntegral !== null) {
totalPoints = parseFloat(order.payIntegral) || 0;
} else if (order.useIntegral !== undefined && order.useIntegral !== null) {
totalPoints = parseFloat(order.useIntegral) || 0;
} else if (order.integral !== undefined && order.integral !== null) {
totalPoints = parseFloat(order.integral) || 0;
} else {
// 从商品列表计算总积分
totalPoints = orderInfoList.reduce((sum, item) => {
// 优先使用integral字段否则使用vipPrice或price
const itemPoints = parseFloat(item.integral) || parseFloat(item.vipPrice) || parseFloat(item.price) || 0;
const quantity = parseInt(item.cartNum) || 1;
return sum + (itemPoints * quantity);
}, 0);
// 如果计算结果为0尝试使用payPrice或totalPrice
if (totalPoints === 0) {
totalPoints = parseFloat(order.payPrice) || parseFloat(order.totalPrice) || 0;
}
}
console.log('订单详情积分计算:', {
orderId: order.orderId,
payIntegral: order.payIntegral,
useIntegral: order.useIntegral,
integral: order.integral,
payPrice: order.payPrice,
totalPrice: order.totalPrice,
calculatedPoints: totalPoints,
orderInfoList: orderInfoList.map(item => ({
name: item.storeName,
integral: item.integral,
vipPrice: item.vipPrice,
price: item.price,
cartNum: item.cartNum
}))
});
if (orderInfoList.length > 0) {
const firstGoods = orderInfoList[0];
goodsName = firstGoods.storeName || firstGoods.productName || goodsName;
goodsImage = firstGoods.image || goodsImage;
}
// 格式化订单数据
this.orderInfo = {
id: order.id,
order_no: order.orderId || order.orderNo || '',
goods_id: orderInfoList.length > 0 ? orderInfoList[0].productId : '',
goods_name: goodsName,
goods_image: goodsImage,
quantity: totalNum,
points: totalPoints,
status: order.status,
paid: order.paid,
status_text: order.orderStatus || this.getStatusText(order.status, order.paid),
express_company: order.deliveryName || '',
express_no: order.deliveryId || '',
receiver_name: order.realName || '',
receiver_phone: order.userPhone || '',
receiver_address: order.userAddress || '',
created_at: order.createTime || '',
pay_at: order.payTime || '',
ship_at: order.deliveryTime || '',
finish_at: order.finishTime || '',
orderInfoList: orderInfoList
};
} else {
throw new Error(res.msg || '加载失败');
}
} catch (error) {
console.error('加载订单详情失败:', error)
uni.showToast({
title: error.msg || error.message || '加载失败',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} finally {
this.loading = false
uni.hideLoading()
}
},
// 获取状态文本
getStatusText(status, paid) {
if (!paid) return '待兑换';
const statusMap = {
0: '待发货',
1: '待收货',
2: '待评价',
3: '已完成'
};
return statusMap[status] || '未知状态';
},
// 取消订单
cancelOrder() {
uni.showModal({
title: '提示',
content: '确定要取消这个订单吗?积分将退回到您的账户',
confirmColor: '#F54900',
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '取消中...' })
try {
await orderCancel(this.orderInfo.id);
uni.hideLoading()
uni.showToast({
title: '订单已取消',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.msg || '取消失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 确认收货
confirmReceipt() {
uni.showModal({
title: '确认收货',
content: '确认已收到商品?',
confirmColor: '#F54900',
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '确认中...' })
try {
const result = await orderTake(this.orderInfo.id);
console.log('确认收货接口返回:', result)
uni.hideLoading()
// 接口返回 code: 200 表示成功
if (result.code === 200 || result.code === 0) {
uni.showToast({
title: '确认收货成功',
icon: 'success'
})
// 重新加载订单详情
setTimeout(() => {
this.loadOrderDetail()
}, 1500)
} else {
uni.showToast({
title: result.msg || '操作失败,请重试',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('确认收货失败:', error)
uni.showToast({
title: error.msg || error.message || '操作失败,请重试',
icon: 'none'
})
}
}
}
})
},
// 复制文本
copyText(text) {
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({
title: '已复制',
icon: 'success'
})
}
})
},
// 获取状态图标
getStatusIcon(status) {
const icons = {
1: '⏳', // 待发货
2: '🚚', // 待收货
3: '✅', // 已完成
4: '❌' // 已取消
}
return icons[status] || '📦'
},
// 获取状态提示
getStatusTip(status) {
if (!this.orderInfo.paid) {
return '请尽快完成支付,积分商品数量有限';
}
const tips = {
0: '商家正在准备发货,请耐心等待',
1: '商品已发货,请注意查收',
2: '请对本次交易进行评价',
3: '交易已完成,感谢您的支持'
}
return tips[status] || ''
},
// 去支付
goPay() {
uni.navigateTo({
url: `/pages/order/order_payment/index?orderNo=${this.orderInfo.order_no}&payPrice=${this.orderInfo.points}`
})
}
}
}
</script>
<style lang="scss" scoped>
.order-detail-page {
min-height: 100vh;
height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
// 橙色头部
.header-section {
background-color: #F54900;
}
.header-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
}
.header-icon {
font-size: 48rpx;
color: #FFFFFF;
font-weight: 300;
width: 60rpx;
}
.header-title {
flex: 1;
font-size: 34rpx;
color: #FFFFFF;
font-weight: 500;
text-align: center;
}
.header-placeholder {
width: 60rpx;
}
.content {
flex: 1;
padding: 24rpx 32rpx;
padding-bottom: 140rpx;
box-sizing: border-box;
height: calc(100vh - 88rpx - 120rpx);
overflow-y: auto;
}
// 卡片通用样式
.card-section {
background-color: #FFFFFF;
border-radius: 24rpx;
margin-bottom: 24rpx;
overflow: hidden;
}
// 状态卡片
.status-card {
padding: 32rpx;
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
}
.status-text {
font-size: 36rpx;
color: #F54900;
font-weight: 500;
}
.order-no-text {
font-size: 26rpx;
color: #666666;
}
// 进度条
.progress-bar {
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
}
.progress-step {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
&.active {
.step-icon {
background-color: #F54900;
border-color: #F54900;
.icon-text {
color: #FFFFFF;
}
}
.step-text {
color: #F54900;
}
}
&.current {
.step-icon {
background-color: #F54900;
border-color: #F54900;
}
}
}
.step-icon-wrap {
position: relative;
display: flex;
align-items: center;
width: 100%;
justify-content: center;
}
.step-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #FFFFFF;
border: 4rpx solid #E5E7EB;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
position: relative;
}
.icon-text {
font-size: 28rpx;
color: #E5E7EB;
}
.step-line {
position: absolute;
top: 50%;
left: calc(50% + 36rpx);
width: calc(100% - 72rpx);
height: 4rpx;
background-color: #E5E7EB;
z-index: 1;
transform: translateY(-50%);
}
.progress-step.active .step-line {
background-color: #F54900;
}
.progress-step:last-child .step-line {
display: none;
}
.step-text {
margin-top: 16rpx;
font-size: 24rpx;
color: #999999;
white-space: nowrap;
text-align: center;
}
// 收货信息卡片
.address-card {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 20rpx;
padding: 32rpx;
}
.address-icon {
flex-shrink: 0;
}
.icon-location {
font-size: 36rpx;
}
.address-content {
flex: 1;
}
.address-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
}
.receiver-name {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
.receiver-phone {
font-size: 30rpx;
color: #333333;
}
.address-text {
font-size: 28rpx;
color: #666666;
line-height: 1.5;
}
// 商品信息卡片
.goods-card {
display: flex;
flex-direction: column;
}
.card-header {
padding: 32rpx;
border-bottom: 1rpx solid #F5F5F5;
}
.card-title {
font-size: 28rpx;
color: #999999;
font-weight: normal;
}
.goods-list {
padding: 0 32rpx;
}
.goods-item {
padding: 32rpx 0;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
}
.goods-name {
font-size: 30rpx;
color: #333333;
line-height: 1.5;
margin-bottom: 16rpx;
display: block;
}
.goods-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-price {
font-size: 30rpx;
color: #F54900;
font-weight: 500;
}
.goods-qty {
font-size: 28rpx;
color: #999999;
}
// 商品汇总
.goods-summary {
padding: 24rpx 32rpx;
border-top: 1rpx solid #F5F5F5;
}
.summary-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.summary-label {
font-size: 28rpx;
color: #333333;
}
.summary-value {
font-size: 28rpx;
color: #333333;
}
.summary-total {
font-size: 32rpx;
color: #F54900;
font-weight: 500;
}
// 订单信息卡片
.order-info-card {
display: flex;
flex-direction: column;
}
.info-content {
padding: 24rpx 32rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.status-value {
color: #F54900 !important;
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
}
/* 底部操作栏 - 固定在页面底部 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #FFFFFF;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #EEEEEE;
display: flex;
justify-content: flex-end;
gap: 20rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.action-btn {
padding: 0 50rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
border: none;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #F5F5F5;
color: #666666;
}
.primary-btn {
background-color: #F54900;
color: #FFFFFF;
}
</style>