Files
medical-mall/pages/mall/delivery/order-history.uvue
not-like-juvenile 09c33be394 订单匹配机制
2026-02-03 17:22:55 +08:00

511 lines
14 KiB
Plaintext
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-history-container">
<!-- 顶部导航栏 -->
<view class="page-header">
<!-- 左上角:返回主页按钮(箭头+文字 垂直排列) -->
<view class="nav-left" @click="goBackToHome">
<text class="nav-icon">←</text>
</view>
<!-- 页面标题居中 -->
<text class="page-title">历史订单</text>
<!-- 右上角留空 -->
<view class="nav-right"></view>
</view>
<!-- 主要内容区域 -->
<view class="content-wrapper">
<!-- 订单列表 -->
<view v-if="orderList.length > 0" class="order-list">
<view v-for="order in orderList" :key="order.id" class="order-item">
<view class="order-header">
<text class="order-id">订单号: {{ order.order_no }}</text>
<text class="order-status" :class="getOrderStatusClass(order.status)">{{ getOrderStatusText(order.status) }}</text>
</view>
<view class="order-addresses">
<view class="address-item">
<text class="address-icon">📍</text>
<view class="address-info">
<text class="address-label">取货地址</text>
<text class="address-text">{{ order.pickup_address.detail }}</text>
<text class="contact-info">联系人: {{ order.pickup_contact.name }} {{ order.pickup_contact.phone }}</text>
</view>
</view>
<view class="address-line"></view>
<view class="address-item">
<text class="address-icon">🏠</text>
<view class="address-info">
<text class="address-label">收货地址</text>
<text class="address-text">{{ order.delivery_address.detail }}</text>
<text class="contact-info">联系人: {{ order.delivery_contact.name }} {{ order.delivery_contact.phone }}</text>
</view>
</view>
</view>
<view class="order-details">
<text class="order-info">配送费: ¥{{ order.delivery_fee }}</text>
<text class="order-info">预计距离: {{ order.distance }}km</text>
<text class="order-info">预计时间: {{ order.estimated_time }}分钟</text>
</view>
<view class="order-actions">
<button class="action-btn primary" @click="viewOrderDetail(order)">查看详情</button>
</view>
</view>
</view>
<!-- 无数据时显示 -->
<view v-else class="no-data">
<text class="no-data-text">暂无历史订单</text>
</view>
</view>
</view>
</template>
<script lang="uts">
import type { DeliveryTaskType } from '@/types/mall-types.uts'
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
export default {
data() {
return {
// 模拟历史订单数据
orderList: [] as Array<DeliveryTaskType>
}
},
onLoad() {
this.loadOrderHistory()
},
onShow() {
// 页面每次显示时都检查是否有新的已完成订单
this.checkForNewCompletedOrder()
},
methods: {
// 检查是否有新的已完成订单
checkForNewCompletedOrder() {
const completedOrderFromStorage = uni.getStorageSync('completed_order_for_history')
if (completedOrderFromStorage) {
// 仅在本地存储条目标记为已完成status >= 4时才合并到历史
const storedStatus = Number(completedOrderFromStorage && (completedOrderFromStorage.status ?? 0))
if (storedStatus >= 4) {
const exists = this.orderList.some(order => order.id === completedOrderFromStorage.id)
if (!exists) {
this.orderList.unshift(completedOrderFromStorage)
}
} else {
console.warn('checkForNewCompletedOrder: ignoring stored completed_order_for_history with non-completed status', completedOrderFromStorage)
}
// 清除本地存储,防止下次进入页面时重复添加
uni.removeStorageSync('completed_order_for_history')
}
},
// 加载历史订单(从数据库读取)
async loadOrderHistory() {
try {
await supaReady
} catch (e) {
console.warn('supaReady failed', e)
}
const uid = getCurrentUserId()
console.log('loadOrderHistory: currentUserId=', uid)
// 首先尝试解析出 ml_delivery_drivers 的 iddriver_id避免直接使用 auth user id 导致不匹配
let driverId: string | null = null
try {
if (uid && uid !== '') {
// 尝试直接按 user_id 查找 driver
const drvRes: any = await supa.from('ml_delivery_drivers').select('id').eq('user_id', uid).limit(1).execute()
if (drvRes && Array.isArray(drvRes.data) && drvRes.data.length > 0) {
driverId = drvRes.data[0].id
} else {
// 回退:尝试 ak_users 表根据 auth_id 查出 ak_users.id再查 ml_delivery_drivers
const akRes: any = await supa.from('ak_users').select('id').eq('auth_id', uid).limit(1).execute()
if (akRes && Array.isArray(akRes.data) && akRes.data.length > 0) {
const akId = akRes.data[0].id
const drvRes2: any = await supa.from('ml_delivery_drivers').select('id').eq('user_id', akId).limit(1).execute()
if (drvRes2 && Array.isArray(drvRes2.data) && drvRes2.data.length > 0) {
driverId = drvRes2.data[0].id
}
}
}
}
} catch (err) {
console.error('loadOrderHistory: driver lookup failed', err)
}
// 直接以 ml_delivery_tasks 作为数据源(配送端真源)
let tasksRes: any = { data: [] }
try {
if (driverId) {
tasksRes = await supa.from('ml_delivery_tasks')
.select('*')
.eq('driver_id', driverId)
.gte('status', 2)
.order('created_at', { ascending: false })
.limit(200)
.execute()
} else {
tasksRes = { data: [] }
}
} catch (err) {
console.error('loadOrderHistory: ml_delivery_tasks query failed', err)
tasksRes = { data: [] }
}
console.log('loadOrderHistory: tasksRes=', tasksRes)
// 如果任务包含 order_id则从 ml_orders 查询 order_no 用于显示
const orderIdsFromTasks = (tasksRes && Array.isArray(tasksRes.data)) ? tasksRes.data.map((r: any) => r.order_id).filter(Boolean) : []
let ordersRes: any = { data: [] }
try {
if (orderIdsFromTasks.length > 0) {
ordersRes = await supa.from('ml_orders').select('id,order_no').in('id', orderIdsFromTasks).execute()
}
} catch (err) {
console.warn('loadOrderHistory: ml_orders lookup failed', err)
ordersRes = { data: [] }
}
console.log('loadOrderHistory: ordersRes=', ordersRes)
const orderNoMap: Record<string,string> = {}
if (ordersRes && Array.isArray(ordersRes.data)) {
ordersRes.data.forEach((o: any) => { if (o && o.id) orderNoMap[o.id] = o.order_no })
}
const parseAddress = (a: any) => {
if (!a) return { detail: '', area: '' }
let obj = a
if (typeof a === 'string') {
try { obj = JSON.parse(a) } catch (e) { obj = { detail: a } }
}
const detail = obj.detail || obj.address || obj.full_address || obj.address_detail || obj.name || ''
const area = (obj.city || obj.district || obj.area || '')
return { detail, area }
}
const parseContact = (c: any) => {
if (!c) return { name: '', phone: '' }
let obj = c
if (typeof c === 'string') {
try { obj = JSON.parse(c) } catch (e) { obj = { name: c } }
}
return { name: obj.name || obj.contact_name || obj.receiver_name || '', phone: obj.phone || obj.mobile || obj.contact_phone || '' }
}
const mapTaskToOrder = (t: any) => ({
id: t.id,
// 优先使用任务自身的 order_no 字段(若存在),否则使用从 ml_orders 查询到的 order_no
order_no: t.order_no || t.orderNo || t.trade_no || orderNoMap[t.order_id] || '',
status: Number(t.status) || 0,
pickup_address: parseAddress(t.pickup_address),
delivery_address: parseAddress(t.delivery_address),
pickup_contact: parseContact(t.pickup_contact),
delivery_contact: parseContact(t.delivery_contact),
delivery_fee: Number(t.delivery_fee) || 0,
distance: Number(t.distance) || 0,
estimated_time: Number(t.estimated_time) || 0,
created_at: t.created_at,
order_id: t.order_id || ''
})
this.orderList = (tasksRes && Array.isArray(tasksRes.data)) ? tasksRes.data.map(mapTaskToOrder) : []
// 检查是否有新完成的订单(在加载初始数据后)
this.checkForNewCompletedOrder()
},
// 获取订单状态样式
getOrderStatusClass(status: number): string {
switch (status) {
case 1: return 'status-pending'
case 2: return 'status-accepted'
case 3: return 'status-picking'
case 4: return 'status-picked' // 已取货
case 5: return 'status-delivered' // 已送达
default: return 'status-default'
}
},
// 获取订单状态文本
getOrderStatusText(status: number): string {
switch (status) {
case 1: return '待接取'
case 2: return '已接取'
case 3: return '取货中'
case 4: return '已取货'
case 5: return '已完成'
default: return '未知状态'
}
},
// 查看订单详情
viewOrderDetail(order: any) {
// 优先传递 ml_orders.id但如果订单表对应行确实缺失详情页逻辑现在支持用 Task ID 回退显示
const targetId = order.order_id || order.id
uni.navigateTo({
url: `/pages/mall/delivery/order-detail?id=${targetId}&status=${order.status}`
})
},
// 返回主页
goBackToHome() {
uni.reLaunch({
url: '/pages/mall/delivery/index'
})
}
}
}
</script>
<style lang="scss">
.order-history-container {
background-color: #f5f5f5;
min-height: 100vh;
padding: 20rpx 30rpx;
}
.page-header {
background-color: #fff;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #e9ecef;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
position: relative;
min-height: 80rpx; /* 确保有足够空间放垂直排列的按钮和标题 */
}
.nav-left {
position: absolute;
top: 20rpx;
left: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 10rpx;
border-radius: 8rpx;
transition: background-color 0.2s ease;
}
.nav-left:hover {
background-color: #f0f0f0; /* 悬停效果 */
}
.nav-left:active {
background-color: #e0e0e0; /* 点击效果 */
}
.nav-icon {
font-size: 36rpx;
color: #333;
margin-bottom: 5rpx;
}
.nav-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
text-align: center;
}
.page-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-top: 20rpx; /* 与 nav-left 保持一定距离 */
}
.nav-right {
/* 为了保持左右对齐,右侧需要一个占位元素 */
width: 1rpx;
height: 1rpx;
}
.content-wrapper {
margin-top: 20rpx;
}
.order-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.order-item {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
border-left: 6rpx solid #74b9ff;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #f8f9fa;
}
.order-id {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.order-status {
font-size: 24rpx;
padding: 6rpx 12rpx;
border-radius: 20rpx;
font-weight: 500;
}
.status-pending {
background-color: #E3F2FD;
color: #1976D2;
}
.status-accepted {
background-color: #FFF3E0;
color: #F57C00;
}
.status-picking {
background-color: #FFF3E0;
color: #F57C00;
}
.status-picked {
background-color: #E8F5E8;
color: #388E3C;
}
.status-delivered {
background-color: #E8F5E8;
color: #388E3C;
}
.status-default {
background-color: #F8F8F8;
color: #666;
}
.order-addresses {
margin-bottom: 20rpx;
}
.address-item {
display: flex;
align-items: flex-start;
margin-bottom: 15rpx;
}
.address-icon {
font-size: 28rpx;
margin-right: 15rpx;
margin-top: 5rpx;
}
.address-info {
display: flex;
flex-direction: column;
flex: 1;
}
.address-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.address-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.contact-info {
font-size: 24rpx;
color: #666;
}
.address-line {
width: 2rpx;
height: 30rpx;
background-color: #ddd;
margin: 10rpx 0 10rpx 14rpx;
}
.order-details {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
padding: 15rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
}
.order-info {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
font-size: 24rpx;
color: #666;
margin: 0 5rpx;
}
.order-actions {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
font-weight: 500;
padding: 0 10rpx;
box-sizing: border-box;
}
.primary {
background-color: #4CAF50;
color: #fff;
}
.secondary {
background-color: #f0f0f0;
color: #333;
border: 1rpx solid #ddd;
}
.no-data {
text-align: center;
padding: 80rpx 30rpx;
border-radius: 16rpx;
background-color: #fff;
}
.no-data-text {
font-size: 32rpx;
color: #999;
margin-bottom: 15rpx;
}
</style>