spec.md 32 KB

006 - 订单状态流转设计(美团方案)

背景

系统新增自取订单(type=1)和堂食订单(type=2)后,订单不再需要骑手参与。现有 state 字段将支付状态、订单状态、配送状态、退款状态全部混在一起,导致自取/堂食订单无法直接复用。

旧状态定义(待废弃)

旧 state 含义 类别
0 未支付 支付
1 已付款 支付
2 已接单 订单
3 骑手接单 配送
4 配送中 配送
5 已完成 订单
6 申请退款 退款
7 同意退款 退款
8 拒绝退款 退款
9 客服接入 退款
10 作废 订单
11 售后完成 退款
12 送达 配送
13 退款处理中 退款

选定方案:全面重新设计,四字段分离

参考美团做法,将原来混在 state 中的四种关注点拆成四个独立字段:

  • state:订单生命周期(所有类型共用)
  • deliveryStatus:配送追踪(仅外送订单使用)
  • payStatus:支付状态(所有类型共用)
  • afterSaleStatus:售后/退款状态(独立于订单状态,参考美团做法)

同时废弃 diningStatus 字段,出餐动作由 state 状态变更表示。

新字段定义

state(订单状态)— 重新编号

新 state 含义 谁触发 说明
0 待处理 系统创建 订单已创建,等待商家接单
1 已接单 商家 商家已接单,备餐中
2 已出餐 商家 商家出餐完成
3 已完成 系统 外送:骑手送达;自取:用户确认取餐;堂食:备餐完成
4 已取消 系统/商家 作废/取消(全额退款、商家取消、系统超时取消)

state 只有 5 个值,只跟踪订单的核心生命周期。

deliveryStatus(配送状态)— 仅 type=0 外送订单

deliveryStatus 含义 谁触发
0 待接单 系统(商家出餐后设为0,等待骑手)
1 骑手已接单 骑手
2 配送中 骑手(取餐出发)
3 已送达 骑手

自取(type=1)和堂食(type=2)订单 deliveryStatus 为 NULL。

payStatus(支付状态)— 新增字段

payStatus 含义 说明
0 未支付 订单刚创建,等待付款
1 已支付 在线支付成功,或到付外送订单创建时直接设为1
2 已退款 订单退款完成
  • 外送到付(type=0, payType=1):创建时直接设为 payStatus=1(不需要等在线支付)
  • 自取/堂食现金(type=1或2):创建时 payStatus=0,商家收款并完成订单时同步设为 1
  • 在线支付(vnpay/zalopay/银行卡):创建时 payStatus=0,支付回调成功后改为 1
  • 退款完成:afterSaleStatus=3(已退款)时,同步把 payStatus 改为 2

设计决策:自取/堂食现金订单 payStatus 在下单时为 0,商家收款+完成时才设为 1。原因:商家需要明确知道"现金是否已收到",收钱和完成是同一个动作。外送订单则不同,到付意味着骑手送货时收钱,所以创建时就确认支付方式。

afterSaleStatus(售后状态)— 新增字段,独立于订单状态

参考美团做法:退款申请本身不改变订单状态,只有全额退款成功才将订单设为已取消。

afterSaleStatus 含义 谁触发 说明
0 无售后 - 订单正常,无退款申请
1 申请中 用户 用户发起退款申请,等待商家处理
2 退款中 系统/商家 商家同意退款,退款正在处理
3 已退款 系统 退款完成,全额退款时同步设 state=4
4 退款拒绝 商家 商家拒绝退款,订单继续正常流转
5 客服介入 客服 客服正在处理争议
6 售后完成 系统 售后流程结束

废弃字段

  • diningStatus:不再使用。"出餐"动作由 state 从 1 变为 2 表示。

各类型状态流转

外送-在线支付 (type=0):
  state:          0 → 1 → 2 → 3
  deliveryStatus:          0 → 1 → 2 → 3
  payStatus:      0 → 1(支付回调)
  afterSaleStatus: 0(无售后时始终为0)
  骑手参与: 是(state 和 deliveryStatus 独立推进)
  完成触发: 骑手点"已送达"

