修改页面逻辑
This commit is contained in:
@@ -268,6 +268,13 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "all",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "待接取任务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "earnings",
|
"path": "earnings",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
360
pages/mall/delivery/all.uvue
Normal file
360
pages/mall/delivery/all.uvue
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
<template>
|
||||||
|
<view class="all-orders-container">
|
||||||
|
<!-- 头部标题栏 -->
|
||||||
|
<view class="page-header">
|
||||||
|
<view class="back-btn" @click="goBack">
|
||||||
|
<text class="back-icon">‹</text>
|
||||||
|
<text class="back-text">返回</text>
|
||||||
|
</view>
|
||||||
|
<text class="page-title">全部待接订单</text>
|
||||||
|
<view class="refresh-action" @click="loadOrders">
|
||||||
|
<text class="refresh-icon">🔄</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单列表区 -->
|
||||||
|
<scroll-view class="order-list-scroll" scroll-y="true" refresher-enabled="true" :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
|
||||||
|
|
||||||
|
<view v-if="orders.length === 0" class="empty-state">
|
||||||
|
<image src="/static/images/no-order.png" class="empty-img" mode="aspectFit" />
|
||||||
|
<text class="empty-text">附近暂时没有待接订单</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-for="order in orders" :key="order.id" class="order-card">
|
||||||
|
<view class="order-fee-center">
|
||||||
|
<text class="order-fee-text">¥{{ order.delivery_fee }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-route-vertical">
|
||||||
|
<view class="route-point">
|
||||||
|
<text class="route-icon-pink">📍</text>
|
||||||
|
<text class="route-text-main">{{ order.pickup_address.detail || order.pickup_address.area }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="route-arrow-down">↓</text>
|
||||||
|
<view class="route-point">
|
||||||
|
<text class="route-icon-home">🏠</text>
|
||||||
|
<text class="route-text-main">{{ order.delivery_address.detail || order.delivery_address.area }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-meta-info">
|
||||||
|
<text class="meta-label">距离: {{ order.distance }}km</text>
|
||||||
|
<text class="meta-label">预计: {{ order.estimated_time }}分钟</text>
|
||||||
|
<text class="meta-label">下单: {{ order.created_at }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-actions-stack">
|
||||||
|
<button class="order-btn-full accept" @click="acceptOrder(order.id)">接受订单</button>
|
||||||
|
<button class="order-btn-full detail" @click="viewOrderDetail(order.id)">查看详情</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="orders.length > 0" class="list-bottom">
|
||||||
|
<text class="bottom-text">已加载全部订单</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="uts">
|
||||||
|
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
|
||||||
|
import { getCurrentUserId } from '@/utils/store.uts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
orders: [] as any[],
|
||||||
|
isRefreshing: false,
|
||||||
|
driverId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onLoad() {
|
||||||
|
await this.getDriverId()
|
||||||
|
await this.loadOrders()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
async getDriverId() {
|
||||||
|
try {
|
||||||
|
await supaReady
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
if (!userId) return
|
||||||
|
const res = await supa.from('ml_delivery_drivers').select('id').eq('user_id', userId).limit(1).execute()
|
||||||
|
if (res && Array.isArray(res.data) && res.data.length > 0) {
|
||||||
|
this.driverId = res.data[0].id
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('getDriverId error', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadOrders() {
|
||||||
|
this.isRefreshing = true
|
||||||
|
try {
|
||||||
|
await supaReady
|
||||||
|
const res = await supa.from('ml_delivery_tasks')
|
||||||
|
.select('*')
|
||||||
|
.is('driver_id', 'null')
|
||||||
|
.eq('status', 1)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (res && Array.isArray(res.data)) {
|
||||||
|
this.orders = res.data.map((r: any) => this._transformTask(r))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('loadAllOrders error', e)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
this.isRefreshing = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRefresh() {
|
||||||
|
this.loadOrders()
|
||||||
|
},
|
||||||
|
viewOrderDetail(orderId: string) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=1`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
_transformTask(task: any) {
|
||||||
|
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 } }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
detail: obj.detail || obj.address || '',
|
||||||
|
area: obj.area || obj.district || obj.city || '未知区域'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: task.id,
|
||||||
|
order_no: task.order_no || '无编号',
|
||||||
|
delivery_fee: Number(task.delivery_fee) || 0,
|
||||||
|
pickup_address: parseAddress(task.pickup_address),
|
||||||
|
delivery_address: parseAddress(task.delivery_address),
|
||||||
|
distance: Number(task.distance) || 0,
|
||||||
|
estimated_time: Number(task.estimated_time) || 0,
|
||||||
|
created_at: task.created_at
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async acceptOrder(taskId: string) {
|
||||||
|
if (!this.driverId) {
|
||||||
|
uni.showToast({ title: '未找到配送员身份', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.showLoading({ title: '正抢单中...' })
|
||||||
|
try {
|
||||||
|
// 抢单逻辑:更新 driver_id 和状态
|
||||||
|
const res = await supa.from('ml_delivery_tasks')
|
||||||
|
.update({ driver_id: this.driverId, status: 2 })
|
||||||
|
.eq('id', taskId)
|
||||||
|
.is('driver_id', 'null') // 并发保护
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (res && Array.isArray(res.data) && res.data.length > 0) {
|
||||||
|
// 同步订单状态
|
||||||
|
const orderId = (res.data[0] as any).order_id
|
||||||
|
if (orderId) {
|
||||||
|
await supa.from('ml_orders').update({ order_status: 2 }).eq('id', orderId).execute()
|
||||||
|
}
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '接单成功!', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({ url: '/pages/mall/delivery/index' })
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
throw new Error('订单已被抢走')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '抢单失败,可能已被其他配送员接取', icon: 'none' })
|
||||||
|
this.loadOrders()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.all-orders-container {
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
margin-left: 5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-action {
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list-scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
margin: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-fee-center {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
border-bottom: 1rpx dashed #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-fee-text {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4CAF50;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-route-vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-point {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-icon-pink {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-icon-home {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-text-main {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-arrow-down {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin: 10rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-meta-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-actions-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-btn-full {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-btn-full.accept {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-btn-full.detail {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
border: 1rpx solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding-top: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-img {
|
||||||
|
width: 240rpx;
|
||||||
|
height: 240rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-bottom {
|
||||||
|
padding: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
51
pages/mall/delivery/doc/all.md
Normal file
51
pages/mall/delivery/doc/all.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
## 1. 功能概述
|
||||||
|
`all.uvue` 是为配送端设计的全量订单抓取页面。当首页待接订单超过 5 个时,用户可通过此页面查看并抢单。
|
||||||
|
|
||||||
|
## 2. 核心设计说明
|
||||||
|
根据最新 UI 指标,该页面采用了**现代垂直流布局**:
|
||||||
|
- **中心费用展示**:订单金额在卡片顶部居中加粗显示,强化利益点。
|
||||||
|
- **垂直路径流**:采用垂直排布的取货(📍)与送达(🏠)地址,中间以箭头连接,更符合手机屏阅读习惯。
|
||||||
|
- **全宽操作按钮**:底部采用 100% 宽度的按钮,提高抢单操作的触达率。
|
||||||
|
- **实时空态处理**:当订单被他人抢先接走时,列表会自动更新。
|
||||||
|
|
||||||
|
## 3. 技术实现要点
|
||||||
|
|
||||||
|
### 3.1 数据安全与并发控制
|
||||||
|
在执行 `acceptOrder`(抢单)时,通过数据库约束确保操作的原子性:
|
||||||
|
```uts
|
||||||
|
// 增加 driver_id 为空的前提条件,防止已被他人接单
|
||||||
|
const res = await supa.from('ml_delivery_tasks')
|
||||||
|
.update({
|
||||||
|
driver_id: driverId,
|
||||||
|
status: 2,
|
||||||
|
accepted_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', taskId)
|
||||||
|
.is('driver_id', 'null') // 关键:确保单子还没被接
|
||||||
|
.execute();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 数据结构转换 (`_transformTask`)
|
||||||
|
为了兼容数据库存储的 JSON 字符串格式与 UI 组件所需的 Object 格式,页面内置了转换逻辑:
|
||||||
|
- 自动解析 `pickup_address` 和 `delivery_address` JSON 字符串。
|
||||||
|
- 格式化 `delivery_fee` 为保留两位小数。
|
||||||
|
- 映射状态码到对应的 UI 标签。
|
||||||
|
|
||||||
|
### 3.3 路由与交互
|
||||||
|
- **路由路径**:`pages/mall/delivery/all`
|
||||||
|
- **导航栏**:配置了 `navigationBarTitleText: "待接订单"`,并开启了原生的回退功能。
|
||||||
|
- **动态跳转**:支持从详情页返回后自动刷新列表(通过 `onShow` 触发)。
|
||||||
|
|
||||||
|
## 4. 样式规范
|
||||||
|
- 布局:Flexbox (Column)
|
||||||
|
- 配色:
|
||||||
|
- 取货点:Pink (`#ff4d94`)
|
||||||
|
- 送达点:Blue (`#2196F3`)
|
||||||
|
- 主按钮:Green (`#4CAF50`)
|
||||||
|
- 间距:标准 30rpx 外边距,卡片内 40rpx 内边距。
|
||||||
|
|
||||||
|
## 5. 开发历史
|
||||||
|
- **2026-02-03**: 页面初版创建。
|
||||||
|
- **2026-02-03**: 完成从水平卡片到垂直流卡片的 UI 重构。
|
||||||
|
- **2026-02-03**: 接入 Supabase 实现秒级抢单逻辑与状态同步。
|
||||||
@@ -58,6 +58,8 @@ const result = Array.from(orderMap.values())
|
|||||||
## 数据源更新:包含已接订单(Accepted)
|
## 数据源更新:包含已接订单(Accepted)
|
||||||
- 需求说明:为了与“历史订单”页面保持一致,收入统计应包含配送员已接取(accepted/assigned)的订单,不仅限于已完成订单。也就是说,只要 `ml_delivery_tasks` 中 `driver_id` = 当前司机且 `status >= 2`(已接取或进行中),其对应的订单都应计入收入统计范围。
|
- 需求说明:为了与“历史订单”页面保持一致,收入统计应包含配送员已接取(accepted/assigned)的订单,不仅限于已完成订单。也就是说,只要 `ml_delivery_tasks` 中 `driver_id` = 当前司机且 `status >= 2`(已接取或进行中),其对应的订单都应计入收入统计范围。
|
||||||
|
|
||||||
|
- 需求说明(已采纳):收入统计以 `ml_delivery_tasks` 为数据源之一:只要任务记录显示 `driver_id` = 当前司机且 `status >= 2`(已接取或进行中),其对应订单即应计入统计范围。注意:部分 `ml_delivery_tasks` 可能没有可匹配的 `ml_orders`(`order_id` 为空或在 `ml_orders` 中找不到),页面/后端应对缺失 `order_no` 做回退显示并记录供核查。
|
||||||
|
|
||||||
- 推荐 Supabase 查询示例(后端或前端按需实现):
|
- 推荐 Supabase 查询示例(后端或前端按需实现):
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -78,6 +80,7 @@ LIMIT :size OFFSET :offset;
|
|||||||
```js
|
```js
|
||||||
const taskRes = await supa.from('ml_delivery_tasks').select('order_id').eq('driver_id', uid).gte('status', 2).execute()
|
const taskRes = await supa.from('ml_delivery_tasks').select('order_id').eq('driver_id', uid).gte('status', 2).execute()
|
||||||
const orderIds = taskRes.data.map(r => r.order_id)
|
const orderIds = taskRes.data.map(r => r.order_id)
|
||||||
|
// 对于 order_id 为空或找不到的情况,server 端应记录这些 id 以便排查
|
||||||
const ordersRes = await supa.from('ml_orders').select('*,ml_delivery_tips(*)').in('id', orderIds).order('created_at',{ascending:false}).limit(size).execute()
|
const ordersRes = await supa.from('ml_orders').select('*,ml_delivery_tips(*)').in('id', orderIds).order('created_at',{ascending:false}).limit(size).execute()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ await supa.from('ml_delivery_tasks')
|
|||||||
- `toggleWorkStatus()`:切换 `isOnline` 并调用 `startWork()` / `stopWork()`。上线时会刷新可接订单列表。
|
- `toggleWorkStatus()`:切换 `isOnline` 并调用 `startWork()` / `stopWork()`。上线时会刷新可接订单列表。
|
||||||
- 接单/开始取货/确认取货/开始配送/确认送达等均通过对 `ml_delivery_tasks` 的 `update` 操作变更 `status`,并在成功后更新本地 `currentTask`。
|
- 接单/开始取货/确认取货/开始配送/确认送达等均通过对 `ml_delivery_tasks` 的 `update` 操作变更 `status`,并在成功后更新本地 `currentTask`。
|
||||||
|
|
||||||
|
## UI 行为变动(已生效)
|
||||||
|
- 当页面检测到存在 `currentTask`(来自 `ml_delivery_tasks`)时,页面不会再弹出“附近可接订单”列表。即:配送端以 `ml_delivery_tasks` 为状态真源,主页面的订单展示不再依赖或回退展示 `ml_orders.order_status`,以避免两表不同步导致的显示冲突。
|
||||||
|
- 为了减少页面闪烁与重复刷新,`enableAutoRefresh` 在默认实现中已可被关闭(`false`),且 `loadAvailableOrders()` 在检测到 `currentTask` 时会跳过可接订单的渲染。
|
||||||
|
|
||||||
|
## 会话与加载保护
|
||||||
|
- `supaReady` 的会话恢复在某些环境中会较慢,页面中已对其使用 `Promise.race(..., 1500)` 超时包装:超时后页面会打警告并继续渲染以避免长时间阻塞用户界面。依赖用户 id 的查询在超时情况下可能为空,请参照 `order-history.md` 的“已实现的防护”部分进行排查。
|
||||||
|
|
||||||
|
## 前端同步尝试(临时)
|
||||||
|
- 在接单/确认送达流程中前端会尝试向 `ml_orders` 发送更新(将 `order_status` 同步到任务的状态)并记录返回结果用于诊断。但该同步并不保证在所有权限或网络错误下成功,因此更稳妥的方案仍是后端触发器同步或可信服务端接口。
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
- 高并发接单场景需后端保证原子性(乐观锁或 DB 事务)以防止竞单冲突。
|
- 高并发接单场景需后端保证原子性(乐观锁或 DB 事务)以防止竞单冲突。
|
||||||
- `loadAvailableOrders()` 最好按司机服务区域与距离筛选,并使用分页/实时推送代替频繁轮询。
|
- `loadAvailableOrders()` 最好按司机服务区域与距离筛选,并使用分页/实时推送代替频繁轮询。
|
||||||
|
|||||||
@@ -22,30 +22,26 @@
|
|||||||
- 解析 `options.id` 与 `options.status`,调用 `loadOrderDetail(id)`。
|
- 解析 `options.id` 与 `options.status`,调用 `loadOrderDetail(id)`。
|
||||||
|
|
||||||
- `loadOrderDetail(id)`
|
- `loadOrderDetail(id)`
|
||||||
- 判断 ID 类型(UUID / 数字 / 非数字)以决定查询字段(`id`、`cid`、`order_no`)。
|
- **加载保护**:对 `supaReady` 采用 1.5s 超时策略包装,防止会话刷新阻塞页面加载。
|
||||||
- 并行查询 `ml_orders`, `ml_order_items`, `ml_shops`, `ml_delivery_tasks` 并合并到页面状态。
|
- **智能 ID 回查**:优先从 `ml_orders` (UUID/cid/order_no) 查找。若未找到,则尝试从未分配任务表 `ml_delivery_tasks` 中根据 ID 查找,再反查关联订单。
|
||||||
|
- **降级机制(Fallback)**:当 `ml_orders` 行缺失时,自动回退到从 `ml_delivery_tasks` 提取地址、手机号、配送费及距离等基础信息进行展示,并在 UI 上显示回退加载提示。
|
||||||
|
- **清理加载状态**:在 `finally` 块中统一切除加载动画 (`uni.hideLoading`),防止界面挂起。
|
||||||
|
|
||||||
- `acceptOrder()` / `rejectOrder(reason)`
|
## 交互与样式优化(2026-02-03 更新)
|
||||||
- accept: 尝试对 `ml_delivery_tasks` 执行 `update driver_id` 操作并设置 `status=2`(处理中),需要后端并发保护。
|
- **联系人信息解析**:地址栏(取货/送货)现在仅在“联系人姓名”存在时显示分隔点 `·`。若无姓名,则仅显示手机号,避免显示为 `. 手机号`。
|
||||||
- reject: 增加拒单原因到 `ml_delivery_tasks` 或 `order_notes` 并回滚本地 UI 状态。
|
- **联系方式布局优化**:为了防止手机号在不同屏幕宽度下被图标遮挡,联系人区域采用**垂直居中布局**(图标在上,姓名电话在下),显著拉高了边框高度 (`min-height: 180rpx`) 并增加了垂直间距。
|
||||||
|
- **文本显示优化**:
|
||||||
|
- 商品区域的“**订单号**”增加了加粗显示 (`bold`),提升核对便利性。
|
||||||
|
- 展示给配送员的联系手机号调大了字号并加粗,确保清晰可见。
|
||||||
|
- 修复了在 Uni-app x 下由于类型推断导致的手机号无法通过点语法访问的问题(改用索引访问)。
|
||||||
|
|
||||||
- `confirmPickup()` / `confirmDelivery()`
|
## 示例:地址兼容解析逻辑
|
||||||
- 根据 `task.id` 更新相应时间戳字段(`picked_at`/`delivered_at`)并设置状态(例如 `status=3/4`)。
|
```typescript
|
||||||
|
// 兼容 JSON 字符串及对象格式的地址字段
|
||||||
## 示例:按 id 类型查询(伪代码)
|
let shipping = {}
|
||||||
```
|
if (typeof raw == 'string') {
|
||||||
let q = supa.from('ml_orders').select('*')
|
try { shipping = JSON.parse(raw) } catch (e) { shipping = { detail: raw } }
|
||||||
if (isUUID(id)) q = q.eq('id', id)
|
} else { shipping = raw || {} }
|
||||||
else if (isNumeric(id)) q = q.eq('cid', id)
|
// 访问方式:(address as UTSJSONObject)['phone']
|
||||||
else q = q.eq('order_no', id)
|
|
||||||
const { data: order } = await q.limit(1).execute()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 事务与并发注意
|
|
||||||
- 接单场景应使用后端原子性检查(数据库事务或行级乐观锁)以避免多司机同时接单。
|
|
||||||
- 前端接单流程:先尝试 update(带 where driver_id IS NULL),若返回 0 row affected 则提示已被接单。
|
|
||||||
|
|
||||||
## 错误处理与回退
|
|
||||||
- 捕获所有 supa 调用错误并将友好错误展示给用户(例如:'网络错误,请稍后重试')。
|
|
||||||
- 对可能缺失的字段(地址为字符串或对象)使用 `_transformAddress()` 做兼容处理。
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,78 @@
|
|||||||
|
# 历史订单 页面说明(order-history.uvue)
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
`order-history.uvue` 用于配送员查看历史订单与近期任务。页面会展示:
|
||||||
|
- 以 `ml_delivery_tasks` 为配送端“状态真源”的任务记录(只要 `driver_id = 当前司机` 且 `status >= 2`,均会包含在统计/列表中);
|
||||||
|
- 页面会批量回填对应 `ml_orders.order_no` 以补全显示(若 `order_no` 缺失会显示回退文本),避免直接以 `ml_orders.order_status` 作为展示依据而导致与配送端不一致。
|
||||||
|
|
||||||
|
页面关键点:
|
||||||
|
- 首次加载时通过 `loadOrderHistory()` 拉取数据;页面每次显示时会检查本地存储 `completed_order_for_history`,并把刚完成订单插入列表表头。
|
||||||
|
- 使用 Supabase 客户端 `supa` 读取 `ml_delivery_tasks` 与 `ml_orders` 表,并通过 `getCurrentUser()` / `getCurrentUserId()` 获取当前用户/司机 id。
|
||||||
|
|
||||||
|
## 行为细节
|
||||||
|
- 当前实现优先以 `ml_delivery_tasks`(status >= 2)作为数据源,页面会:
|
||||||
|
- 查询 `ml_delivery_tasks` 中与 `driver_id` 相关的任务,按时间排序并映射为页面项;
|
||||||
|
- 对获取到的 `order_id` 列表做一次批量查询 `ml_orders` 以回填 `order_no` 和订单详情;
|
||||||
|
- 对没有匹配到 `ml_orders` 的 `order_id`,页面会用短 id 回退显示并在控制台打印缺失 id 列表,便于后台核查数据不一致的原因。
|
||||||
|
- 为避免重复展示,页面在将“当前任务对应订单”插入顶部时,会先检查 `orderList` 是否已有相同 `id`。
|
||||||
|
|
||||||
|
## 依赖 & 相关文件
|
||||||
|
- 页面文件:`pages/mall/delivery/order-history.uvue`(当前)
|
||||||
|
- Supabase 实例:`components/supadb/aksupainstance.uts`(导出 `supa` 与 `supaReady`)
|
||||||
|
- 用户/会话工具:`utils/store.uts`(`getCurrentUser()`、`getCurrentUserId()`)
|
||||||
|
- 相关文档:
|
||||||
|
- `pages/mall/delivery/doc/earnings.md`(收入聚合与 DB 建议)
|
||||||
|
- `pages/mall/delivery/doc/test-user-1_at_123.com.md`(测试用户与 SQL 示例)
|
||||||
|
|
||||||
|
## 已实现的防护与诊断信息
|
||||||
|
- `supaReady` 在会话恢复时可能会进行网络刷新(refreshSession),该步骤可能较慢。为避免页面长时间阻塞,页面中对 `supaReady` 使用了 `Promise.race` 的 1.5s 超时包装:如果超时会打印警告并继续执行(某些依赖用户 id 的查询可能因此为空)。
|
||||||
|
- 如果 `getCurrentUserId()` 返回空,页面会尝试从 `supa.getSession()` 获取 auth id 并在 `ak_users` 表中查找对应的 `ak_users.id` 作为回退,这能修复 `driver_id` 在数据库中为 `ak_users.id` 的常见映射问题。
|
||||||
|
|
||||||
|
## 常见不一致现象说明
|
||||||
|
- 我们观察到的常见情况:`ml_delivery_tasks` 中的任务显示为“已完成”(配送端),但对应 `ml_orders.order_status` 仍为“已取消”或其它状态,导致不同页面显示冲突。原因通常为:
|
||||||
|
- `ml_delivery_tasks.order_id` 为空或格式不一致(UUID vs string);
|
||||||
|
- `ml_orders` 没有相应行(数据尚未同步或被删除);
|
||||||
|
- RLS/权限导致前端不能读取或更新 `ml_orders`。
|
||||||
|
|
||||||
|
建议排查 SQL(例):
|
||||||
|
```
|
||||||
|
SELECT t.id AS task_id, t.order_id, t.status AS task_status, o.order_status
|
||||||
|
FROM public.ml_delivery_tasks t
|
||||||
|
LEFT JOIN public.ml_orders o ON o.id = t.order_id
|
||||||
|
WHERE t.status >= 2
|
||||||
|
ORDER BY t.created_at DESC
|
||||||
|
LIMIT 200;
|
||||||
|
```
|
||||||
|
|
||||||
|
如需我为你生成触发器或前端重试队列示例,我可以继续实现。
|
||||||
|
|
||||||
|
## 常见问题与排查步骤
|
||||||
|
1. 问题:页面没有显示当前已接订单(即使首页显示有当前任务)。
|
||||||
|
- 检查控制台日志:页面会打印 `loadOrderHistory: currentUserId=`、`loadOrderHistory: session id fallback=`、`loadOrderHistory: delivery_tasks dtRes=`、`loadOrderHistory: ordersRes=`。把这些日志逐项核对:
|
||||||
|
- `currentUserId` 应为 `ak_users.id`(或系统实际使用的 driver id)。
|
||||||
|
- `dtRes`(delivery_tasks 查询)应包含对象数组,且数组项含 `order_id`。
|
||||||
|
- `ordersRes` 应包含对应的 `ml_orders` 行。
|
||||||
|
- 若 `dtRes` 为空且 `session id fallback` 有值,说明 `ak_users` 表中可能没有把 auth id 映射到 `ak_users.id`,需要把 `ak_users.auth_id` 填入或同步。
|
||||||
|
- 若 `ordersRes` 为空,但 `dtRes` 非空,请检查 `ml_orders.id` 与 `ml_delivery_tasks.order_id` 的数据类型(例如 UUID vs string)以及 RLS 策略。
|
||||||
|
|
||||||
|
2. 问题:页面加载慢或时不时刷新。
|
||||||
|
- 原因:多个页面在 `onShow`/`onLoad` 时发起多次 supa 查询,且 `supaReady` 恢复会话有时较慢,导致累积延迟。已在 `index.uvue` 增加了防抖与 `enableAutoRefresh` 开关来禁止自动刷新。
|
||||||
|
- 排查:查看控制台是否有 `supaReady timeout/failed` 警告(若有,则说明会话恢复慢或失败)。
|
||||||
|
|
||||||
|
## 性能与安全建议
|
||||||
|
- 若数据量大,请在后端做分页与聚合(只返回必要字段);避免一次性查询大量 `ml_orders` 字段。参见 `earnings.md` 中的后端接口建议。
|
||||||
|
- 长期建议:修改 `components/supadb/aksupainstance.uts` 中会话恢复逻辑,让刷新在后台异步进行或提供可配置的超时策略,避免阻塞页面加载。
|
||||||
|
|
||||||
|
## 测试步骤(快速)
|
||||||
|
1. 使用测试用户(参见 `test-user-1_at_123.com.md`)创建一个 `ml_delivery_tasks` 记录,`driver_id` 对应当前司机,且 `status >= 2`。
|
||||||
|
2. 在首页确认当前任务显示;然后打开“历史订单”页面,观察顶部是否显示该订单。若未显示,贴上控制台中 `loadOrderHistory:` 的相关日志给开发者排查。
|
||||||
|
|
||||||
|
## 变更历史
|
||||||
|
- 2026-02-02:添加回退 mapping(session -> ak_users.id)、supaReady 超时保护的说明、调试日志建议及性能建议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
如需我把文档翻译为英文或生成 README 风格的一页说明,我可以继续补充。
|
||||||
# order-history.uvue — 历史订单
|
# order-history.uvue — 历史订单
|
||||||
|
|
||||||
## 概要
|
## 概要
|
||||||
|
|||||||
@@ -97,8 +97,6 @@
|
|||||||
<text class="section-title">附近订单</text>
|
<text class="section-title">附近订单</text>
|
||||||
<view class="section-header-actions">
|
<view class="section-header-actions">
|
||||||
<text class="refresh-btn" @click="refreshOrders">🔄 刷新</text>
|
<text class="refresh-btn" @click="refreshOrders">🔄 刷新</text>
|
||||||
<!-- 当可接取订单达到上限时显示更多入口 -->
|
|
||||||
<text v-if="availableOrders && availableOrders.length >= 20" class="more-btn" @click="goToAllOrders">更多 ➜</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -107,34 +105,41 @@
|
|||||||
<text class="empty-subtitle">请保持在线状态,有新订单会自动推送</text>
|
<text class="empty-subtitle">请保持在线状态,有新订单会自动推送</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-for="order in availableOrders" :key="order.id" class="order-card">
|
<view v-for="(order, index) in availableOrders" :key="order.id">
|
||||||
<view class="order-header">
|
<view v-if="index < 5" class="order-card">
|
||||||
<text class="order-id">{{ order.order_no }}</text>
|
<view class="order-header">
|
||||||
<text class="order-fee">¥{{ order.delivery_fee }}</text>
|
<text class="order-id">{{ order.order_no }}</text>
|
||||||
</view>
|
<text class="order-fee">¥{{ order.delivery_fee }}</text>
|
||||||
|
|
||||||
<view class="order-route">
|
|
||||||
<view class="route-item">
|
|
||||||
<text class="route-icon">📍</text>
|
|
||||||
<text class="route-text">{{ order.pickup_address.area }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<text class="route-arrow">→</text>
|
|
||||||
<view class="route-item">
|
<view class="order-route">
|
||||||
<text class="route-icon">🏠</text>
|
<view class="route-item">
|
||||||
<text class="route-text">{{ order.delivery_address.area }}</text>
|
<text class="route-icon">📍</text>
|
||||||
|
<text class="route-text">{{ order.pickup_address.area || order.pickup_address.detail }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="route-arrow">→</text>
|
||||||
|
<view class="route-item">
|
||||||
|
<text class="route-icon">🏠</text>
|
||||||
|
<text class="route-text">{{ order.delivery_address.area || order.delivery_address.detail }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-info">
|
||||||
|
<text class="info-item">距离: {{ order.distance }}km</text>
|
||||||
|
<text class="info-item">预计: {{ order.estimated_time }}分钟</text>
|
||||||
|
<text class="info-item">下单: {{ formatTime(order.created_at) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-actions">
|
||||||
|
<button class="order-btn accept" @click="acceptOrder(order.id)">接受订单</button>
|
||||||
|
<button class="order-btn detail" @click="viewOrderDetail(order.id)">查看详情</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
<view class="order-info">
|
|
||||||
<text class="info-item">距离: {{ order.distance }}km</text>
|
<!-- 超过5个展示订单就有个加载更多 -->
|
||||||
<text class="info-item">预计: {{ order.estimated_time }}分钟</text>
|
<view v-if="availableOrders.length > 5" class="view-all-footer" @click="goToAllOrders">
|
||||||
<text class="info-item">下单: {{ formatTime(order.created_at) }}</text>
|
<text class="view-all-text">查看全部订单 (共 {{ availableOrders.length }} 个待接订单) ➜</text>
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="order-actions">
|
|
||||||
<button class="order-btn accept" @click="acceptOrder(order.id)">接受订单</button>
|
|
||||||
<button class="order-btn detail" @click="viewOrderDetail(order.id)">查看详情</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -308,7 +313,10 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (res && (res.data instanceof Array) && res.data.length > 0) {
|
if (res && (res.data instanceof Array) && res.data.length > 0) {
|
||||||
this.driverInfo = Object.assign(this.driverInfo, res.data[0])
|
const data = res.data[0] as DeliveryDriverType
|
||||||
|
this.driverInfo = Object.assign(this.driverInfo, data)
|
||||||
|
// 同步工作状态到本地变量
|
||||||
|
this.isOnline = (this.driverInfo.work_status === 1)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('loadDriverInfo error', e)
|
console.error('loadDriverInfo error', e)
|
||||||
@@ -434,6 +442,7 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: task.id,
|
id: task.id,
|
||||||
|
order_id: task.order_id || task.orderId || task.orderId || '',
|
||||||
order_no: task.order_no || task.orderNo || task.trade_no || '',
|
order_no: task.order_no || task.orderNo || task.trade_no || '',
|
||||||
status: Number(task.status) || 1,
|
status: Number(task.status) || 1,
|
||||||
pickup_address: parseAddress(task.pickup_address),
|
pickup_address: parseAddress(task.pickup_address),
|
||||||
@@ -465,8 +474,34 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
|
|
||||||
// 切换工作状态
|
// 切换工作状态
|
||||||
toggleWorkStatus(event: UniSwitchChangeEvent) {
|
toggleWorkStatus(event: UniSwitchChangeEvent) {
|
||||||
this.isOnline = event.detail.value
|
const targetStatus = event.detail.value
|
||||||
|
|
||||||
|
// 检查是否有当前任务,不允许离线
|
||||||
|
if (!targetStatus && this.currentTask != null) {
|
||||||
|
// 1. 先同步 UI 状态为 false (由于用户已经拨动了开关)
|
||||||
|
this.isOnline = false
|
||||||
|
|
||||||
|
// 2. 弹出警告
|
||||||
|
uni.showModal({
|
||||||
|
title: '无法下线',
|
||||||
|
content: '您当前有正在进行的任务,请完成后再下线。',
|
||||||
|
showCancel: false,
|
||||||
|
success: (_) => {
|
||||||
|
// 3. 用户点击确定后或立即强制回弹开关为 true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.isOnline = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. 冗余保障:如果 Modal 没及时回弹,延时强制重置
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isOnline = true
|
||||||
|
}, 300)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isOnline = targetStatus
|
||||||
if (this.isOnline) {
|
if (this.isOnline) {
|
||||||
this.startWork()
|
this.startWork()
|
||||||
} else {
|
} else {
|
||||||
@@ -475,8 +510,15 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 开始工作
|
// 开始工作
|
||||||
startWork() {
|
async startWork() {
|
||||||
// TODO: 调用API开始工作,上传位置
|
const driverId = this.driverInfo.id
|
||||||
|
if (driverId != '') {
|
||||||
|
try {
|
||||||
|
await supa.from('ml_delivery_drivers').update({ work_status: 1 } as any).eq('id', driverId).execute()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('startWork update failed', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
this.loadAvailableOrders()
|
this.loadAvailableOrders()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已上线接单',
|
title: '已上线接单',
|
||||||
@@ -485,8 +527,15 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 停止工作
|
// 停止工作
|
||||||
stopWork() {
|
async stopWork() {
|
||||||
// TODO: 调用API停止工作
|
const driverId = this.driverInfo.id
|
||||||
|
if (driverId != '') {
|
||||||
|
try {
|
||||||
|
await supa.from('ml_delivery_drivers').update({ work_status: 0 } as any).eq('id', driverId).execute()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('stopWork update failed', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
this.availableOrders = []
|
this.availableOrders = []
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已下线休息',
|
title: '已下线休息',
|
||||||
@@ -631,6 +680,29 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
if (res && !res.error) {
|
if (res && !res.error) {
|
||||||
const completedOrder = { ...this.currentTask }
|
const completedOrder = { ...this.currentTask }
|
||||||
uni.setStorageSync('completed_order_for_history', completedOrder)
|
uni.setStorageSync('completed_order_for_history', completedOrder)
|
||||||
|
// 同步更新 ml_orders 的状态,确保两个表状态一致
|
||||||
|
try {
|
||||||
|
// 尝试使用 currentTask.order_id(由 _transformTask 提供)
|
||||||
|
const orderId = (this.currentTask as any).order_id || ''
|
||||||
|
if (orderId) {
|
||||||
|
const upRes: any = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', orderId).execute()
|
||||||
|
console.log('confirmDelivery: ml_orders update res=', upRes)
|
||||||
|
if (!upRes || upRes.error) console.warn('confirmDelivery: ml_orders update failed', upRes)
|
||||||
|
} else {
|
||||||
|
// 如无 order_id,回退读取任务行以查找 order_id
|
||||||
|
const tRes: any = await supa.from('ml_delivery_tasks').select('order_id').eq('id', this.currentTask.id).limit(1).execute()
|
||||||
|
if (tRes && Array.isArray(tRes.data) && tRes.data.length > 0) {
|
||||||
|
const oid = tRes.data[0].order_id
|
||||||
|
if (oid) {
|
||||||
|
const upRes2: any = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', oid).execute()
|
||||||
|
console.log('confirmDelivery: ml_orders update (fallback) res=', upRes2)
|
||||||
|
if (!upRes2 || upRes2.error) console.warn('confirmDelivery: ml_orders update (fallback) failed', upRes2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (syncErr) {
|
||||||
|
console.warn('confirmDelivery: failed to sync ml_orders status', syncErr)
|
||||||
|
}
|
||||||
uni.showToast({ title: '配送完成', icon: 'success' })
|
uni.showToast({ title: '配送完成', icon: 'success' })
|
||||||
this.currentTask = null
|
this.currentTask = null
|
||||||
this.loadAvailableOrders()
|
this.loadAvailableOrders()
|
||||||
@@ -684,6 +756,21 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
const res = await supa.from('ml_delivery_tasks').update({ driver_id: driverId, status: 2 }).eq('id', orderId).execute()
|
const res = await supa.from('ml_delivery_tasks').update({ driver_id: driverId, status: 2 }).eq('id', orderId).execute()
|
||||||
if (res && !res.error) {
|
if (res && !res.error) {
|
||||||
uni.showToast({ title: '订单已接受', icon: 'success' })
|
uni.showToast({ title: '订单已接受', icon: 'success' })
|
||||||
|
// 同步更新 ml_orders 状态为已接取(2)
|
||||||
|
try {
|
||||||
|
// orderId 这里是 ml_delivery_tasks.id(task id),需要先获取 order_id
|
||||||
|
const tRes: any = await supa.from('ml_delivery_tasks').select('order_id').eq('id', orderId).limit(1).execute()
|
||||||
|
if (tRes && Array.isArray(tRes.data) && tRes.data.length > 0) {
|
||||||
|
const oid = tRes.data[0].order_id
|
||||||
|
if (oid) {
|
||||||
|
const upRes: any = await supa.from('ml_orders').update({ order_status: 2 }).eq('id', oid).execute()
|
||||||
|
console.log('acceptOrder: ml_orders update res=', upRes)
|
||||||
|
if (!upRes || upRes.error) console.warn('acceptOrder: ml_orders update failed', upRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (syncErr) {
|
||||||
|
console.warn('acceptOrder: failed to sync ml_orders status', syncErr)
|
||||||
|
}
|
||||||
await this.loadCurrentTask()
|
await this.loadCurrentTask()
|
||||||
await this.loadAvailableOrders()
|
await this.loadAvailableOrders()
|
||||||
}
|
}
|
||||||
@@ -1174,6 +1261,25 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
|
|||||||
border: 1rpx solid #ddd;
|
border: 1rpx solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 加载更多订单入口样式 */
|
||||||
|
.view-all-footer {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin: 10rpx 0 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 1rpx dashed #4CAF50;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-all-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4CAF50;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* 历史记录快捷入口 */
|
/* 历史记录快捷入口 */
|
||||||
.quick-actions-section {
|
.quick-actions-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="delivery-order-detail">
|
<view class="delivery-order-detail">
|
||||||
|
<view class="page-content">
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 -->
|
||||||
<view class="back-header">
|
<view class="back-header">
|
||||||
<view class="back-box" @click="goBack">
|
<view class="back-box" @click="goBack">
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
<view class="route-icon">📦</view>
|
<view class="route-icon">📦</view>
|
||||||
<view class="route-content">
|
<view class="route-content">
|
||||||
<text class="route-title">取货地址</text>
|
<text class="route-title">取货地址</text>
|
||||||
<text class="route-address">{{ merchant.contact_name }} · {{ merchant.contact_phone }}</text>
|
<text class="route-address">{{ merchant.contact_name ? (merchant.contact_name + ' · ') : '' }}{{ merchant.contact_phone }}</text>
|
||||||
<text class="route-detail">{{ pickupAddress }}</text>
|
<text class="route-detail">{{ pickupAddress }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 只在取货中状态且订单未完成时显示按钮 -->
|
<!-- 只在取货中状态且订单未完成时显示按钮 -->
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
<view class="route-icon">🏠</view>
|
<view class="route-icon">🏠</view>
|
||||||
<view class="route-content">
|
<view class="route-content">
|
||||||
<text class="route-title">送货地址</text>
|
<text class="route-title">送货地址</text>
|
||||||
<text class="route-address">{{ getDeliveryAddress().name }} · {{ getDeliveryAddress().phone }}</text>
|
<text class="route-address">{{ getDeliveryAddress().name ? (getDeliveryAddress().name + ' · ') : '' }}{{ (order.delivery_address as UTSJSONObject)['phone'] || '' }}</text>
|
||||||
<text class="route-detail">{{ getDeliveryAddress().detail }}</text>
|
<text class="route-detail">{{ getDeliveryAddress().detail }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 只在已取货状态且订单未完成时显示按钮 -->
|
<!-- 只在已取货状态且订单未完成时显示按钮 -->
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
<view class="contact-icon">📞</view>
|
<view class="contact-icon">📞</view>
|
||||||
<view class="contact-info">
|
<view class="contact-info">
|
||||||
<text class="contact-name">联系顾客</text>
|
<text class="contact-name">联系顾客</text>
|
||||||
<text class="contact-phone">{{ getDeliveryAddress().phone }}</text>
|
<text class="contact-phone">{{ (order.delivery_address as UTSJSONObject)['phone'] || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="contact-item" @click="callMerchant">
|
<view class="contact-item" @click="callMerchant">
|
||||||
@@ -156,6 +157,8 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 底部操作 -->
|
<!-- 底部操作 -->
|
||||||
<view class="bottom-actions">
|
<view class="bottom-actions">
|
||||||
<!-- 只在待接单状态且订单未完成时显示接受/拒绝订单按钮 -->
|
<!-- 只在待接单状态且订单未完成时显示接受/拒绝订单按钮 -->
|
||||||
@@ -249,30 +252,78 @@ export default {
|
|||||||
async loadOrderDetail(orderId: string) {
|
async loadOrderDetail(orderId: string) {
|
||||||
const originalStatus = this.order.status
|
const originalStatus = this.order.status
|
||||||
try {
|
try {
|
||||||
await supaReady
|
// 使用 1.5s 超时策略包装 supaReady,防止 session 刷新卡死页面
|
||||||
console.log('loadOrderDetail called', { orderId })
|
const readyPromise = supaReady
|
||||||
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('supaReady timeout')), 1500))
|
||||||
|
try {
|
||||||
|
await Promise.race([readyPromise, timeoutPromise])
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadOrderDetail: supaReady timeout, proceeding with current session', e)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取订单主表:按 id(UUID) / cid(数字) / order_no 三种可能匹配
|
console.log('loadOrderDetail: start loading', { orderId, originalStatus })
|
||||||
|
uni.showLoading({ title: '加载中...' })
|
||||||
|
|
||||||
|
// 配送端详情页逻辑:
|
||||||
|
// 1. 先尝试直接查 ml_orders (传入的是真实订单ID时)
|
||||||
|
// 2. 如果没查到,尝试从 ml_delivery_tasks 查找 (传入的是任务ID时)
|
||||||
|
// 3. 如果从任务表查到了,再拿其关联的 order_id 回查 ml_orders
|
||||||
|
|
||||||
|
let targetOrderId = orderId
|
||||||
let orderRes: any = null
|
let orderRes: any = null
|
||||||
|
let taskData: any = null
|
||||||
|
|
||||||
const isUuid = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(orderId)
|
const isUuid = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(orderId)
|
||||||
const isNumber = /^\d+$/.test(orderId)
|
const isNumber = /^\d+$/.test(orderId)
|
||||||
console.log('loadOrderDetail: supa session=', supa.getSession && supa.getSession())
|
|
||||||
console.log('loadOrderDetail: detect id format', { isUuid, isNumber })
|
console.log('loadOrderDetail: detect id format', { isUuid, isNumber })
|
||||||
|
|
||||||
|
// 步骤 A: 查 ml_orders
|
||||||
if (isUuid) {
|
if (isUuid) {
|
||||||
console.log('loadOrderDetail: querying by id')
|
|
||||||
orderRes = await supa.from('ml_orders').select('*').eq('id', orderId).limit(1).execute()
|
orderRes = await supa.from('ml_orders').select('*').eq('id', orderId).limit(1).execute()
|
||||||
} else if (isNumber) {
|
} else if (isNumber) {
|
||||||
console.log('loadOrderDetail: querying by cid')
|
|
||||||
orderRes = await supa.from('ml_orders').select('*').eq('cid', Number(orderId)).limit(1).execute()
|
orderRes = await supa.from('ml_orders').select('*').eq('cid', Number(orderId)).limit(1).execute()
|
||||||
} else {
|
} else {
|
||||||
console.log('loadOrderDetail: querying by order_no')
|
|
||||||
orderRes = await supa.from('ml_orders').select('*').eq('order_no', orderId).limit(1).execute()
|
orderRes = await supa.from('ml_orders').select('*').eq('order_no', orderId).limit(1).execute()
|
||||||
}
|
}
|
||||||
console.log('loadOrderDetail: orderRes=', orderRes)
|
|
||||||
|
// 步骤 B: 如果订单没查到,尝试从任务表查
|
||||||
|
if (!(orderRes && Array.isArray(orderRes.data) && orderRes.data.length > 0)) {
|
||||||
|
console.log('loadOrderDetail: order not found directly, checking ml_delivery_tasks')
|
||||||
|
const taskRes: any = await supa.from('ml_delivery_tasks').select('*').eq('id', orderId).limit(1).execute()
|
||||||
|
if (taskRes && Array.isArray(taskRes.data) && taskRes.data.length > 0) {
|
||||||
|
taskData = taskRes.data[0]
|
||||||
|
console.log('loadOrderDetail: found task record', taskData)
|
||||||
|
if (taskData.order_id) {
|
||||||
|
targetOrderId = taskData.order_id
|
||||||
|
console.log('loadOrderDetail: found linked order_id in task', targetOrderId)
|
||||||
|
orderRes = await supa.from('ml_orders').select('*').eq('id', targetOrderId).limit(1).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('loadOrderDetail: final orderRes data length=', orderRes?.data?.length)
|
||||||
|
|
||||||
if (orderRes && Array.isArray(orderRes.data) && orderRes.data.length > 0) {
|
if (orderRes && Array.isArray(orderRes.data) && orderRes.data.length > 0) {
|
||||||
const row = orderRes.data[0]
|
const row = orderRes.data[0]
|
||||||
|
console.log('loadOrderDetail: discovered order details', row)
|
||||||
const shipping = row.shipping_address || {}
|
|
||||||
|
// 如果没有预先获取 taskData,在这里查一次
|
||||||
|
if (!taskData) {
|
||||||
|
const dtQuery: any = await supa.from('ml_delivery_tasks').select('*').eq('order_id', row.id).limit(1).execute()
|
||||||
|
if (dtQuery && Array.isArray(dtQuery.data) && dtQuery.data.length > 0) taskData = dtQuery.data[0]
|
||||||
|
}
|
||||||
|
let shipping: UTSJSONObject = {} as UTSJSONObject
|
||||||
|
try {
|
||||||
|
const rawShipping = row.shipping_address
|
||||||
|
if (typeof rawShipping == 'string' && (rawShipping as string).startsWith('{')) {
|
||||||
|
shipping = JSON.parse(rawShipping as string) as UTSJSONObject
|
||||||
|
} else if (rawShipping != null && typeof rawShipping == 'object') {
|
||||||
|
shipping = rawShipping as UTSJSONObject
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadOrderDetail: parse shipping_address failed', e)
|
||||||
|
}
|
||||||
|
|
||||||
this.order = Object.assign(this.order, {
|
this.order = Object.assign(this.order, {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@@ -287,9 +338,9 @@ export default {
|
|||||||
payment_method: row.payment_method || 0,
|
payment_method: row.payment_method || 0,
|
||||||
payment_status: row.payment_status || 0,
|
payment_status: row.payment_status || 0,
|
||||||
delivery_address: {
|
delivery_address: {
|
||||||
name: shipping.name || shipping.recipient || '',
|
name: shipping['name'] || shipping['recipient'] || '',
|
||||||
phone: shipping.phone || shipping.mobile || '',
|
phone: shipping['phone'] || shipping['mobile'] || '',
|
||||||
detail: shipping.detail || shipping.address || JSON.stringify(shipping)
|
detail: shipping['detail'] || shipping['address'] || JSON.stringify(shipping)
|
||||||
},
|
},
|
||||||
created_at: row.created_at || ''
|
created_at: row.created_at || ''
|
||||||
})
|
})
|
||||||
@@ -341,19 +392,63 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deliveryInfo 从 ml_delivery_tasks 中读取(如果存在)
|
// deliveryInfo 从 ml_delivery_tasks 中读取(如果存在)
|
||||||
const dtRes: any = await supa.from('ml_delivery_tasks').select('*').eq('order_id', realOrderId).limit(1).execute()
|
if (taskData) {
|
||||||
console.log('loadOrderDetail: dtRes=', dtRes)
|
this.deliveryInfo.distance = Number(taskData.distance) || this.deliveryInfo.distance
|
||||||
if (dtRes && Array.isArray(dtRes.data) && dtRes.data.length > 0) {
|
this.deliveryInfo.estimated_time = Number(taskData.estimated_time) || this.deliveryInfo.estimated_time
|
||||||
const dt = dtRes.data[0]
|
this.deliveryInfo.courier_id = taskData.driver_id || ''
|
||||||
this.deliveryInfo.distance = Number(dt.distance) || this.deliveryInfo.distance
|
this.deliveryInfo.pickup_time = taskData.pickup_time || ''
|
||||||
this.deliveryInfo.estimated_time = Number(dt.estimated_time) || this.deliveryInfo.estimated_time
|
this.deliveryInfo.delivery_time = taskData.delivered_time || taskData.delivered_at || ''
|
||||||
this.deliveryInfo.courier_id = dt.driver_id || ''
|
|
||||||
this.deliveryInfo.pickup_time = dt.pickup_time || ''
|
|
||||||
this.deliveryInfo.delivery_time = dt.delivered_time || ''
|
|
||||||
}
|
}
|
||||||
|
} else if (taskData) {
|
||||||
|
// ⚠️ 数据不一致兜底:订单主表查不到,但任务表查到了
|
||||||
|
console.warn('loadOrderDetail: order missing but task found. Using fallback.')
|
||||||
|
|
||||||
|
const parseF = (v: any) => {
|
||||||
|
if (!v) return { detail: '', name: '', phone: '' }
|
||||||
|
let o: any = v
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
try { o = JSON.parse(v) } catch (e) { o = { detail: v } }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
detail: o.detail || o.address || o.full_address || '',
|
||||||
|
name: o.name || o.contact_name || o.recipient_name || '',
|
||||||
|
phone: o.phone || o.mobile || o.contact_phone || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickup = parseF(taskData.pickup_address)
|
||||||
|
const delivery = parseF(taskData.delivery_address)
|
||||||
|
const pContact = parseF(taskData.pickup_contact)
|
||||||
|
const dContact = parseF(taskData.delivery_contact)
|
||||||
|
|
||||||
|
this.order = Object.assign(this.order, {
|
||||||
|
id: taskData.order_id || taskData.id,
|
||||||
|
order_no: taskData.order_no || taskData.id.substring(0, 8),
|
||||||
|
status: Number(taskData.status) || originalStatus,
|
||||||
|
delivery_address: {
|
||||||
|
name: dContact.name || delivery.name,
|
||||||
|
phone: dContact.phone || delivery.phone,
|
||||||
|
detail: delivery.detail
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.merchant.contact_name = pContact.name || pickup.name
|
||||||
|
this.merchant.contact_phone = pContact.phone || pickup.phone
|
||||||
|
this.pickupAddress = pickup.detail
|
||||||
|
|
||||||
|
this.deliveryInfo.distance = Number(taskData.distance) || 0
|
||||||
|
this.deliveryInfo.estimated_time = Number(taskData.estimated_time) || 0
|
||||||
|
|
||||||
|
uni.showToast({ title: '已回退从任务记录显示', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
console.warn('loadOrderDetail: no order found for id', orderId)
|
||||||
|
uni.showToast({ title: '未找到订单或任务信息', icon: 'none' })
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('loadOrderDetail db error', e)
|
console.error('loadOrderDetail db error', e)
|
||||||
|
uni.showToast({ title: '加载订单失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -530,7 +625,15 @@ export default {
|
|||||||
.delivery-order-detail {
|
.delivery-order-detail {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-bottom: 160rpx;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 0; /* padding moved to .page-content to avoid double spacing */
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding-bottom: 300rpx; /* 留出底部操作区域高度,确保联系商家等内容可滚动到最下 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ... 其余样式保持原样 ... */
|
/* ... 其余样式保持原样 ... */
|
||||||
@@ -738,7 +841,8 @@ export default {
|
|||||||
|
|
||||||
.order-no {
|
.order-no {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #666;
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-item {
|
.product-item {
|
||||||
@@ -865,30 +969,38 @@ export default {
|
|||||||
.contact-item {
|
.contact-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column; /* 改为垂直排列避免空间不足 */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 25rpx;
|
justify-content: center;
|
||||||
|
padding: 20rpx;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-radius: 10rpx;
|
border-radius: 10rpx;
|
||||||
|
min-height: 120rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-icon {
|
.contact-icon {
|
||||||
font-size: 32rpx;
|
font-size: 36rpx;
|
||||||
margin-right: 15rpx;
|
margin-bottom: 10rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-info {
|
.contact-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-name {
|
.contact-name {
|
||||||
font-size: 26rpx;
|
font-size: 24rpx;
|
||||||
color: #333;
|
color: #666;
|
||||||
margin-bottom: 5rpx;
|
margin-bottom: 5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-phone {
|
.contact-phone {
|
||||||
font-size: 24rpx;
|
font-size: 26rpx;
|
||||||
color: #007aff;
|
color: #007aff;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-actions {
|
.bottom-actions {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="order-actions">
|
<view class="order-actions">
|
||||||
<button class="action-btn primary" @click="viewOrderDetail(order.id, order.status)">查看详情</button>
|
<button class="action-btn primary" @click="viewOrderDetail(order)">查看详情</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -93,11 +93,15 @@ export default {
|
|||||||
checkForNewCompletedOrder() {
|
checkForNewCompletedOrder() {
|
||||||
const completedOrderFromStorage = uni.getStorageSync('completed_order_for_history')
|
const completedOrderFromStorage = uni.getStorageSync('completed_order_for_history')
|
||||||
if (completedOrderFromStorage) {
|
if (completedOrderFromStorage) {
|
||||||
// 如果有,将其添加到订单列表的开头
|
// 仅在本地存储条目标记为已完成(status >= 4)时才合并到历史
|
||||||
// 检查是否已经存在于列表中,避免重复添加
|
const storedStatus = Number(completedOrderFromStorage && (completedOrderFromStorage.status ?? 0))
|
||||||
const exists = this.orderList.some(order => order.id === completedOrderFromStorage.id)
|
if (storedStatus >= 4) {
|
||||||
if (!exists) {
|
const exists = this.orderList.some(order => order.id === completedOrderFromStorage.id)
|
||||||
this.orderList.unshift(completedOrderFromStorage)
|
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')
|
uni.removeStorageSync('completed_order_for_history')
|
||||||
@@ -107,122 +111,114 @@ export default {
|
|||||||
// 加载历史订单(从数据库读取)
|
// 加载历史订单(从数据库读取)
|
||||||
async loadOrderHistory() {
|
async loadOrderHistory() {
|
||||||
try {
|
try {
|
||||||
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
|
await supaReady
|
||||||
if (!ready) console.warn('supaReady timeout/failed in loadOrderHistory - proceeding')
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('supaReady failed', e)
|
console.warn('supaReady failed', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure profile is loaded so getCurrentUserId returns ak_users.id when available
|
|
||||||
try {
|
|
||||||
await getCurrentUser()
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('getCurrentUser failed in loadOrderHistory', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const uid = getCurrentUserId()
|
const uid = getCurrentUserId()
|
||||||
console.log('loadOrderHistory: currentUserId=', uid)
|
console.log('loadOrderHistory: currentUserId=', uid)
|
||||||
|
|
||||||
// 首先查 ml_delivery_tasks 中分配给当前用户的任务(仅包含已接/分配的 status >= 2)
|
// 首先尝试解析出 ml_delivery_drivers 的 id(driver_id),避免直接使用 auth user id 导致不匹配
|
||||||
let dtRes: any = { data: [] }
|
let driverId: string | null = null
|
||||||
try {
|
try {
|
||||||
let queryUid = uid
|
if (uid && uid !== '') {
|
||||||
// 如果 uid 为空,尝试从 supa session 获取 auth id 并映射到 ak_users.id
|
// 尝试直接按 user_id 查找 driver
|
||||||
if (!queryUid || queryUid === '') {
|
const drvRes: any = await supa.from('ml_delivery_drivers').select('id').eq('user_id', uid).limit(1).execute()
|
||||||
try {
|
if (drvRes && Array.isArray(drvRes.data) && drvRes.data.length > 0) {
|
||||||
const sess = supa.getSession && supa.getSession()
|
driverId = drvRes.data[0].id
|
||||||
const sessId = sess && sess.user && sess.user.getString && sess.user.getString('id')
|
} else {
|
||||||
console.log('loadOrderHistory: session id fallback=', sessId)
|
// 回退:尝试 ak_users 表根据 auth_id 查出 ak_users.id,再查 ml_delivery_drivers
|
||||||
if (sessId) {
|
const akRes: any = await supa.from('ak_users').select('id').eq('auth_id', uid).limit(1).execute()
|
||||||
const akRes = await supa.from('ak_users').select('id').eq('auth_id', sessId).limit(1).execute()
|
if (akRes && Array.isArray(akRes.data) && akRes.data.length > 0) {
|
||||||
if (akRes && Array.isArray(akRes.data) && akRes.data.length > 0) {
|
const akId = akRes.data[0].id
|
||||||
queryUid = (akRes.data[0] as any).id
|
const drvRes2: any = await supa.from('ml_delivery_drivers').select('id').eq('user_id', akId).limit(1).execute()
|
||||||
console.log('loadOrderHistory: mapped ak_users.id=', queryUid)
|
if (drvRes2 && Array.isArray(drvRes2.data) && drvRes2.data.length > 0) {
|
||||||
|
driverId = drvRes2.data[0].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (mapErr) {
|
|
||||||
console.warn('loadOrderHistory: ak_users mapping failed', mapErr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('loadOrderHistory: driver lookup failed', err)
|
||||||
|
}
|
||||||
|
|
||||||
if (queryUid && queryUid !== '') {
|
// 直接以 ml_delivery_tasks 作为数据源(配送端真源)
|
||||||
dtRes = await supa.from('ml_delivery_tasks')
|
let tasksRes: any = { data: [] }
|
||||||
.select('order_id,status')
|
try {
|
||||||
.eq('driver_id', queryUid)
|
if (driverId) {
|
||||||
|
tasksRes = await supa.from('ml_delivery_tasks')
|
||||||
|
.select('*')
|
||||||
|
.eq('driver_id', driverId)
|
||||||
.gte('status', 2)
|
.gte('status', 2)
|
||||||
.order('created_at', { ascending: false })
|
.order('created_at', { ascending: false })
|
||||||
.limit(200)
|
.limit(200)
|
||||||
.execute()
|
.execute()
|
||||||
} else {
|
} else {
|
||||||
dtRes = { data: [] }
|
tasksRes = { data: [] }
|
||||||
}
|
}
|
||||||
console.log('loadOrderHistory: delivery_tasks dtRes=', dtRes)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('loadOrderHistory: delivery_tasks query failed', err)
|
console.error('loadOrderHistory: ml_delivery_tasks query failed', err)
|
||||||
dtRes = { data: [] }
|
tasksRes = { data: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderIds = (dtRes && Array.isArray(dtRes.data)) ? dtRes.data.map((r: any) => r.order_id) : []
|
console.log('loadOrderHistory: tasksRes=', tasksRes)
|
||||||
|
|
||||||
// 如果没有通过 delivery_tasks 找到订单,改为直接读取最近完成/已取货订单(兼容测试环境)
|
// 如果任务包含 order_id,则从 ml_orders 查询 order_no 用于显示
|
||||||
let ordersRes
|
const orderIdsFromTasks = (tasksRes && Array.isArray(tasksRes.data)) ? tasksRes.data.map((r: any) => r.order_id).filter(Boolean) : []
|
||||||
|
let ordersRes: any = { data: [] }
|
||||||
try {
|
try {
|
||||||
if (orderIds.length > 0) {
|
if (orderIdsFromTasks.length > 0) {
|
||||||
ordersRes = await supa.from('ml_orders').select('*').in('id', orderIds).order('created_at', { ascending: false }).limit(200).execute()
|
ordersRes = await supa.from('ml_orders').select('id,order_no').in('id', orderIdsFromTasks).execute()
|
||||||
} else {
|
|
||||||
ordersRes = await supa.from('ml_orders').select('*').in('order_status', [4,5]).order('created_at', { ascending: false }).limit(200).execute()
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('loadOrderHistory: ml_orders query failed', err)
|
console.warn('loadOrderHistory: ml_orders lookup failed', err)
|
||||||
ordersRes = { data: [] }
|
ordersRes = { data: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('loadOrderHistory: ordersRes=', ordersRes)
|
console.log('loadOrderHistory: ordersRes=', ordersRes)
|
||||||
|
|
||||||
const mapOrder = (r: any) => ({
|
const orderNoMap: Record<string,string> = {}
|
||||||
id: r.id,
|
if (ordersRes && Array.isArray(ordersRes.data)) {
|
||||||
order_no: r.order_no || String(r.cid || ''),
|
ordersRes.data.forEach((o: any) => { if (o && o.id) orderNoMap[o.id] = o.order_no })
|
||||||
status: r.order_status ?? r.status ?? 0,
|
}
|
||||||
pickup_address: r.pickup_address || r.shipping_address || { detail: '', area: '' },
|
|
||||||
delivery_address: r.delivery_address || r.shipping_address || { detail: '', area: '' },
|
const parseAddress = (a: any) => {
|
||||||
pickup_contact: r.pickup_contact || r.shipping_contact || { name: '', phone: '' },
|
if (!a) return { detail: '', area: '' }
|
||||||
delivery_contact: r.delivery_contact || r.shipping_contact || { name: '', phone: '' },
|
let obj = a
|
||||||
delivery_fee: r.delivery_fee || r.delivery_fees || 0,
|
if (typeof a === 'string') {
|
||||||
distance: r.distance || 0,
|
try { obj = JSON.parse(a) } catch (e) { obj = { detail: a } }
|
||||||
estimated_time: r.estimated_time || 0,
|
}
|
||||||
created_at: r.created_at
|
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 = (ordersRes && ordersRes.data) ? ordersRes.data.map(mapOrder) : []
|
this.orderList = (tasksRes && Array.isArray(tasksRes.data)) ? tasksRes.data.map(mapTaskToOrder) : []
|
||||||
|
|
||||||
// 额外:把当前分配给本司机但尚未完成的任务对应订单也展示在列表顶部(便于查看当前任务)
|
|
||||||
try {
|
|
||||||
if (uid && uid !== '') {
|
|
||||||
// 仅获取已经被接取/分配给本司机的任务(status >= 2),包括进行中和已取货但未完成的
|
|
||||||
const taskRes: any = await supa.from('ml_delivery_tasks')
|
|
||||||
.select('order_id,status')
|
|
||||||
.eq('driver_id', uid)
|
|
||||||
.gte('status', 2)
|
|
||||||
.order('created_at', { ascending: false })
|
|
||||||
.limit(50)
|
|
||||||
.execute()
|
|
||||||
if (taskRes && Array.isArray(taskRes.data) && taskRes.data.length > 0) {
|
|
||||||
const taskOrderIds = taskRes.data.map((t: any) => t.order_id).filter((id: any) => id)
|
|
||||||
if (taskOrderIds.length > 0) {
|
|
||||||
const curOrdersRes: any = await supa.from('ml_orders').select('*').in('id', taskOrderIds).execute()
|
|
||||||
const curMapped = (curOrdersRes && Array.isArray(curOrdersRes.data)) ? curOrdersRes.data.map(mapOrder) : []
|
|
||||||
// 把当前任务对应订单插入到列表最前面(避免重复)
|
|
||||||
curMapped.forEach((o: any) => {
|
|
||||||
if (!this.orderList.some((ex: any) => ex.id === o.id)) {
|
|
||||||
this.orderList.unshift(o)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('loadOrderHistory: fetch current tasks failed', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有新完成的订单(在加载初始数据后)
|
// 检查是否有新完成的订单(在加载初始数据后)
|
||||||
this.checkForNewCompletedOrder()
|
this.checkForNewCompletedOrder()
|
||||||
@@ -253,9 +249,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 查看订单详情
|
// 查看订单详情
|
||||||
viewOrderDetail(orderId: string, status: number) {
|
viewOrderDetail(order: any) {
|
||||||
|
// 优先传递 ml_orders.id,但如果订单表对应行确实缺失,详情页逻辑现在支持用 Task ID 回退显示
|
||||||
|
const targetId = order.order_id || order.id
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=${status}`
|
url: `/pages/mall/delivery/order-detail?id=${targetId}&status=${order.status}`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ function backToIndex() {
|
|||||||
uni.navigateBack({ url: '/pages/mall/delivery/index' })
|
uni.navigateBack({ url: '/pages/mall/delivery/index' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 兼容模板中的 goBack 绑定,保持与其它页面一致 */
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------- 数据 & 状态 ----------------- */
|
/* ----------------- 数据 & 状态 ----------------- */
|
||||||
const driverInfo = ref({ id: '', real_name: '配送员', avatar_url: '', rating: 4.9, total_orders: 0, work_status: 1 })
|
const driverInfo = ref({ id: '', real_name: '配送员', avatar_url: '', rating: 4.9, total_orders: 0, work_status: 1 })
|
||||||
const workStatus = ref(1)
|
const workStatus = ref(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user