Files
akmon/pages/mall/delivery/order-detail.uvue
2026-01-20 08:04:15 +08:00

775 lines
18 KiB
Plaintext
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="delivery-order-detail">
<!-- 订单状态 -->
<view class="order-status">
<view class="status-progress">
<view class="progress-item" :class="{ active: order.status >= 3 }">
<view class="progress-dot"></view>
<text class="progress-text">待接单</text>
</view>
<view class="progress-line" :class="{ active: order.status >= 4 }"></view>
<view class="progress-item" :class="{ active: order.status >= 4 }">
<view class="progress-dot"></view>
<text class="progress-text">配送中</text>
</view>
<view class="progress-line" :class="{ active: order.status >= 5 }"></view>
<view class="progress-item" :class="{ active: order.status >= 5 }">
<view class="progress-dot"></view>
<text class="progress-text">已送达</text>
</view>
</view>
<text class="status-desc">{{ getStatusDesc() }}</text>
</view>
<!-- 配送信息 -->
<view class="delivery-info">
<view class="section-title">配送信息</view>
<view class="delivery-route">
<view class="route-item pickup">
<view class="route-icon">📦</view>
<view class="route-content">
<text class="route-title">取货地址</text>
<text class="route-address">{{ merchant.contact_name }} · {{ merchant.contact_phone }}</text>
<text class="route-detail">{{ pickupAddress }}</text>
</view>
<button v-if="order.status === 3" class="route-action" @click="confirmPickup">确认取货</button>
</view>
<view class="route-line"></view>
<view class="route-item delivery">
<view class="route-icon">🏠</view>
<view class="route-content">
<text class="route-title">送货地址</text>
<text class="route-address">{{ getDeliveryAddress().name }} · {{ getDeliveryAddress().phone }}</text>
<text class="route-detail">{{ getDeliveryAddress().detail }}</text>
</view>
<button v-if="order.status === 4" class="route-action" @click="confirmDelivery">确认送达</button>
</view>
</view>
<view class="delivery-distance">
<text class="distance-label">配送距离:</text>
<text class="distance-value">{{ deliveryInfo.distance }}km</text>
<text class="time-label">预计时长:</text>
<text class="time-value">{{ deliveryInfo.estimated_time }}分钟</text>
</view>
</view>
<!-- 订单商品 -->
<view class="order-products">
<view class="section-title">订单商品</view>
<view class="shop-info">
<text class="shop-name">{{ merchant.shop_name }}</text>
<text class="order-no">订单号:{{ order.order_no }}</text>
</view>
<view v-for="item in orderItems" :key="item.id" class="product-item">
<image :src="item.product_image || '/static/default-product.png'" class="product-image" />
<view class="product-info">
<text class="product-name">{{ item.product_name }}</text>
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
<view class="price-quantity">
<text class="product-price">¥{{ item.price }}</text>
<text class="product-quantity">×{{ item.quantity }}</text>
</view>
</view>
</view>
<view class="order-summary">
<view class="summary-item">
<text class="summary-label">商品总额</text>
<text class="summary-value">¥{{ order.total_amount }}</text>
</view>
<view class="summary-item">
<text class="summary-label">配送费</text>
<text class="summary-value delivery-fee">¥{{ order.delivery_fee }}</text>
</view>
<view class="summary-item total">
<text class="summary-label">实付金额</text>
<text class="summary-value">¥{{ order.actual_amount }}</text>
</view>
</view>
</view>
<!-- 配送备注 -->
<view class="delivery-notes">
<view class="section-title">配送备注</view>
<view class="note-item">
<text class="note-label">顾客备注:</text>
<text class="note-content">{{ customerNote || '无备注' }}</text>
</view>
<view class="note-item">
<text class="note-label">商家备注:</text>
<text class="note-content">{{ merchantNote || '无备注' }}</text>
</view>
<view class="note-item">
<text class="note-label">配送备注:</text>
<input v-model="deliveryNote" class="note-input" placeholder="请输入配送备注" />
</view>
</view>
<!-- 联系方式 -->
<view class="contact-section">
<view class="section-title">联系方式</view>
<view class="contact-list">
<view class="contact-item" @click="callCustomer">
<view class="contact-icon">📞</view>
<view class="contact-info">
<text class="contact-name">联系顾客</text>
<text class="contact-phone">{{ getDeliveryAddress().phone }}</text>
</view>
</view>
<view class="contact-item" @click="callMerchant">
<view class="contact-icon">🏪</view>
<view class="contact-info">
<text class="contact-name">联系商家</text>
<text class="contact-phone">{{ merchant.contact_phone }}</text>
</view>
</view>
</view>
</view>
<!-- 底部操作 -->
<view class="bottom-actions">
<button v-if="order.status === 3" class="action-btn accept" @click="acceptOrder">接受订单</button>
<button v-if="order.status === 3" class="action-btn reject" @click="rejectOrder">拒绝订单</button>
<button v-if="order.status === 4" class="action-btn navigate" @click="startNavigation">开始导航</button>
<button v-if="order.status === 4" class="action-btn complete" @click="completeDelivery">完成配送</button>
<button class="action-btn contact" @click="contactService">联系客服</button>
</view>
</view>
</template>
<script>
import { OrderType, OrderItemType, MerchantType } from '@/types/mall-types.uts'
type DeliveryInfoType = {
distance: number
estimated_time: number
courier_id: string
pickup_time: string
delivery_time: string
}
export default {
data() {
return {
order: {
id: '',
order_no: '',
user_id: '',
merchant_id: '',
status: 0,
total_amount: 0,
discount_amount: 0,
delivery_fee: 0,
actual_amount: 0,
payment_method: 0,
payment_status: 0,
delivery_address: {},
created_at: ''
} as OrderType,
orderItems: [] as Array<OrderItemType & { product_image: string }>,
merchant: {
id: '',
user_id: '',
shop_name: '',
shop_logo: '',
shop_banner: '',
shop_description: '',
contact_name: '',
contact_phone: '',
shop_status: 0,
rating: 0,
total_sales: 0,
created_at: ''
} as MerchantType,
deliveryInfo: {
distance: 0,
estimated_time: 0,
courier_id: '',
pickup_time: '',
delivery_time: ''
} as DeliveryInfoType,
pickupAddress: '',
customerNote: '',
merchantNote: '',
deliveryNote: ''
}
},
onLoad(options: any) {
const orderId = options.orderId as string
if (orderId) {
this.loadOrderDetail(orderId)
}
},
methods: {
loadOrderDetail(orderId: string) {
// 模拟加载订单详情数据
this.order = {
id: orderId,
order_no: 'ORD202401150001',
user_id: 'user_001',
merchant_id: 'merchant_001',
status: 3, // 3:待接单 4:配送中 5:已送达
total_amount: 299.98,
discount_amount: 30.00,
delivery_fee: 8.00,
actual_amount: 277.98,
payment_method: 1,
payment_status: 1,
delivery_address: {
name: '张三',
phone: '13800138000',
detail: '北京市朝阳区某某街道某某小区1号楼101室'
},
created_at: '2024-01-15 14:30:00'
}
this.orderItems = [
{
id: 'item_001',
order_id: orderId,
product_id: 'product_001',
sku_id: 'sku_001',
product_name: '精选好物商品',
sku_specifications: { color: '红色', size: 'M' },
price: 199.99,
quantity: 1,
total_amount: 199.99,
product_image: '/static/product1.jpg'
}
]
this.merchant = {
id: 'merchant_001',
user_id: 'user_001',
shop_name: '优质好店',
shop_logo: '/static/shop-logo.png',
shop_banner: '/static/shop-banner.png',
shop_description: '专注品质生活',
contact_name: '店主小王',
contact_phone: '13800138000',
shop_status: 1,
rating: 4.8,
total_sales: 15680,
created_at: '2023-06-01'
}
this.pickupAddress = '北京市朝阳区商家街道123号'
this.customerNote = '请送到门口,谢谢'
this.merchantNote = '商品易碎,小心搬运'
this.deliveryInfo = {
distance: 3.2,
estimated_time: 25,
courier_id: 'courier_001',
pickup_time: '',
delivery_time: ''
}
},
getStatusDesc(): string {
const statusDescs = [
'',
'',
'',
'等待配送员接单',
'配送员正在前往取货',
'订单已送达完成'
]
return statusDescs[this.order.status] || ''
},
getDeliveryAddress(): any {
return this.order.delivery_address as any
},
getSpecText(specifications: any): string {
if (!specifications) return ''
return Object.keys(specifications).map(key => `${key}: ${specifications[key]}`).join(', ')
},
confirmPickup() {
uni.showModal({
title: '确认取货',
content: '确认已从商家处取到商品?',
success: (res) => {
if (res.confirm) {
this.deliveryInfo.pickup_time = new Date().toISOString()
uni.showToast({
title: '取货确认成功',
icon: 'success'
})
}
}
})
},
confirmDelivery() {
uni.showModal({
title: '确认送达',
content: '确认商品已送达到顾客手中?',
success: (res) => {
if (res.confirm) {
this.order.status = 5
this.deliveryInfo.delivery_time = new Date().toISOString()
uni.showToast({
title: '送达确认成功',
icon: 'success'
})
}
}
})
},
acceptOrder() {
uni.showModal({
title: '接受订单',
content: '确定接受这个配送订单吗?',
success: (res) => {
if (res.confirm) {
this.order.status = 4
uni.showToast({
title: '订单接受成功',
icon: 'success'
})
}
}
})
},
rejectOrder() {
uni.showModal({
title: '拒绝订单',
content: '确定拒绝这个配送订单吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '订单已拒绝',
icon: 'success'
})
uni.navigateBack()
}
}
})
},
startNavigation() {
// 开启导航功能
uni.showToast({
title: '正在启动导航',
icon: 'loading'
})
// 模拟调用地图导航
setTimeout(() => {
uni.showToast({
title: '导航已启动',
icon: 'success'
})
}, 1000)
},
completeDelivery() {
if (!this.deliveryNote.trim()) {
uni.showToast({
title: '请填写配送备注',
icon: 'none'
})
return
}
this.confirmDelivery()
},
callCustomer() {
const phone = this.getDeliveryAddress().phone
uni.makePhoneCall({
phoneNumber: phone
})
},
callMerchant() {
uni.makePhoneCall({
phoneNumber: this.merchant.contact_phone
})
},
contactService() {
uni.navigateTo({
url: `/pages/mall/service/chat?orderId=${this.order.id}&type=delivery`
})
}
}
}
</script>
<style>
.delivery-order-detail {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 160rpx;
}
.order-status {
background-color: #fff;
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
}
.status-progress {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.progress-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.progress-dot {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #ddd;
margin-bottom: 10rpx;
}
.progress-item.active .progress-dot {
background-color: #4caf50;
}
.progress-text {
font-size: 22rpx;
color: #666;
}
.progress-item.active .progress-text {
color: #4caf50;
font-weight: bold;
}
.progress-line {
height: 2rpx;
background-color: #ddd;
flex: 1;
margin: 0 20rpx;
margin-bottom: 32rpx;
}
.progress-line.active {
background-color: #4caf50;
}
.status-desc {
font-size: 26rpx;
color: #666;
text-align: center;
}
.delivery-info, .order-products, .delivery-notes, .contact-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 25rpx;
}
.delivery-route {
margin-bottom: 30rpx;
}
.route-item {
display: flex;
align-items: flex-start;
padding: 25rpx 0;
}
.route-icon {
font-size: 36rpx;
margin-right: 20rpx;
margin-top: 10rpx;
}
.route-content {
flex: 1;
}
.route-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.route-address {
font-size: 26rpx;
color: #666;
margin-bottom: 8rpx;
}
.route-detail {
font-size: 24rpx;
color: #999;
line-height: 1.4;
}
.route-action {
padding: 15rpx 30rpx;
background-color: #4caf50;
color: #fff;
border-radius: 25rpx;
font-size: 24rpx;
border: none;
}
.route-line {
width: 2rpx;
height: 40rpx;
background-color: #ddd;
margin-left: 38rpx;
}
.delivery-distance {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 10rpx;
}
.distance-label, .time-label {
font-size: 24rpx;
color: #666;
margin-right: 10rpx;
}
.distance-value, .time-value {
font-size: 24rpx;
color: #333;
font-weight: bold;
margin-right: 30rpx;
}
.shop-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
margin-bottom: 25rpx;
}
.shop-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.order-no {
font-size: 24rpx;
color: #666;
}
.product-item {
display: flex;
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.product-item:last-child {
border-bottom: none;
}
.product-image {
width: 100rpx;
height: 100rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.product-info {
flex: 1;
}
.product-name {
font-size: 26rpx;
color: #333;
margin-bottom: 8rpx;
line-height: 1.3;
}
.product-spec {
font-size: 22rpx;
color: #999;
margin-bottom: 12rpx;
}
.price-quantity {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 26rpx;
color: #ff4444;
font-weight: bold;
}
.product-quantity {
font-size: 24rpx;
color: #666;
}
.order-summary {
margin-top: 25rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f5f5f5;
}
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 0;
}
.summary-label {
font-size: 26rpx;
color: #666;
}
.summary-value {
font-size: 26rpx;
color: #333;
}
.summary-value.delivery-fee {
color: #4caf50;
}
.summary-item.total {
border-top: 1rpx solid #f5f5f5;
margin-top: 10rpx;
padding-top: 15rpx;
}
.summary-item.total .summary-label,
.summary-item.total .summary-value {
font-weight: bold;
font-size: 28rpx;
color: #ff4444;
}
.note-item {
margin-bottom: 20rpx;
}
.note-label {
font-size: 26rpx;
color: #666;
margin-bottom: 8rpx;
}
.note-content {
font-size: 26rpx;
color: #333;
line-height: 1.4;
}
.note-input {
width: 100%;
padding: 15rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 26rpx;
background-color: #f8f9fa;
}
.contact-list {
display: flex;
gap: 30rpx;
}
.contact-item {
flex: 1;
display: flex;
align-items: center;
padding: 25rpx;
background-color: #f8f9fa;
border-radius: 10rpx;
}
.contact-icon {
font-size: 32rpx;
margin-right: 15rpx;
}
.contact-info {
flex: 1;
}
.contact-name {
font-size: 26rpx;
color: #333;
margin-bottom: 5rpx;
}
.contact-phone {
font-size: 24rpx;
color: #007aff;
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 25rpx 30rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
display: flex;
gap: 15rpx;
}
.action-btn {
flex: 1;
height: 70rpx;
border-radius: 35rpx;
font-size: 26rpx;
border: none;
}
.action-btn.accept, .action-btn.complete {
background-color: #4caf50;
color: #fff;
}
.action-btn.reject {
background-color: #ff4444;
color: #fff;
}
.action-btn.navigate {
background-color: #2196f3;
color: #fff;
}
.action-btn.contact {
background-color: #ffa726;
color: #fff;
}
</style>