外送-到付 (type=0, payType=1):
  state:          0 → 1 → 2 → 3
  deliveryStatus:          0 → 1 → 2 → 3
  payStatus:      1(创建时即确认,骑手送货时收现金)
  afterSaleStatus: 0
  骑手参与: 是
  完成触发: 骑手点"已送达"

自取-现金 (type=1):
  state:     0 → 1 → 2 → 3
  payStatus: 0 ──────────→ 1(商家收款+完成,同一操作)
  afterSaleStatus: 0
  骑手参与: 否(用户到店自取)
  deliveryStatus: 无
  完成触发: 商家点"完成"(收款+出餐)

堂食-现金 (type=2):
  state:     0 → 1 → 2 → 3
  payStatus: 0 ──────────→ 1(商家收款+完成,同一操作)
  afterSaleStatus: 0
  骑手参与: 否
  deliveryStatus: 无
  完成触发: 商家点"完成"(收款+出餐)

售后/退款流转(所有类型,独立状态机)

afterSaleStatus: 0(无售后) → 1(申请中) → 2(退款中) → 3(已退款)
                                         → 4(退款拒绝) → 5(客服介入) → 6(售后完成)

售后与订单状态的联动规则(参考美团)

售后动作 afterSaleStatus state payStatus deliveryStatus 骑手行为
用户申请退款 → 1(申请中) 不变 不变 不变 继续配送
商家同意退款(全额) → 2(退款中) → 4(已取消) → 2(已退款) 废弃 停止配送
商家同意退款(部分) → 3(已退款) 不变 视金额调整 不变 继续配送
商家拒绝退款 → 4(退款拒绝) 不变 不变 不变 继续配送
客服介入 → 5(客服介入) 不变 不变 不变 视客服裁决
用户撤销退款 → 0(无售后) 不变 不变 不变 继续配送

核心原则:退款申请本身不影响订单状态和配送,只有全额退款成功才取消订单。

state=4(已取消)的触发场景

场景 state afterSaleStatus 说明
商家主动取消 → 4 0 未付款或刚接单时商家取消
系统超时自动取消 → 4 0 商家超时未接单
全额退款成功 → 4 3(已退款) 退款完成后订单取消
用户未支付取消 → 4 0 订单超时未支付

旧值 → 新值 数据迁移映射

旧 state 旧含义 新 state 新 payStatus 新 deliveryStatus 新 afterSaleStatus
0 未支付 0 0 NULL 0
1 已付款 0 1 NULL 0
2 已接单 1 1 NULL 0
3 骑手接单 1 1 1 0
4 配送中 1 1 2 0
5 已完成 3 1 3(外送) 0
6 申请退款 1 1 NULL 1
7 同意退款 4 2 NULL 3
8 拒绝退款 1 1 NULL 4
9 客服接入 1 1 NULL 5
10 作废 4 0 NULL 0
11 售后完成 4 2 NULL 6
12 送达 2 1 3 0
13 退款处理中 1 1 NULL 2

注:旧退款状态(6,8,9)迁移时新 state 推算为 1(已接单),因为退款前订单大概率在已接单阶段。历史数据够用即可。

前端展示逻辑

商家端订单列表 Tab

商家端 Tab 筛选条件
待受理 state=0 AND (payStatus=1 OR type IN (1,2))
待出餐 state=1
已出餐 state=2 + afterSaleStatus=0
已完成 state=3 + afterSaleStatus=0
已取消 state=4 + afterSaleStatus=0
退款/售后 afterSaleStatus > 0

设计决策:待受理条件区分了外送和自取/堂食。外送订单必须 payStatus=1(已付款/到付确认)才出现在待受理;自取/堂食订单下单即可见(因为现金是线下收取的,不需要等在线支付)。

用户端订单列表 Tab

用户端 Tab 筛选条件
待付款 payStatus=0 AND type=0(仅外送在线支付订单)
进行中 state IN (0,1,2) AND afterSaleStatus=0 AND (payStatus=1 OR type IN (1,2))
已完成 state=3 + afterSaleStatus=0
已取消 state=4 + afterSaleStatus=0
退款/售后 afterSaleStatus > 0

