修改页面逻辑

This commit is contained in:
not-like-juvenile
2026-02-03 12:01:10 +08:00
parent 8efe6d5e89
commit c803a77c8f
12 changed files with 904 additions and 181 deletions

View File

@@ -268,6 +268,13 @@
"navigationStyle": "custom"
}
},
{
"path": "all",
"style": {
"navigationBarTitleText": "待接取任务",
"navigationStyle": "custom"
}
},
{
"path": "earnings",
"style": {

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

View 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 实现秒级抢单逻辑与状态同步。

View File

@@ -58,6 +58,8 @@ const result = Array.from(orderMap.values())
## 数据源更新包含已接订单Accepted
- 需求说明为了与“历史订单”页面保持一致收入统计应包含配送员已接取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 查询示例(后端或前端按需实现):
```
@@ -78,6 +80,7 @@ LIMIT :size OFFSET :offset;
```js
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)
// 对于 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()
```

View File

@@ -46,6 +46,16 @@ await supa.from('ml_delivery_tasks')
- `toggleWorkStatus()`:切换 `isOnline` 并调用 `startWork()` / `stopWork()`。上线时会刷新可接订单列表。
- 接单/开始取货/确认取货/开始配送/确认送达等均通过对 `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 事务)以防止竞单冲突。
- `loadAvailableOrders()` 最好按司机服务区域与距离筛选,并使用分页/实时推送代替频繁轮询。

View File

