Files
integral-shop/single_uniapp22miao/pages/integral/order-detail.vue

863 lines
21 KiB
Vue
Raw Normal View History

<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>