设计决策:待付款仅显示外送未支付订单。自取/堂食现金订单的 payStatus=0 但不显示在"待付款"(用户无需在线付款),直接进入"进行中"Tab。用户端 Tab 名从"待收货"改为"进行中",因为自取/堂食不存在"收货"概念。

骑手端订单列表 Tab

骑手端 Tab 筛选条件
新任务 type=0 + deliveryStatus=0 + state=2 + afterSaleStatus=0
待取货 deliveryStatus=1 + qsId=当前骑手 + afterSaleStatus=0
配送中 deliveryStatus=2 + qsId=当前骑手 + afterSaleStatus=0
已完成 state=3 + afterSaleStatus=0 + qsId=当前骑手
已取消 state=4 + qsId=当前骑手 + afterSaleStatus=0
退款/售后 afterSaleStatus > 0 + qsId=当前骑手

前端展示给用户的文字(对齐美团精简方案)

用户只关心关键节点,多个内部状态合并为一个展示文字。前端根据字段组合判断,afterSaleStatus 优先。

外送 (type=0) 用户看到的文字

原来的文字 美团方案 对应字段条件
待付款 待支付 payStatus=0
待商家确认 + 商家备餐中 商家已接单 state=0/1, payStatus=1, afterSaleStatus=0
待骑手配送 + 骑手已接单 + 配送中 配送中 state=2, deliveryStatus=0/1/2
已送达 已送达 state=2, deliveryStatus=3
已完成 已完成 state=3
已取消 已取消 state=4, afterSaleStatus=0
退款申请中/退款中/已退款 退款中/已退款 afterSaleStatus=1/2/3

自取 (type=1) 用户看到的文字

美团方案 条件
待支付 payStatus=0
商家已接单 state=0/1
待取餐 state=2
已完成 state=3
已取消 state=4

堂食 (type=2) 用户看到的文字

美团方案 条件
待支付 payStatus=0
商家已接单 state=0/1
备餐完成 state=2
已完成 state=3
已取消 state=4

需要修改的代码

1. 数据库变更

-- 新增 delivery_status 字段
ALTER TABLE pos_order ADD COLUMN delivery_status BIGINT DEFAULT NULL COMMENT '配送状态:0待接单,1骑手已接单,2配送中,3已送达';

-- 新增 pay_status 字段
ALTER TABLE pos_order ADD COLUMN pay_status BIGINT DEFAULT 0 COMMENT '支付状态:0未支付,1已支付,2已退款';

-- 新增 after_sale_status 字段
ALTER TABLE pos_order ADD COLUMN after_sale_status BIGINT DEFAULT 0 COMMENT '售后状态:0无售后,1申请中,2退款中,3已退款,4退款拒绝,5客服介入,6售后完成';

-- 废弃 dining_status 字段
-- ALTER TABLE pos_order DROP COLUMN dining_status;  -- 视情况是否删除

2. 数据迁移

不需要数据迁移。系统未正式使用,历史订单数据由开发者手动清空。新字段(delivery_status, pay_status, after_sale_status)通过 ALTER TABLE 添加后,所有新订单直接使用新值。

注意:如果将来需要迁移历史数据,不能用逐步 UPDATE 的方式(会有值冲突 bug),必须用单条 CASE WHEN 语句原子更新。

3. PosOrder 实体类

  • 新增字段:deliveryStatus(Long)、payStatus(Long)、afterSaleStatus(Long)
  • 废弃但保留的字段:diningStatus、isAccepted、kefuState、kefuContent、kefuRepeat、repeatDdId — 新逻辑不再使用,保留在实体中避免序列化问题
    • diningStatus 由 state 值变更替代(state=2 即出餐)
    • isAccepted 由 state=1(已接单)替代
    • kefuState 等由 afterSaleStatus 替代(客服介入 = afterSaleStatus=5)