@@ -22,30 +22,26 @@
- 解析 `options.id``options.status`,调用 `loadOrderDetail(id)`
- `loadOrderDetail(id)`
- 判断 ID 类型UUID / 数字 / 非数字)以决定查询字段(`id``cid``order_no`
- 并行查询 `ml_orders`, `ml_order_items`, `ml_shops`, `ml_delivery_tasks` 并合并到页面状态
- **加载保护**:对 `supaReady` 采用 1.5s 超时策略包装,防止会话刷新阻塞页面加载
- **智能 ID 回查**:优先从 `ml_orders` (UUID/cid/order_no) 查找。若未找到,则尝试从未分配任务表 `ml_delivery_tasks` 中根据 ID 查找,再反查关联订单
- **降级机制Fallback**:当 `ml_orders` 行缺失时,自动回退到从 `ml_delivery_tasks` 提取地址、手机号、配送费及距离等基础信息进行展示,并在 UI 上显示回退加载提示。
- **清理加载状态**:在 `finally` 块中统一切除加载动画 (`uni.hideLoading`),防止界面挂起。
- `acceptOrder()` / `rejectOrder(reason)`
- accept: 尝试对 `ml_delivery_tasks` 执行 `update driver_id` 操作并设置 `status=2`(处理中),需要后端并发保护
- reject: 增加拒单原因到 `ml_delivery_tasks``order_notes`回滚本地 UI 状态
## 交互与样式优化2026-02-03 更新)
- **联系人信息解析**:地址栏(取货/送货)现在仅在“联系人姓名”存在时显示分隔点 `·`。若无姓名,则仅显示手机号,避免显示为 `. 手机号`
- **联系方式布局优化**:为了防止手机号在不同屏幕宽度下被图标遮挡,联系人区域采用**垂直居中布局**(图标在上,姓名电话在下),显著拉高了边框高度 (`min-height: 180rpx`)增加了垂直间距
- **文本显示优化**
- 商品区域的“**订单号**”增加了加粗显示 (`bold`),提升核对便利性。
- 展示给配送员的联系手机号调大了字号并加粗,确保清晰可见。
- 修复了在 Uni-app x 下由于类型推断导致的手机号无法通过点语法访问的问题(改用索引访问)。
- `confirmPickup()` / `confirmDelivery()`
- 根据 `task.id` 更新相应时间戳字段(`picked_at`/`delivered_at`)并设置状态(例如 `status=3/4`)。
## 示例:按 id 类型查询(伪代码)
```
let q = supa.from('ml_orders').select('*')
if (isUUID(id)) q = q.eq('id', id)
else if (isNumeric(id)) q = q.eq('cid', id)
else q = q.eq('order_no', id)
const { data: order } = await q.limit(1).execute()
## 示例:地址兼容解析逻辑
```typescript
// 兼容 JSON 字符串及对象格式的地址字段
let shipping = {}
if (typeof raw == 'string') {
try { shipping = JSON.parse(raw) } catch (e) { shipping = { detail: raw } }
} else { shipping = raw || {} }
// 访问方式:(address as UTSJSONObject)['phone']
```
## 事务与并发注意
- 接单场景应使用后端原子性检查(数据库事务或行级乐观锁)以避免多司机同时接单。
- 前端接单流程:先尝试 update带 where driver_id IS NULL若返回 0 row affected 则提示已被接单。
## 错误处理与回退
- 捕获所有 supa 调用错误并将友好错误展示给用户(例如:'网络错误,请稍后重试')。
- 对可能缺失的字段(地址为字符串或对象)使用 `_transformAddress()` 做兼容处理。

View File

@@ -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添加回退 mappingsession -> ak_users.id、supaReady 超时保护的说明、调试日志建议及性能建议。
---
如需我把文档翻译为英文或生成 README 风格的一页说明,我可以继续补充。
# order-history.uvue — 历史订单
## 概要

View File

@@ -97,8 +97,6 @@
<text class="section-title">附近订单</text>
<view class="section-header-actions">
<text class="refresh-btn" @click="refreshOrders">🔄 刷新</text>
<!-- 当可接取订单达到上限时显示更多入口 -->
<text v-if="availableOrders && availableOrders.length >= 20" class="more-btn" @click="goToAllOrders">更多 ➜</text>
</view>
</view>
@@ -107,34 +105,41 @@
<text class="empty-subtitle">请保持在线状态,有新订单会自动推送</text>
</view>
<view v-for="order in availableOrders" :key="order.id" class="order-card">
<view class="order-header">
<text class="order-id">{{ order.order_no }}</text>
<text class="order-fee">¥{{ order.delivery_fee }}</text>
</view>
<view class="order-route">
<view class="route-item">
<text class="route-icon">📍</text>
<text class="route-text">{{ order.pickup_address.area }}</text>
<view v-for="(order, index) in availableOrders" :key="order.id">
<view v-if="index < 5" class="order-card">
<view class="order-header">
<text class="order-id">{{ order.order_no }}</text>
<text class="order-fee">¥{{ order.delivery_fee }}</text>
</view>
<text class="route-arrow">→</text>
<view class="route-item">
<text class="route-icon">🏠</text>
<text class="route-text">{{ order.delivery_address.area }}</text>
<view class="order-route">
<view class="route-item">
<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 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>
<!-- 超过5个展示订单就有个加载更多 -->
<view v-if="availableOrders.length > 5" class="view-all-footer" @click="goToAllOrders">
<text class="view-all-text">查看全部订单 (共 {{ availableOrders.length }} 个待接订单) ➜</text>
</view>
</view>
@@ -308,7 +313,10 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
}
}
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) {
console.error('loadDriverInfo error', e)
@@ -434,6 +442,7 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
return {
id: task.id,
order_id: task.order_id || task.orderId || task.orderId || '',
order_no: task.order_no || task.orderNo || task.trade_no || '',
status: Number(task.status) || 1,
pickup_address: parseAddress(task.pickup_address),
@@ -465,8 +474,34 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
// 切换工作状态
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) {
this.startWork()
} else {
@@ -475,8 +510,15 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
},
// 开始工作
startWork() {
// TODO: 调用API开始工作上传位置
async startWork() {
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()
uni.showToast({
title: '已上线接单',
@@ -485,8 +527,15 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
},
// 停止工作
stopWork() {
// TODO: 调用API停止工作
async stopWork() {
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 = []
uni.showToast({
title: '已下线休息',
@@ -631,6 +680,29 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
if (res && !res.error) {
const completedOrder = { ...this.currentTask }
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' })
this.currentTask = null
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()
if (res && !res.error) {
uni.showToast({ title: '订单已接受', icon: 'success' })
// 同步更新 ml_orders 状态为已接取2
try {
// orderId 这里是 ml_delivery_tasks.idtask 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.loadAvailableOrders()
}
@@ -1174,6 +1261,25 @@ import { getCurrentUserId, getCurrentUser } from '@/utils/store.uts'
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 {
background-color: #fff;

View File

@@ -1,5 +1,6 @@
<template>
<view class="delivery-order-detail">
<view class="page-content">
<!-- 返回按钮 -->
<view class="back-header">
<view class="back-box" @click="goBack">
@@ -47,7 +48,7 @@
<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-address">{{ merchant.contact_name ? (merchant.contact_name + ' · ') : '' }}{{ merchant.contact_phone }}</text>
<text class="route-detail">{{ pickupAddress }}</text>
</view>
<!-- 只在取货中状态且订单未完成时显示按钮 -->
@@ -60,7 +61,7 @@
<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-address">{{ getDeliveryAddress().name ? (getDeliveryAddress().name + ' · ') : '' }}{{ (order.delivery_address as UTSJSONObject)['phone'] || '' }}</text>
<text class="route-detail">{{ getDeliveryAddress().detail }}</text>
</view>
<!-- 只在已取货状态且订单未完成时显示按钮 -->
@@ -143,7 +144,7 @@
<view class="contact-icon">📞</view>
<view class="contact-info">
<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 class="contact-item" @click="callMerchant">
@@ -156,6 +157,8 @@
</view>
</view>
</view>
<!-- 底部操作 -->
<view class="bottom-actions">
<!-- 只在待接单状态且订单未完成时显示接受/拒绝订单按钮 -->
@@ -249,30 +252,78 @@ export default {
async loadOrderDetail(orderId: string) {
const originalStatus = this.order.status
try {
await supaReady
console.log('loadOrderDetail called', { orderId })
// 使用 1.5s 超时策略包装 supaReady防止 session 刷新卡死页面
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 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 isNumber = /^\d+$/.test(orderId)
console.log('loadOrderDetail: supa session=', supa.getSession && supa.getSession())
console.log('loadOrderDetail: detect id format', { isUuid, isNumber })
// 步骤 A: 查 ml_orders
if (isUuid) {
console.log('loadOrderDetail: querying by id')
orderRes = await supa.from('ml_orders').select('*').eq('id', orderId).limit(1).execute()
} else if (isNumber) {
console.log('loadOrderDetail: querying by cid')
orderRes = await supa.from('ml_orders').select('*').eq('cid', Number(orderId)).limit(1).execute()
} else {
console.log('loadOrderDetail: querying by order_no')
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) {
const row = orderRes.data[0]
const shipping = row.shipping_address || {}
console.log('loadOrderDetail: discovered order details', row)
// 如果没有预先获取 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, {
id: row.id,
@@ -287,9 +338,9 @@ export default {
payment_method: row.payment_method || 0,
payment_status: row.payment_status || 0,
delivery_address: {
name: shipping.name || shipping.recipient || '',
phone: shipping.phone || shipping.mobile || '',
detail: shipping.detail || shipping.address || JSON.stringify(shipping)
name: shipping['name'] || shipping['recipient'] || '',
phone: shipping['phone'] || shipping['mobile'] || '',
detail: shipping['detail'] || shipping['address'] || JSON.stringify(shipping)
},
created_at: row.created_at || ''
})
@@ -341,19 +392,63 @@ export default {
}
// deliveryInfo 从 ml_delivery_tasks 中读取(如果存在)
const dtRes: any = await supa.from('ml_delivery_tasks').select('*').eq('order_id', realOrderId).limit(1).execute()
console.log('loadOrderDetail: dtRes=', dtRes)
if (dtRes && Array.isArray(dtRes.data) && dtRes.data.length > 0) {
const dt = dtRes.data[0]
this.deliveryInfo.distance = Number(dt.distance) || this.deliveryInfo.distance
this.deliveryInfo.estimated_time = Number(dt.estimated_time) || this.deliveryInfo.estimated_time
this.deliveryInfo.courier_id = dt.driver_id || ''
this.deliveryInfo.pickup_time = dt.pickup_time || ''
this.deliveryInfo.delivery_time = dt.delivered_time || ''
if (taskData) {
this.deliveryInfo.distance = Number(taskData.distance) || this.deliveryInfo.distance
this.deliveryInfo.estimated_time = Number(taskData.estimated_time) || this.deliveryInfo.estimated_time
this.deliveryInfo.courier_id = taskData.driver_id || ''
this.deliveryInfo.pickup_time = taskData.pickup_time || ''
this.deliveryInfo.delivery_time = taskData.delivered_time || taskData.delivered_at || ''
}
} 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) {
console.error('loadOrderDetail db error', e)
uni.showToast({ title: '加载订单失败', icon: 'none' })
} finally {
uni.hideLoading()
}
},
@@ -530,7 +625,15 @@ export default {
.delivery-order-detail {
background-color: #f5f5f5;
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 {
font-size: 24rpx;
color: #666;
color: #333;
font-weight: bold;
}
.product-item {
@@ -865,30 +969,38 @@ export default {
.contact-item {
flex: 1;
display: flex;
flex-direction: column; /* 改为垂直排列避免空间不足 */
align-items: center;
padding: 25rpx;
justify-content: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 10rpx;
min-height: 120rpx;
}
.contact-icon {
font-size: 32rpx;
margin-right: 15rpx;
font-size: 36rpx;
margin-bottom: 10rpx;
}
.contact-info {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.contact-name {
font-size: 26rpx;
color: #333;
font-size: 24rpx;
color: #666;
margin-bottom: 5rpx;
}
.contact-phone {
font-size: 24rpx;
font-size: 26rpx;
color: #007aff;
font-weight: bold;
}
.bottom-actions {

View File

@@ -53,7 +53,7 @@
</view>
<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>
@@ -93,11 +93,15 @@ export default {
checkForNewCompletedOrder() {
const completedOrderFromStorage = uni.getStorageSync('completed_order_for_history')
if (completedOrderFromStorage) {
// 如果有,将其添加到订单列表的开头
// 检查是否已经存在于列表中,避免重复添加
const exists = this.orderList.some(order => order.id === completedOrderFromStorage.id)
if (!exists) {
this.orderList.unshift(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')
@@ -107,122 +111,114 @@ export default {
// 加载历史订单(从数据库读取)
async loadOrderHistory() {
try {
const ready = await Promise.race([supaReady, new Promise(resolve => setTimeout(() => resolve(false), 1500))])
if (!ready) console.warn('supaReady timeout/failed in loadOrderHistory - proceeding')
await supaReady
} catch (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()
console.log('loadOrderHistory: currentUserId=', uid)
// 首先 ml_delivery_tasks 中分配给当前用户的任务(仅包含已接/分配的 status >= 2
let dtRes: any = { data: [] }
// 首先尝试解析出 ml_delivery_drivers 的 iddriver_id避免直接使用 auth user id 导致不匹配
let driverId: string | null = null
try {
let queryUid = uid
// 如果 uid 为空,尝试从 supa session 获取 auth id 并映射到 ak_users.id
if (!queryUid || queryUid === '') {
try {
const sess = supa.getSession && supa.getSession()
const sessId = sess && sess.user && sess.user.getString && sess.user.getString('id')
console.log('loadOrderHistory: session id fallback=', sessId)
if (sessId) {
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) {
queryUid = (akRes.data[0] as any).id
console.log('loadOrderHistory: mapped ak_users.id=', queryUid)
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 (mapErr) {
console.warn('loadOrderHistory: ak_users mapping failed', mapErr)
}
}
} catch (err) {
console.error('loadOrderHistory: driver lookup failed', err)
}
if (queryUid && queryUid !== '') {
dtRes = await supa.from('ml_delivery_tasks')
.select('order_id,status')
.eq('driver_id', queryUid)
// 直接以 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 {
dtRes = { data: [] }
tasksRes = { data: [] }
}
console.log('loadOrderHistory: delivery_tasks dtRes=', dtRes)
} catch (err) {
console.error('loadOrderHistory: delivery_tasks query failed', err)
dtRes = { data: [] }
console.error('loadOrderHistory: ml_delivery_tasks query failed', err)
tasksRes = { data: [] }
}
const orderIds = (dtRes && Array.isArray(dtRes.data)) ? dtRes.data.map((r: any) => r.order_id) : []
console.log('loadOrderHistory: tasksRes=', tasksRes)
// 如果没有通过 delivery_tasks 找到订单,改为直接读取最近完成/已取货订单(兼容测试环境)
let ordersRes
// 如果任务包含 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 (orderIds.length > 0) {
ordersRes = await supa.from('ml_orders').select('*').in('id', orderIds).order('created_at', { ascending: false }).limit(200).execute()
} else {
ordersRes = await supa.from('ml_orders').select('*').in('order_status', [4,5]).order('created_at', { ascending: false }).limit(200).execute()
if (orderIdsFromTasks.length > 0) {
ordersRes = await supa.from('ml_orders').select('id,order_no').in('id', orderIdsFromTasks).execute()
}
} catch (err) {
console.error('loadOrderHistory: ml_orders query failed', err)
console.warn('loadOrderHistory: ml_orders lookup failed', err)
ordersRes = { data: [] }
}
console.log('loadOrderHistory: ordersRes=', ordersRes)
const mapOrder = (r: any) => ({
id: r.id,
order_no: r.order_no || String(r.cid || ''),
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: '' },
pickup_contact: r.pickup_contact || r.shipping_contact || { name: '', phone: '' },
delivery_contact: r.delivery_contact || r.shipping_contact || { name: '', phone: '' },
delivery_fee: r.delivery_fee || r.delivery_fees || 0,
distance: r.distance || 0,
estimated_time: r.estimated_time || 0,
created_at: r.created_at
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 = (ordersRes && ordersRes.data) ? ordersRes.data.map(mapOrder) : []
// 额外:把当前分配给本司机但尚未完成的任务对应订单也展示在列表顶部(便于查看当前任务)
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.orderList = (tasksRes && Array.isArray(tasksRes.data)) ? tasksRes.data.map(mapTaskToOrder) : []
// 检查是否有新完成的订单(在加载初始数据后)
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({
url: `/pages/mall/delivery/order-detail?id=${orderId}&status=${status}`
url: `/pages/mall/delivery/order-detail?id=${targetId}&status=${order.status}`
})
},

View File

@@ -209,6 +209,11 @@ function backToIndex() {
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 workStatus = ref(1)