管理端开发日记④:三种异常处置流程的实现

1439 字
7 分钟
管理端开发日记④:三种异常处置流程的实现

异常工单真正难的地方,从来不是点一下“处理”。取消、重派和补发看起来都是解决问题,背后修改的数据却完全不同:有的终止原任务,有的保留原订单并生成新链路,有的还涉及退款。

1. 统一入口,不强行统一业务#

三种流程共用 ExceptionProcessPage 和 useExceptionProcess.js,但不会为了复用而把所有字段揉成一团。

const PROCESS_TYPES = {
CANCEL: '取消退款',
REDISPATCH: '重新派送',
RESHIPMENT: '补发订单'
}

公用部分包括异常概览、原订单信息、凭证、处理日志和底部操作区;差异部分由 processType 决定请求、表单和提交动作。

2. 页面阶段:登记与跟踪#

处置页并非只有“未处理/已处理”。重派和补发在创建派生订单后还需要继续跟踪,因此定义两个页面阶段:

const stage = computed(() => {
return ticket.value?.resultOrderId ? 'tracking' : 'registration'
})
  • registration:确认方案、填写原因、创建后续任务;
  • tracking:展示新订单、新飞行任务和合并后的流程日志。

这个阶段模型让刷新页面后仍能根据服务端数据还原视图,不需要依赖浏览器内存中的“刚刚提交成功”。

3. 取消退款流程#

取消流程需要先判断当前订单和飞行任务是否允许取消。如果任务仍在 WAIT_TAKEOFF,先停止任务,再调用取消退款接口,最后更新异常工单状态和用户消息。

读取原订单与退款信息
↓
校验订单是否仍可取消
↓
取消待起飞任务(如存在)
↓
提交取消/退款
↓
更新工单与通知

这一顺序不能随意交换。如果先把工单关闭,再取消飞行任务失败,就会出现“页面显示处理完毕,但无人机任务仍在”的数据裂缝。

4. 重新派送流程#

重派适用于原货物仍可继续配送,但需要重新安排飞行任务的情况。它通过专用接口建立新的调度关系,同时保留原订单作为问题来源。

重派编号会带有可辨识前缀和序号:

function buildRedispatchNo(sourceOrderNo, sequence) {
return `RP${sourceOrderNo}-${String(sequence).padStart(2, '0')}`
}

这样客服从编号就能判断它不是独立的新订单,而是某张订单的再次派送。

5. 补发流程与子订单#

补发意味着需要创建一张派生订单。新订单复制必要的收货、货品和服务信息,但必须清空自身主键、当前任务等运行态字段:

function createDerivedOrder(original, sequence) {
return {
...original,
orderId: undefined,
currentTaskId: undefined,
parentOrderId: original.orderId,
orderNo: `${original.orderNo}-B${sequence}`,
orderStatus: 'WAIT_DISPATCH',
deliveryFee: 0
}
}

这里最容易出错的是“复制得太完整”。如果把原订单 ID 或已完成任务一起提交,新订单就会继承不属于自己的状态。派生订单应复制业务事实,不复制数据库身份和运行过程。

补发完成后,还要分别写入原订单和子订单的流程日志,并把子订单 ID 写回异常工单的 resultOrderId。这三个动作建立了可追踪的关系链:

异常工单 ── resultOrderId ──> 补发子订单
│ │
└── orderId ──> 原订单 <── parentOrderId

6. 合并流程日志#

进入跟踪阶段后,页面同时加载原订单和派生订单日志。合并时按 processId 去重,再按操作时间排序:

const logMap = new Map()
originalLogs.concat(childLogs).forEach(log => {
logMap.set(String(log.processId), log)
})
const mergedLogs = [...logMap.values()].sort((a, b) => {
return new Date(a.operateTime) - new Date(b.operateTime)
})

用户看到的是一条连续故事,而不是两张互不相干的日志表。

7. 异步加载的容错#

处置页会并行读取订单、凭证、日志、退款、子订单和飞行任务。非核心数据使用 Promise.allSettled,关键动作则必须串行并在失败时中止。简单说就是:展示数据可以“尽量多拿”,写业务数据必须“每一步都算数”。

8. 本篇小结#

一致性与恢复能力#

三种流程都包含多个写动作,理想情况下应由后端事务或专用业务接口保证一致性。前端不能通过连续调用几个 CRUD 就假装拥有事务能力。若接口暂时拆分,页面至少要做到:前一步失败不执行后一步、错误信息明确、重新进入页面能根据工单与子订单数据恢复阶段。

页面内存不是流程凭证
工单状态 + resultOrderId + 流程日志才是恢复依据

重复点击“补发”时,后端需要检查是否已有 resultOrderId;重复取消时检查订单和退款状态;重派接口则要避免生成两条有效任务。前端的 submitting 只拦住当前设备上的连点,真正的幂等仍在服务端。

为什么保留原订单#

重派和补发都不直接覆写原订单,因为原订单包含用户最初提交的事实、审核意见和失败过程。派生对象记录后续补救,父子关系负责追溯。若直接把原订单从“失败”改成“待调度”,历史看起来会像失败从未发生,报表口径也会被悄悄改写。

三种异常流程让我真正感受到状态机不是画在文档里的箭头,而是数据之间不能走错的路。取消要收尾,重派要续跑,补发要另起一条支线。把关系理顺以后,复杂流程就不再像一锅面条——至少能看清哪根是自己的。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
管理端开发日记④:三种异常处置流程的实现
https://firefly.cuteleaf.cn/posts/admin-dev-diary-4/
作者
Yore
发布于
2026-08-04
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Yore
Hello, I'm Yore.
公告
欢迎来到Yore的博客!本站基于原创作者夏叶的博客网站模版创作。
分类
标签
最新动态
站点统计
文章
51
分类
2
标签
124
总字数
94,115
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.15.0
文章许可
CC BY-NC-SA 4.0