4. MyBatis Mapper(PosOrderMapper.xml)

  • resultMap 新增 delivery_status、pay_status、after_sale_status 映射
  • insert/update 新增对应字段

5. PosOrderShOprateController(商家操作)

  • 接单:state 从 0 改为 1
  • 出餐:state 从 1 改为 2
    • 外送订单:额外设置 deliveryStatus=0(等待骑手接单)
    • 自取/堂食订单:只改 state=2
  • 完成(自取/堂食):state 从 2 改为 3,同时 payStatus 从 0 改为 1(收现金+完成,一个操作)
    • 仅限 type=1(自取)或 type=2(堂食)订单

6. PosOrderQsOprateController(骑手操作)

  • acceptOrder:校验 type=0 且 afterSaleStatus=0,设置 deliveryStatus=1
  • pickupOrder:校验 type=0 且 afterSaleStatus=0,设置 deliveryStatus=2
  • deliverOrder:校验 type=0,设置 deliveryStatus=3,同时设置 state=3
  • 所有骑手操作需检查 afterSaleStatus,有活跃退款申请时允许操作但需提示

7. 订单完成机制

参考美团做法:商家/骑手操作是主触发器,用户确认是可选的,自动完成是兜底。

类型 完成触发者 操作 自动完成兜底
外送 骑手 骑手点"已送达" → state=3, deliveryStatus=3 送达后 N 小时自动完成
自取 商家 商家点"完成" → state=3, payStatus=1 出餐后 N 小时自动完成
堂食 商家 商家点"完成" → state=3, payStatus=1 出餐后 N 小时自动完成
  • 用户确认取餐:可选的"确认取餐"按钮,仅限自取订单(type=1)且已支付(payStatus=1)的用户可操作,用户点了设 state=3。现金自取订单(payStatus=0)的用户无法自己确认取餐,只能等商家通过"完成"操作同时设置 state=3 和 payStatus=1。这样避免出现 state=3(已完成) 但 payStatus=0(未支付) 的矛盾状态。
  • 定时任务兜底:商家出餐(state=2)后超时未完成,系统自动设为 state=3

8. 订单创建(UserOrderController)

  • 创建订单时设置 state=0、afterSaleStatus=0、deliveryStatus=NULL(所有类型),根据类型和支付方式设置 payStatus
    • 外送到付(type=0, payType=1):payStatus=1
    • 自取/堂食现金(type=1或2):payStatus=0(商家收款时设为1)
    • 在线支付:payStatus=0

9. 支付回调

  • 支付成功后:payStatus 从 0 改为 1

10. 售后/退款操作(OrderAppealController 等)

本次不实现完整退款流程。系统尚未接入在线支付,暂无退款场景。afterSaleStatus 字段预留,但只实现以下两个操作:

  • 用户确认取餐(自取已支付订单):state → 3,仅限 type=1 且 payStatus=1 且 state=2 的订单
  • 用户取消订单(未接单前):state → 4,afterSaleStatus 保持 0
  • 商家取消订单:state → 4,afterSaleStatus 保持 0

以下完整退款流程留待接入在线支付后实现:

  • 用户申请退款:afterSaleStatus → 1,state 不变
  • 商家同意退款(全额):afterSaleStatus → 2 → 3,state → 4,payStatus → 2
  • 商家同意退款(部分):afterSaleStatus → 3,state 不变
  • 商家拒绝退款:afterSaleStatus → 4,state 不变
  • 客服介入:afterSaleStatus → 5
  • 售后完成:afterSaleStatus → 6
  • 用户撤销退款:afterSaleStatus → 0

11. 旧订单列表查询方法(废弃)

以下旧方法在数据迁移后自然废弃,不再修改。新方法见第 15 节。

  • PosOrderController.getstoreorderlist() — 商家端订单列表(废弃,由 PosOrderShOprateController.orderList 替代)
  • PosOrderController.getorderlist() — 用户端订单列表(废弃,由 UserOrderController.orderList 替代)
  • PosOrderController.getqsorderlist() — 骑手端订单列表(废弃,由 PosOrderQsOprateController.orderList 替代)
  • PosOrderController.getqishouorderlist() — 骑手抢单列表(废弃,由 PosOrderQsOprateController.orderList 的 newTask Tab 替代)
  • ShindexController — 商家首页(废弃)
  • TableQrcodeController — 堂食二维码订单(废弃)
  • PosOrderMapper — 旧 SQL 中的 state 条件不再维护

12. 定时任务(TestTask.java)

  • 旧自动完成逻辑(state=12→5)不再需要:骑手"已送达"操作直接设 state=3 + deliveryStatus=3,不存在需要从"送达"自动转为"已完成"的中间状态
  • 新增自动完成兜底:查 state=2 AND afterSaleStatus=0 且超时未完成,自动设 state=3(外送同时设 deliveryStatus=3,自取/堂食同时设 payStatus=1)
  • 退款处理逻辑:原来查 state=13 改为查 afterSaleStatus=2(本次不实现)
  • 作废逻辑:原来设 state=10 改为设 state=4

13. 骑手约束逻辑

  • 防止骑手同时接多单:原来查 state IN (3,4) 改为查 deliveryStatus IN (1,2)
  • RiderPositionMapper 排除有活跃订单的骑手:条件同步更新
  • 有退款申请的订单(afterSaleStatus>0)仍允许骑手操作,直到全额退款成功

14. 推送通知

  • 推送消息中的 state 值需对应新值
  • 根据 type + state + deliveryStatus + afterSaleStatus 组合发送不同内容
  • 退款相关推送应基于 afterSaleStatus 而非 state

15. 新订单列表接口设计

设计原则

  • 旧方法不动:PosOrderController 中旧的 getstoreorderlist/getorderlist/getqsorderlist/getqishouorderlist 等方法保留代码不删,数据迁移后自然废弃
  • 新方法写在三个 Controller 中:用户端写 UserOrderController,骑手端写 PosOrderQsOprateController,商家端写 PosOrderShOprateController
  • 使用 LambdaQueryWrapper:三个端统一用 LambdaQueryWrapper 拼查询条件
  • Tab 参数:前端传 tab 字符串参数(如 pending、active、completed),后端映射到四字段查询条件
  • 状态展示文字由前端计算:后端只返回 state/deliveryStatus/payStatus/afterSaleStatus/type 字段,前端根据这些字段组合自行决定显示文案
  • 分页返回 total:新接口统一返回 { records, total, page, size } 格式

前置条件

以下变更必须在列表接口开发前完成:

  1. PosOrder 实体添加 deliveryStatus、payStatus、afterSaleStatus 字段
  2. PosOrderMapper.xml resultMap 添加三个新字段映射,insert/update 语句同步更新
  3. 数据库执行 ALTER TABLE 添加三个新列(第1节 SQL 脚本)
  4. 历史订单数据手动清空(第2节:不需要迁移脚本)
  5. PosOrder 已有 longitude(经度)和 latitude(纬度)字段,骑手距离排序可直接使用

15.1 用户端订单列表

位置:UserOrderController.java(路径前缀 /system/userOrder) 接口:GET /system/userOrder/orderList

参数:

参数 类型 必填 说明
token Header 是 用户身份
page int 是 页码,从1开始
size int 是 每页条数
tab String 是 Tab 标识
type String 否 订单类型筛选:0 外送 / 1 自取 / 2 堂食,不传则查所有类型

Tab 映射:

tab 值 查询条件 说明
unpaid payStatus=0 AND type=0 待付款(仅外送在线支付订单)
active state IN (0,1,2) AND afterSaleStatus=0 AND (payStatus=1 OR type IN (1,2)) 进行中
completed state=3 AND afterSaleStatus=0 已完成
cancelled state=4 AND afterSaleStatus=0 已取消
refund afterSaleStatus > 0 退款/售后

返回格式:复用旧接口 getorderlist() 的返回格式。PosOrder 全字段自动序列化(含 deliveryStatus、payStatus、afterSaleStatus 新字段),额外附加以下关联数据:

附加字段 来源 说明
ddId PosOrder.ddId 转为字符串
shanghu InfoUser(shId) 商家用户信息
store PosStore(mdId) 门店信息
shaddress InfoAddress(shdzId) 或 JSON解析 收货地址
user InfoUser(userId) 用户信息
food PosOrder.food JSON 解析为商品列表
parentRemarks OrderParent 父订单备注

骑手信息只返回 PosOrder 自带的 qsId 和 qsImg,不做额外 InfoUser 查询。

返回示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "ddId": "202605150001",
        "type": 0,
        "state": 1,
        "deliveryStatus": null,
        "payStatus": 1,
        "afterSaleStatus": 0,
        "qsId": null,
        "qsImg": null,
        "mdId": 100,
        "amount": 50000,
        "cretim": "2026-05-15 12:00:00",
        "shanghu": { "userId": 50, "nickName": "商家A" },
        "store": { "id": 100, "mdName": "测试门店" },
        "shaddress": { "id": 1, "address": "..." },
        "user": { "userId": 1, "nickName": "用户A" },
        "food": [{ "foodName": "菜品1", "num": 2 }],
        "parentRemarks": ""
      }
    ],
    "total": 100,
    "page": 1,
    "size": 10
  }
}

15.2 商家端订单列表

位置:PosOrderShOprateController.java(路径前缀 /system/orderShOprate) 接口:GET /system/orderShOprate/orderList

参数:

参数 类型 必填 说明
token Header 是 商家身份
page int 是 页码,从1开始
size int 是 每页条数
tab String 是 Tab 标识
mdId String 是 门店ID
type String 否 订单类型筛选:0 外送 / 1 自取 / 2 堂食,不传则显示全部类型

Tab 映射:

tab 值 查询条件 说明
pending state=0 AND (payStatus=1 OR type IN (1,2)) AND mdId=? 待受理
preparing state=1 AND mdId=? 待出餐
ready state=2 AND afterSaleStatus=0 AND mdId=? 已出餐
completed state=3 AND afterSaleStatus=0 AND mdId=? 已完成
cancelled state=4 AND afterSaleStatus=0 AND mdId=? 已取消
refund afterSaleStatus > 0 AND mdId=? 退款/售后

返回格式:PosOrder 全字段序列化 + 新字段自动包含。返回 { records, total, page, size } 格式。

15.3 骑手端订单列表

位置:PosOrderQsOprateController.java(路径前缀 /system/orderQsOprate) 接口:GET /system/orderQsOprate/orderList

参数:

参数 类型 必填 说明
token Header 是 骑手身份(JwtUtil.getusid(token) 即为 qsId)
page int 是 页码,从1开始
size int 是 每页条数
tab String 是 Tab 标识
longitude BigDecimal 条件必填 骑手当前经度(newTask Tab 必填)
latitude BigDecimal 条件必填 骑手当前纬度(newTask Tab 必填)

Tab 映射:

tab 值 查询条件 排序
newTask type=0 AND deliveryStatus=0 AND state=2 AND afterSaleStatus=0 距离 ASC(使用 PosOrder.longitude/latitude 计算到骑手的距离)
toPickup deliveryStatus=1 AND qsId=当前骑手ID AND afterSaleStatus=0 时间 ASC
delivering deliveryStatus=2 AND qsId=当前骑手ID AND afterSaleStatus=0 时间 ASC
completed state=3 AND afterSaleStatus=0 AND qsId=当前骑手ID 时间 DESC
cancelled state=4 AND qsId=当前骑手ID AND afterSaleStatus=0 时间 DESC
refund afterSaleStatus > 0 AND qsId=当前骑手ID 时间 DESC

距离排序实现:

wrapper.last("ORDER BY ST_Distance_Sphere(point(longitude, latitude), " +
             "point(" + lng + ", " + lat + ")) ASC");

返回格式:PosOrder 全字段序列化 + 新字段自动包含。返回 { records, total, page, size } 格式。

15.4 设计决策记录

以下问题在需求讨论中确认,记录备查:

# 问题 决策 原因
1 旧方法如何处理 保留代码不删,数据迁移后自然废弃 系统未正式使用,旧方法不再维护
2 Tab 参数格式 用有意义的字符串(pending/active 等) 参考美团做法,语义清晰,与旧接口 z01 模式完全独立
3 用户端返回格式 复用旧接口格式 + 新字段自动包含 前端切换成本低,新字段通过 PosOrder 序列化自动包含
4 骑手信息 只返回 qsId + qsImg 和旧接口一致,不做额外 InfoUser 查询
5 已取消订单归属 三个端都新增 cancelled Tab 参考美团做法,已取消订单(state=4, afterSaleStatus=0)需要独立 Tab 承接,否则无任何 Tab 显示
6 商家端 Tab 数量 从 4 个扩充到 6 个(加 completed + cancelled) 参考美团商家端有"已完成"和"已取消"分类
7 骑手距离排序 用 LambdaQueryWrapper.last() 追加原始 SQL PosOrder 已有 longitude/latitude 字段,无需 JOIN PosStore
8 状态展示文字 前端根据 type+state+deliveryStatus+afterSaleStatus 组合自行计算 后端只返回数据,不返回 statusText
9 分页格式 返回 { records, total, page, size } 前端需要 total 做分页控件
10 类型筛选 用户端和商家端都支持可选的 type 参数 商家可能需要区分外送/自取/堂食订单
11 数据迁移策略 四步骤一次性执行(加字段+填充+迁移state) 系统未正式使用,无需分步兼容
12 自取/堂食现金 payStatus 创建时 payStatus=0,商家收款+完成时设为1 商家需要明确知道现金是否已收到,收钱和完成是同一动作
13 待受理 Tab 条件 state=0 AND (payStatus=1 OR type IN (1,2)) 自取/堂食下单即可见,外送需先付款
14 用户端"待付款"Tab payStatus=0 AND type=0 自取/堂食现金无需在线付款,不显示在待付款
15 订单完成机制 商家/骑手操作为主触发,用户确认为可选,定时任务兜底 参考美团:不依赖用户点确认
16 数据迁移 不需要,历史数据手动清空 系统未正式使用
17 退款流程 本次不实现,afterSaleStatus 字段预留 尚未接入在线支付,无退款场景
18 旧字段处理 diningStatus/isAccepted/kefuState 等保留但废弃 新逻辑不再使用,避免序列化问题
19 商家操作步骤 接单(state 0→1)和出餐(state 1→2)分两步 参考美团商家端流程

16. 平台管理端(foodie-admin-vue)调整

16.1 订单列表页(src/views/system/order/index.vue)

位置:E:\QtwCode\foodie\foodie-admin-vue\src\views\system\order\index.vue 后端接口:/system/order/list(保留现有接口,后端需支持新的筛选参数)

筛选条件变更

原来用单个下拉框筛选旧 state(0-12),改为四个独立下拉框:

筛选字段 下拉选项 默认
state 全部 / 待处理(0) / 已接单(1) / 已出餐(2) / 已完成(3) / 已取消(4) 全部
payStatus 全部 / 未支付(0) / 已支付(1) / 已退款(2) 全部
deliveryStatus 全部 / 待接单(0) / 骑手已接单(1) / 配送中(2) / 已送达(3) / 不适用(NULL) 全部
afterSaleStatus 全部 / 无售后(0) / 申请中(1) / 退款中(2) / 已退款(3) / 退款拒绝(4) / 客服介入(5) / 售后完成(6) 全部

保留现有的订单号、用户ID、订单类型、创建时间筛选条件不变。

状态列变更

原来单个 el-tag 显示旧 state,改为两个 tag 组合显示——主状态 + 副状态:

| 订单状态列 |
|-----------|
| [待处理] [已支付]          ← state=0, payStatus=1 |
| [已接单]                  ← state=1 |
| [已出餐] [配送中]          ← state=2, deliveryStatus=2 |
| [已出餐] [待骑手]          ← state=2, deliveryStatus=0 |
| [已完成]                  ← state=3 |
| [已取消]                  ← state=4, afterSaleStatus=0 |
| [已取消] [已退款]          ← state=4, afterSaleStatus=3 |
| [已接单] [退款申请中]      ← state=1, afterSaleStatus=1 |

规则:

  • 主 tag:根据 state 显示(待处理/已接单/已出餐/已完成/已取消)
  • 副 tag:根据订单类型和附加状态显示
    • 外送(type=0):显示 deliveryStatus(待骑手/骑手已接单/配送中/已送达)
    • 所有类型:显示 payStatus(仅未支付/已退款时显示,已支付不显示避免冗余)
    • 所有类型:显示 afterSaleStatus(仅 >0 时显示)
  • 已取消的订单主 tag 用红色(type="danger")

修改订单弹窗

原来用 sys_order_type 字典的 radio 选择旧 state 值。改为:

  • 只能修改 state 字段(0-4 的下拉选择)
  • 平台管理员通常不应该直接改状态,如确需保留此功能,下拉选项为:待处理(0) / 已接单(1) / 已出餐(2) / 已完成(3) / 已取消(4)

16.2 订单详情弹窗

进度条变更

原来7步进度条(未支付→已付款→已接单→取餐中→配送中→已完成→异常),改为4步:

待处理(0) → 已接单(1) → 已出餐(2) → 已完成(3)
  • 已取消(state=4)的订单不显示进度条,改为显示红色 已取消 标签
  • :active 绑定 orxiangq.state(0-3 直接对应步骤索引)

新增状态信息区域

在进度条下方,增加三个状态信息行(仅在对应值非默认时显示):

显示条件 内容
payStatus != 1 支付状态:未支付(payStatus=0) / 已退款(payStatus=2)
type == 0 且 deliveryStatus != null 配送状态:待接单(0) / 骑手已接单(1) / 配送中(2) / 已送达(3)
afterSaleStatus > 0 售后状态:申请中(1) / 退款中(2) / 已退款(3) / 退款拒绝(4) / 客服介入(5) / 售后完成(6)

16.3 后端接口调整

现有 /system/order/list 接口需支持新的筛选参数:

  • 新增 state、payStatus、deliveryStatus、afterSaleStatus 查询参数
  • 多个筛选条件同时传入时为 AND 关系
  • deliveryStatus 需支持 NULL 筛选(自取/堂食订单)
  • 旧 sys_order_type 字典可废弃

16.4 字典数据

新增以下字典或在前端硬编码(推荐前端硬编码,因为值固定不变):

字段 选项
state [{value:0, label:'待处理'}, {value:1, label:'已接单'}, {value:2, label:'已出餐'}, {value:3, label:'已完成'}, {value:4, label:'已取消'}]
payStatus [{value:0, label:'未支付'}, {value:1, label:'已支付'}, {value:2, label:'已退款'}]
deliveryStatus [{value:0, label:'待接单'}, {value:1, label:'骑手已接单'}, {value:2, label:'配送中'}, {value:3, label:'已送达'}]
afterSaleStatus [{value:0, label:'无售后'}, {value:1, label:'申请中'}, {value:2, label:'退款中'}, {value:3, label:'已退款'}, {value:4, label:'退款拒绝'}, {value:5, label:'客服介入'}, {value:6, label:'售后完成'}]

推荐前端硬编码而非字典表,因为这些值由代码逻辑定义,不会由运营人员修改。

美团订单完成机制参考

美团各类型订单的完成机制:

  • 外送:骑手点"已送达"后启动自动完成倒计时(通常24小时),到期自动标记已完成。用户可提前手动确认收货,但不点确认不影响完成。
  • 自取:商家点"出餐完成"后启动倒计时,到期自动完成。用户到店取餐可在 App 中扫码确认,但非必须。
  • 堂食:商家出餐后自动倒计时完成。无需用户额外操作。

核心原则:商家/骑手操作是主触发器,用户确认是可选的,自动完成是兜底。 用户不确认不会导致订单永远"挂着"。

参考资源