From 56ae71babf28044c96e5492d75e04a9cde779115 Mon Sep 17 00:00:00 2001
From: not-like-juvenile <16056107+not-like-juvenile@user.noreply.gitee.com>
Date: Fri, 6 Feb 2026 15:10:18 +0800
Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E5=88=86=E6=9E=90?=
=?UTF-8?q?=E5=92=8C=E5=AF=B9=E5=BA=94=E4=B8=8D=E5=90=8C=E8=A7=92=E8=89=B2?=
=?UTF-8?q?=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages.json | 14 +
.../delivery/doc/{ => old}/DELIVERY_LOGIC.md | 2 +-
.../doc/{ => old}/FIX_REPORT_20260203.md | 0
.../delivery/doc/{ => old}/STATE_1-5_LOGIC.md | 0
pages/mall/delivery/doc/{ => old}/all.md | 0
pages/mall/delivery/doc/{ => old}/delivery.md | 0
pages/mall/delivery/doc/{ => old}/earnings.md | 0
pages/mall/delivery/doc/{ => old}/index.md | 0
.../delivery/doc/{ => old}/more-orders.md | 0
.../delivery/doc/{ => old}/order-detail.md | 0
.../delivery/doc/{ => old}/order-history.md | 0
.../delivery/doc/{ => old}/profile-edit.md | 0
pages/mall/delivery/doc/{ => old}/profile.md | 0
pages/mall/delivery/doc/需求文档/README.md | 43 +-
.../express_tracking_mock_platform.sql | 4 +
.../express_tracking_platform_upgrade.sql | 174 ++++++
.../delivery/doc/需求文档/前端字段清单.md | 11 +
.../doc/需求文档/数据库对比与修改建议.md | 196 +++++++
.../需求文档/生产表说明_platform_express.md | 148 ++++++
.../delivery/doc/需求文档/配送模块需求文档.md | 12 +-
pages/mall/delivery/test/api-simulator.uvue | 291 ++++++++++
.../test/consumer-logistics-detail.uvue | 494 +++++++++++++++++
.../delivery/test/consumer-order-list.uvue | 225 ++++++++
pages/mall/delivery/test/index.uvue | 122 +++++
.../delivery/test/merchant-order-detail.uvue | 269 ++++++++++
.../delivery/test/merchant-order-list.uvue | 327 ++++++++++++
pages/mall/delivery/test/mock-service.uts | 496 ++++++++++++++++++
.../delivery/test/platform-config-center.uvue | 187 +++++++
.../test/platform-tracking-query.uvue | 278 ++++++++++
.../delivery/test/platform-webhook-logs.uvue | 238 +++++++++
30 files changed, 3510 insertions(+), 21 deletions(-)
rename pages/mall/delivery/doc/{ => old}/DELIVERY_LOGIC.md (99%)
rename pages/mall/delivery/doc/{ => old}/FIX_REPORT_20260203.md (100%)
rename pages/mall/delivery/doc/{ => old}/STATE_1-5_LOGIC.md (100%)
rename pages/mall/delivery/doc/{ => old}/all.md (100%)
rename pages/mall/delivery/doc/{ => old}/delivery.md (100%)
rename pages/mall/delivery/doc/{ => old}/earnings.md (100%)
rename pages/mall/delivery/doc/{ => old}/index.md (100%)
rename pages/mall/delivery/doc/{ => old}/more-orders.md (100%)
rename pages/mall/delivery/doc/{ => old}/order-detail.md (100%)
rename pages/mall/delivery/doc/{ => old}/order-history.md (100%)
rename pages/mall/delivery/doc/{ => old}/profile-edit.md (100%)
rename pages/mall/delivery/doc/{ => old}/profile.md (100%)
create mode 100644 pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql
create mode 100644 pages/mall/delivery/doc/需求文档/数据库对比与修改建议.md
create mode 100644 pages/mall/delivery/doc/需求文档/生产表说明_platform_express.md
create mode 100644 pages/mall/delivery/test/api-simulator.uvue
create mode 100644 pages/mall/delivery/test/consumer-logistics-detail.uvue
create mode 100644 pages/mall/delivery/test/consumer-order-list.uvue
create mode 100644 pages/mall/delivery/test/index.uvue
create mode 100644 pages/mall/delivery/test/merchant-order-detail.uvue
create mode 100644 pages/mall/delivery/test/merchant-order-list.uvue
create mode 100644 pages/mall/delivery/test/mock-service.uts
create mode 100644 pages/mall/delivery/test/platform-config-center.uvue
create mode 100644 pages/mall/delivery/test/platform-tracking-query.uvue
create mode 100644 pages/mall/delivery/test/platform-webhook-logs.uvue
diff --git a/pages.json b/pages.json
index a14a82d7..da39f78f 100644
--- a/pages.json
+++ b/pages.json
@@ -401,6 +401,13 @@
"navigationStyle": "custom"
}
},
+ {
+ "path": "test/api-simulator",
+ "style": {
+ "navigationBarTitleText": "第三方接口模拟器",
+ "navigationStyle": "custom"
+ }
+ },
{
"path": "test/platform-config-center",
"style": {
@@ -414,6 +421,13 @@
"navigationBarTitleText": "用户查快递",
"navigationStyle": "custom"
}
+ },
+ {
+ "path": "test/consumer-order-list",
+ "style": {
+ "navigationBarTitleText": "我的快递",
+ "navigationStyle": "custom"
+ }
}
]
},
diff --git a/pages/mall/delivery/doc/DELIVERY_LOGIC.md b/pages/mall/delivery/doc/old/DELIVERY_LOGIC.md
similarity index 99%
rename from pages/mall/delivery/doc/DELIVERY_LOGIC.md
rename to pages/mall/delivery/doc/old/DELIVERY_LOGIC.md
index 510f802d..619d390e 100644
--- a/pages/mall/delivery/doc/DELIVERY_LOGIC.md
+++ b/pages/mall/delivery/doc/old/DELIVERY_LOGIC.md
@@ -1,4 +1,4 @@
-# 配送端逻辑说明(概要与建议)
+# 配送端逻辑说明(概要与建议)(舍弃)
**概览**
- 本文档总结 `pages/mall/delivery` 目录下配送端主要页面的工作流与实现细节,定位到关键数据库表与状态机,列出已识别的并发/同步风险,并给出可执行的改进建议。
diff --git a/pages/mall/delivery/doc/FIX_REPORT_20260203.md b/pages/mall/delivery/doc/old/FIX_REPORT_20260203.md
similarity index 100%
rename from pages/mall/delivery/doc/FIX_REPORT_20260203.md
rename to pages/mall/delivery/doc/old/FIX_REPORT_20260203.md
diff --git a/pages/mall/delivery/doc/STATE_1-5_LOGIC.md b/pages/mall/delivery/doc/old/STATE_1-5_LOGIC.md
similarity index 100%
rename from pages/mall/delivery/doc/STATE_1-5_LOGIC.md
rename to pages/mall/delivery/doc/old/STATE_1-5_LOGIC.md
diff --git a/pages/mall/delivery/doc/all.md b/pages/mall/delivery/doc/old/all.md
similarity index 100%
rename from pages/mall/delivery/doc/all.md
rename to pages/mall/delivery/doc/old/all.md
diff --git a/pages/mall/delivery/doc/delivery.md b/pages/mall/delivery/doc/old/delivery.md
similarity index 100%
rename from pages/mall/delivery/doc/delivery.md
rename to pages/mall/delivery/doc/old/delivery.md
diff --git a/pages/mall/delivery/doc/earnings.md b/pages/mall/delivery/doc/old/earnings.md
similarity index 100%
rename from pages/mall/delivery/doc/earnings.md
rename to pages/mall/delivery/doc/old/earnings.md
diff --git a/pages/mall/delivery/doc/index.md b/pages/mall/delivery/doc/old/index.md
similarity index 100%
rename from pages/mall/delivery/doc/index.md
rename to pages/mall/delivery/doc/old/index.md
diff --git a/pages/mall/delivery/doc/more-orders.md b/pages/mall/delivery/doc/old/more-orders.md
similarity index 100%
rename from pages/mall/delivery/doc/more-orders.md
rename to pages/mall/delivery/doc/old/more-orders.md
diff --git a/pages/mall/delivery/doc/order-detail.md b/pages/mall/delivery/doc/old/order-detail.md
similarity index 100%
rename from pages/mall/delivery/doc/order-detail.md
rename to pages/mall/delivery/doc/old/order-detail.md
diff --git a/pages/mall/delivery/doc/order-history.md b/pages/mall/delivery/doc/old/order-history.md
similarity index 100%
rename from pages/mall/delivery/doc/order-history.md
rename to pages/mall/delivery/doc/old/order-history.md
diff --git a/pages/mall/delivery/doc/profile-edit.md b/pages/mall/delivery/doc/old/profile-edit.md
similarity index 100%
rename from pages/mall/delivery/doc/profile-edit.md
rename to pages/mall/delivery/doc/old/profile-edit.md
diff --git a/pages/mall/delivery/doc/profile.md b/pages/mall/delivery/doc/old/profile.md
similarity index 100%
rename from pages/mall/delivery/doc/profile.md
rename to pages/mall/delivery/doc/old/profile.md
diff --git a/pages/mall/delivery/doc/需求文档/README.md b/pages/mall/delivery/doc/需求文档/README.md
index 0894388e..04242a59 100644
--- a/pages/mall/delivery/doc/需求文档/README.md
+++ b/pages/mall/delivery/doc/需求文档/README.md
@@ -3,12 +3,14 @@
本目录聚焦“模拟三通一达后台(Mock 承运方 Server)”的联调与测试需求,用于在未签约真实承运方前验证平台侧的 Webhook 接收、验签、幂等、入库与前端时间线展示。
## 文档分层(先看什么、后看什么)
-- 需求(做什么):`配送模块需求文档.md`
-- 页面(后台长什么样):`后台页面设计说明.md`
-- 接口(怎么对接):`接口规范.md`
-- 映射(状态怎么统一):`状态映射表.md`
-- 前端契约(怎么展示):`前端字段清单.md`
-- 补齐清单(还缺什么):`缺口与待补充清单.md`
+- 需求(做什么):[配送模块需求文档.md](配送模块需求文档.md)
+- 页面(后台长什么样):[后台页面设计说明.md](后台页面设计说明.md)
+- 接口(怎么对接):[接口规范.md](接口规范.md)
+- 映射(状态怎么统一):[状态映射表.md](状态映射表.md)
+- 前端契约(怎么展示):[前端字段清单.md](前端字段清单.md)
+- 补齐清单(还缺什么):[缺口与待补充清单.md](缺口与待补充清单.md)
+- 数据库(旧表 vs 新表):[数据库对比与修改建议.md](数据库对比与修改建议.md)
+- 数据库(生产表字段说明):[生产表说明_platform_express.md](生产表说明_platform_express.md)
## 先读这个:这套文档在解决什么
你现在要做的是一个“像三通一达一样会产生物流节点并回调的平台外部系统”。真实承运方接口通常需要签约与联调周期,因此先通过 Mock 承运方 Server 把平台侧能力跑通:
@@ -27,14 +29,14 @@
- 故障注入必须:重复推送、乱序、延迟、签名错误、缺字段等是外部对接最常见问题,必须在联调阶段覆盖。
## 阅读顺序(推荐)
-1) 先看 `配送模块需求文档.md`:理解 Mock Server 要做什么、有哪些验收与故障注入。
-2) 再看 `后台页面设计说明.md`:明确平台后台/商家后台需要哪些页面与最小展示/权限要求。
-3) 再看 `接口规范.md`:
+1) 先看 [配送模块需求文档.md](配送模块需求文档.md):理解 Mock Server 要做什么、有哪些验收与故障注入。
+2) 再看 [后台页面设计说明.md](后台页面设计说明.md):明确平台后台/商家后台需要哪些页面与最小展示/权限要求。
+3) 再看 [接口规范.md](接口规范.md):
- Mock Server -> 平台:Webhook 推送格式与验签
- 平台/测试工具 -> Mock Server:控制面 API(创建运单/追加事件/跑场景/触发推送)
-4) 看 `状态映射表.md`:明确 `event_code` 如何映射到平台统一 `status_code`。
-5) 看 `前端字段清单.md`:前端时间线组件到底需要哪些字段(以及 `source` 如何标注)。
-6) 最后看 `缺口与待补充清单.md`:把联调验收所需材料补齐,避免漏项。
+4) 看 [状态映射表.md](状态映射表.md):明确 `event_code` 如何映射到平台统一 `status_code`。
+5) 看 [前端字段清单.md](前端字段清单.md):前端时间线组件到底需要哪些字段(以及 `source` 如何标注)。
+6) 最后看 [缺口与待补充清单.md](缺口与待补充清单.md):把联调验收所需材料补齐,避免漏项。
## 快速上手(联调路径)
1) 平台准备一个 Webhook 接收地址:`/webhook/express/status`,并实现 `X-Signature` 验签与幂等去重(按 `event_id`)。
@@ -48,12 +50,19 @@
- 为什么要状态映射?因为不同承运方文案与事件码不稳定,统一状态才能做稳定 UI 与统计。
文件列表:
-- 配送模块需求文档.md:Mock 承运方 Server 的目标、范围、流程与验收。
-- 接口规范.md:Mock Server 控制面 API + Mock -> 平台 Webhook 推送规范。
-- 状态映射表.md:Mock 事件码到平台统一状态映射建议。
-- 前端字段清单.md:时间线组件所需字段契约与展示规则。
+- [配送模块需求文档.md](配送模块需求文档.md):Mock 承运方 Server 的目标、范围、流程与验收。
+- [后台页面设计说明.md](后台页面设计说明.md):后台页面结构、权限与最小交互。
+- [接口规范.md](接口规范.md):Mock Server 控制面 API + Mock -> 平台 Webhook 推送规范。
+- [状态映射表.md](状态映射表.md):Mock 事件码到平台统一状态映射建议。
+- [前端字段清单.md](前端字段清单.md):时间线组件所需字段契约与展示规则。
+- [缺口与待补充清单.md](缺口与待补充清单.md):联调/验收需要补齐的资料与决策项。
+- [数据库对比与修改建议.md](数据库对比与修改建议.md):旧自营骑手表 vs 第三方快递表的改库建议。
+- [生产表说明_platform_express.md](生产表说明_platform_express.md):生产环境 `platform_express_*` 三表用途、字段、约束/索引与写入查询建议。
数据库:
-- `mall_sql/schemas/express_tracking_mock_platform.sql`:本体系建议的 Postgres 建表脚本(平台统一入库 + Mock 持久化)。
+- 生产环境(推荐执行):[express_tracking_platform_upgrade.sql](express_tracking_platform_upgrade.sql)
+ - 仅包含平台侧 `platform_express_*` 三张表,供生产代码使用。
+- 联调/设计参考(不要用于生产直接落库):[express_tracking_mock_platform.sql](express_tracking_mock_platform.sql)
+ - 包含平台侧表 + `mock_*` 测试表(故障注入/回放用),仅用于联调与文档说明。
建议下一步:在平台侧实现一个可切换的“Mock 数据源”开关(仅测试环境),并在 QA 用例中覆盖重复/乱序/验签失败等注入场景。
diff --git a/pages/mall/delivery/doc/需求文档/express_tracking_mock_platform.sql b/pages/mall/delivery/doc/需求文档/express_tracking_mock_platform.sql
index f0f6c44a..01592533 100644
--- a/pages/mall/delivery/doc/需求文档/express_tracking_mock_platform.sql
+++ b/pages/mall/delivery/doc/需求文档/express_tracking_mock_platform.sql
@@ -1,4 +1,8 @@
-- 物流轨迹(第三方/Mock)数据库设计(PostgreSQL / Supabase 兼容)
+-- 重要说明(生产环境请勿直接执行本文件):
+-- - 本文件包含平台侧表 + mock_* 测试表(用于联调、故障注入与回放)。
+-- - 生产环境请执行迁移脚本:mall_sql/migrations/express_tracking_platform_upgrade.sql
+-- 该脚本仅创建平台侧 platform_express_* 三张表,并与主库 UUID/updated_at 规范对齐。
-- 目标:
-- 1) 平台侧:统一接收不同第三方事件 -> 幂等去重/乱序入库 -> 给前端提供稳定时间线
-- 2) Mock 承运方侧:可持久化运单/事件/场景/推送日志,支持故障注入与回放
diff --git a/pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql b/pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql
new file mode 100644
index 00000000..3d8073a1
--- /dev/null
+++ b/pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql
@@ -0,0 +1,174 @@
+-- =====================================================================================
+-- 第三方快递轨迹(平台侧)表结构升级 (PostgreSQL + Supabase)
+-- 用途:
+-- - 引入第三方承运方运单与轨迹事件的统一入库模型
+-- - 支撑商家端/用户端/平台后台的同源轨迹展示
+-- 说明:
+-- - 仅创建 platform 侧三张表:platform_express_waybills / platform_express_tracking_events / platform_express_event_raw
+-- - 不包含 mock_* 测试表
+-- - 不涉及自营骑手表(ml_delivery_*)
+-- =====================================================================================
+
+BEGIN;
+
+-- 依赖扩展(项目主库已使用 uuid-ossp;这里重复声明是安全的)
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+CREATE EXTENSION IF NOT EXISTS "btree_gin";
+
+-- updated_at 维护函数:若主库已存在则不重复创建
+DO $$
+BEGIN
+ IF to_regprocedure('public.update_updated_at_column()') IS NULL THEN
+ CREATE OR REPLACE FUNCTION public.update_updated_at_column()
+ RETURNS TRIGGER
+ LANGUAGE plpgsql
+ AS $$
+ BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+ END;
+ $$;
+ END IF;
+END $$;
+
+-- =====================================================================
+-- A. 平台侧(platform):统一轨迹模型入库与查询
+-- =====================================================================
+
+-- 运单主表:按 (carrier, tracking_no) 唯一
+CREATE TABLE IF NOT EXISTS public.platform_express_waybills (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+
+ -- 关联订单(推荐关联主键;order_no 作为展示/兜底)
+ order_id UUID NULL REFERENCES public.ml_orders(id) ON DELETE SET NULL,
+ order_no VARCHAR(64) NULL,
+
+ carrier VARCHAR(32) NOT NULL, -- YUNDA/YTO/ZTO/STO/KDN...
+ tracking_no VARCHAR(64) NOT NULL,
+ source VARCHAR(16) NOT NULL DEFAULT 'mock', -- mock/carrier/aggregator
+
+ current_status_code VARCHAR(32) NOT NULL DEFAULT 'PENDING',
+ current_status_text TEXT NULL,
+
+ eta TIMESTAMP WITH TIME ZONE NULL,
+ last_synced_at TIMESTAMP WITH TIME ZONE NULL,
+
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ CONSTRAINT uk_platform_express_waybill UNIQUE (carrier, tracking_no)
+);
+
+-- updated_at 触发器
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_trigger
+ WHERE tgname = 'trigger_platform_express_waybills_updated_at'
+ ) THEN
+ CREATE TRIGGER trigger_platform_express_waybills_updated_at
+ BEFORE UPDATE ON public.platform_express_waybills
+ FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
+ END IF;
+END $$;
+
+-- 索引(按订单查运单是主查询路径)
+CREATE INDEX IF NOT EXISTS idx_platform_express_waybills_order_id
+ ON public.platform_express_waybills(order_id);
+CREATE INDEX IF NOT EXISTS idx_platform_express_waybills_order_no
+ ON public.platform_express_waybills(order_no);
+CREATE INDEX IF NOT EXISTS idx_platform_express_waybills_tracking_no
+ ON public.platform_express_waybills(tracking_no);
+CREATE INDEX IF NOT EXISTS idx_platform_express_waybills_status
+ ON public.platform_express_waybills(current_status_code);
+
+
+-- 轨迹事件表:前端时间线/告警/统计的主数据来源
+CREATE TABLE IF NOT EXISTS public.platform_express_tracking_events (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+
+ waybill_id UUID NOT NULL REFERENCES public.platform_express_waybills(id) ON DELETE CASCADE,
+ carrier VARCHAR(32) NOT NULL,
+ tracking_no VARCHAR(64) NOT NULL,
+
+ -- 第三方事件唯一标识(可能缺失)
+ event_id VARCHAR(128) NULL,
+
+ event_time TIMESTAMP WITH TIME ZONE NOT NULL,
+ event_code VARCHAR(64) NOT NULL,
+ event_text TEXT NOT NULL,
+
+ -- 平台统一状态
+ status_code VARCHAR(32) NOT NULL,
+
+ node_name VARCHAR(128) NULL,
+ location TEXT NULL,
+ description TEXT NULL,
+
+ evidence_urls JSONB NOT NULL DEFAULT '[]'::jsonb,
+
+ -- 原始回文(用于审计/排障)
+ raw_payload JSONB NULL,
+
+ -- 接收侧信息
+ received_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ source VARCHAR(16) NOT NULL DEFAULT 'webhook', -- webhook/poll/manual
+
+ -- 幂等去重键:优先 event_id;缺失时用 tracking_no+event_code+event_time(+可选字段) 构造
+ dedupe_key VARCHAR(256) NOT NULL,
+
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+
+ CONSTRAINT uk_platform_express_event_dedupe UNIQUE (waybill_id, dedupe_key)
+);
+
+CREATE INDEX IF NOT EXISTS idx_platform_express_events_waybill_time
+ ON public.platform_express_tracking_events(waybill_id, event_time);
+CREATE INDEX IF NOT EXISTS idx_platform_express_events_tracking_time
+ ON public.platform_express_tracking_events(tracking_no, event_time);
+CREATE INDEX IF NOT EXISTS idx_platform_express_events_status
+ ON public.platform_express_tracking_events(status_code);
+CREATE INDEX IF NOT EXISTS idx_platform_express_events_received_at
+ ON public.platform_express_tracking_events(received_at);
+CREATE INDEX IF NOT EXISTS gin_platform_express_events_raw_payload
+ ON public.platform_express_tracking_events USING gin (raw_payload);
+
+
+-- 原始接收表:记录每一次 webhook/轮询原文、验签结果与解析错误
+CREATE TABLE IF NOT EXISTS public.platform_express_event_raw (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+
+ received_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ source VARCHAR(16) NOT NULL DEFAULT 'webhook',
+
+ -- 请求侧标识
+ client_id VARCHAR(64) NULL,
+ carrier VARCHAR(32) NULL,
+ tracking_no VARCHAR(64) NULL,
+
+ -- 安全审计
+ signature_valid BOOLEAN NULL,
+ signature TEXT NULL,
+ ts_header TEXT NULL,
+
+ request_id VARCHAR(64) NULL,
+ remote_ip INET NULL,
+ headers JSONB NULL,
+
+ body JSONB NULL,
+ parse_error TEXT NULL,
+
+ -- 去重辅助(可选):用于识别完全重复的原始请求
+ dedupe_key VARCHAR(256) NULL
+);
+
+CREATE INDEX IF NOT EXISTS idx_platform_express_raw_received_at
+ ON public.platform_express_event_raw(received_at);
+CREATE INDEX IF NOT EXISTS idx_platform_express_raw_tracking_no
+ ON public.platform_express_event_raw(tracking_no);
+CREATE INDEX IF NOT EXISTS idx_platform_express_raw_signature_valid
+ ON public.platform_express_event_raw(signature_valid);
+CREATE INDEX IF NOT EXISTS gin_platform_express_raw_body
+ ON public.platform_express_event_raw USING gin (body);
+
+COMMIT;
diff --git a/pages/mall/delivery/doc/需求文档/前端字段清单.md b/pages/mall/delivery/doc/需求文档/前端字段清单.md
index b1194f5d..2c8ec6f4 100644
--- a/pages/mall/delivery/doc/需求文档/前端字段清单.md
+++ b/pages/mall/delivery/doc/需求文档/前端字段清单.md
@@ -45,6 +45,17 @@
- 手机号必须脱敏(如 138****8000),完整手机号仅在客服/运维界面显示并记录访问审计。
- 证据 URL 应带鉴权或短期有效,前端需支持预览与下载。
+4.1 同源数据,不同端展示颗粒度
+- 同一运单的轨迹事件(`event_code/event_text/status_code/node_name/location`)必须同源,保证事实与状态口径一致;不同端仅做“展示层过滤/脱敏”,不做口径改写。
+- 商家端(B 端):只需展示大致进度(到达某地/中转/派送/签收/异常)与最新节点摘要;不展示配送员/快递员手机号等个人敏感信息。
+- 消费者端(C 端):可展示更详细的节点文案;若提供“联系配送”能力,建议用按钮/虚拟号/中转号承载,而不是把第三方 `event_text` 中的手机号原文直接展示或通过消息推送透传。
+
+4.2 `event_text` 使用建议(避免直接透传敏感信息)
+- `event_text` 属于第三方原文,可能包含手机号、网点内部信息等;建议前端展示使用“清洗后的文案”。
+- 最小实现:
+ - 商家端优先展示 `status_code + node_name/location` 组合出的摘要;必要时展示清洗后的 `event_text`。
+ - 消费者端可展示清洗后的 `event_text`;仅在 `OUT_FOR_DELIVERY` 等末端节点按需提供脱敏联系方式。
+
5. 安卓端适配要点
- 缓存:将最近一次 `status_history` 缓存在本地(仅该用户的订单范围),离线可展示并提示“数据可能不是最新”。
- 图片:POD/证据图片支持大图预览与失败重试;弱网下优先加载文本节点。
diff --git a/pages/mall/delivery/doc/需求文档/数据库对比与修改建议.md b/pages/mall/delivery/doc/需求文档/数据库对比与修改建议.md
new file mode 100644
index 00000000..4d3f4086
--- /dev/null
+++ b/pages/mall/delivery/doc/需求文档/数据库对比与修改建议.md
@@ -0,0 +1,196 @@
+# 配送端(自营骑手)表 vs 第三方快递轨迹表:对比与数据库修改建议
+
+本文目标:
+- 整理“原先配送端(自营骑手/同城配送)”使用的核心数据表。
+- 对比“现在第三方快递配送(韵达/圆通等)”的轨迹/运单数据表。
+- 给出建议的数据库改造方案:如何在不破坏订单主表的前提下,引入第三方快递轨迹能力,并(可选)逐步弃用原先配送端表的依赖。
+
+明确决策(当前结论):
+- 仅支持第三方快递轨迹(承运方运单 + 轨迹时间线)。
+- 不做自营骑手端/同城配送任务流;`ml_delivery_drivers`/`ml_delivery_tasks` 视为历史遗留表,不再作为新能力的依赖与写入目标。
+
+> 说明:本对比只讨论“第三方快递轨迹/运单”与“自营骑手任务”两套模型如何在数据库层共存/迁移;二者是不同业务域,不建议强行复用同一张表。
+
+---
+
+## 1. 原先配送端(自营骑手)核心表(现状)
+
+数据来源:主库 DDL(`mall_sql/schemas/complete_mall_database.sql`)与旧配送端文档(`pages/mall/delivery/doc/old/*`)。
+
+### 1.1 `ml_delivery_drivers`(配送员信息表)
+用途:记录配送员(骑手)档案与实时工作状态;用于配送端登录后的“是否可接单/是否在线”。
+
+关键字段(节选):
+- `id`:配送员 ID
+- `user_id`:关联 `ak_users.id`
+- `real_name`、`id_card`、`driver_license`
+- `vehicle_type`、`vehicle_number`
+- `service_areas`(JSONB)
+- `work_status`:1 在线 / 2 忙碌 / 3 离线
+- `current_lat` / `current_lng`
+- `rating_avg` / `rating_count` / `order_count`
+- `status`:1 正常 / 2 暂停 / 3 离职
+
+### 1.2 `ml_delivery_tasks`(配送任务表,配送端“状态真源”)
+用途:描述“一个订单被分配给某个骑手并经历接单→取货→配送→送达”的任务流。
+
+关键字段(节选):
+- 关联:
+ - `order_id`(UUID,**唯一**,FK 到 `ml_orders.id`)
+ - `driver_id`(FK 到 `ml_delivery_drivers.id`,可为空表示未接单)
+- 地址与费用:
+ - `pickup_address`(JSONB)取货地址
+ - `delivery_address`(JSONB)配送地址
+ - `distance`、`estimated_time`、`delivery_fee`
+- 任务状态:`status`(int)
+ - 1 待接单
+ - 2 已接单
+ - 3 取货中
+ - 4 配送中
+ - 5 已送达
+ - 6 配送失败
+- 时间戳:`assigned_at` / `picked_at` / `delivered_at`
+- 其他:`delivery_code`(取货码)、`remark`、`failure_reason`
+
+> 注意:旧页面/旧文档里常出现 `accepted_at`、`pickup_time`、`delivered_time` 等命名,和主库 DDL 的 `assigned_at/picked_at/delivered_at` 存在差异;如果你要继续使用自营配送链路,建议在代码层做统一字段映射或做一次字段对齐迁移。
+
+### 1.3 与订单表的关系(旧模型的关键耦合点)
+- `ml_delivery_tasks.order_id` 强依赖 `ml_orders.id`,并且 **1 个订单只能有 1 条配送任务**(`order_id UNIQUE`)。
+- 旧实现里常尝试把 `ml_delivery_tasks.status` 同步到 `ml_orders.order_status`,容易造成口径不一致(旧文档也提到了这种冲突风险)。
+
+---
+
+## 2. 现在第三方快递配送(轨迹/运单)表(目标模型)
+
+数据来源:`pages/mall/delivery/doc/需求文档/express_tracking_mock_platform.sql`。
+
+> 这套表的定位不是“骑手任务流”,而是“第三方承运的运单 + 轨迹事件时间线”。
+
+### 2.1 `platform_express_waybills`(平台侧:运单主表)
+用途:一条运单(一个 `tracking_no`)的聚合信息;用于订单详情页展示“承运方/运单号/当前状态/最后同步时间”。
+
+关键字段(节选):
+- 关联:`order_id`(可选)、`order_no`(可选)
+- 运单:`carrier`、`tracking_no`、`source`
+- 聚合状态:`current_status_code`、`current_status_text`
+- 时间:`eta`、`last_synced_at`
+- 约束:`UNIQUE (carrier, tracking_no)`
+
+特点:
+- 同一个订单允许对应多条运单(拆包裹/分批发货)——因为 `order_id` 不唯一。
+
+### 2.2 `platform_express_tracking_events`(平台侧:轨迹事件表)
+用途:时间线的主数据来源;用于前端展示、告警统计与排障。
+
+关键字段(节选):
+- 关联:`waybill_id`(FK 到 `platform_express_waybills.id`)
+- 事件:`event_id`(第三方可能缺失)、`event_time`、`event_code`、`event_text`
+- 平台统一状态:`status_code`
+- 节点:`node_name`、`location`、`description`
+- 证据:`evidence_urls`(jsonb)
+- 原始回文:`raw_payload`(jsonb,可用于审计/排障)
+- 幂等:`dedupe_key` + `UNIQUE (waybill_id, dedupe_key)`
+
+### 2.3 `platform_express_event_raw`(平台侧:原始接收表)
+用途:记录 webhook/轮询的原始请求、验签与解析错误,便于排障与审计。
+
+### 2.4 `mock_*`(Mock 承运方侧表)
+这部分只用于联调/回归/故障注入,不建议进入生产主库的核心业务 schema(可以放在独立 schema 或测试库)。
+
+---
+
+## 3. 两套模型的核心差异(为什么不建议“复用一张表”)
+
+| 维度 | 自营骑手(旧配送端) | 第三方快递(新模型) |
+|---|---|---|
+| 业务对象 | 任务(骑手接单、取货、送达) | 运单(承运方扫描轨迹) |
+| 与订单关系 | 1 订单 = 1 任务(`order_id UNIQUE`) | 1 订单 = N 运单(拆包裹常见) |
+| 状态来源 | 平台自己驱动状态机(按钮/操作) | 第三方事件驱动(webhook/轮询) |
+| 数据颗粒度 | 少量节点(接单/取货/送达) | 高频节点(到站/发车/分拣/派送/签收…) |
+| 关键字段 | `driver_id`、地址 JSON、配送费、距离 | `carrier`、`tracking_no`、事件时间线、幂等去重 |
+| 风险点 | 并发抢单、任务状态与订单状态不同步 | 乱序/重复事件、验签、防重放、脱敏 |
+
+结论:
+- 旧表(`ml_delivery_*`)适合“自营骑手/同城配送”。
+- 新表(`platform_express_*`)适合“第三方快递轨迹”。
+- 两者可以共存,但不要强行把第三方轨迹塞进 `ml_delivery_tasks`。
+
+---
+
+## 4. 推荐的数据库修改方案(兼容、可渐进)
+
+### 4.1 方案 1(推荐):新增第三方快递表,不改旧配送表
+适用:你们当前要做第三方快递展示,且已明确“不做自营骑手”。
+
+做法:
+1) 在主库新增(或通过 migration 引入)三张平台侧表:
+ - `platform_express_waybills`
+ - `platform_express_tracking_events`
+ - `platform_express_event_raw`
+2) 商家发货时写入/绑定:创建 `platform_express_waybills` 行(填 `order_id`、`order_no`、`carrier`、`tracking_no`)。
+3) 第三方回调/轮询入库:写 `platform_express_tracking_events`,并更新 `platform_express_waybills.current_status_*` 与 `last_synced_at`。
+
+建议的小幅增强(强烈建议做):
+- 给 `platform_express_waybills(order_id)` 增加索引(便于按订单查运单)。
+- 让 `platform_express_waybills.order_id` 建外键到 `ml_orders(id)`(如果你们能保证订单一定存在)。
+
+### 4.2 方案 2:逐步“弃用旧配送端表”的依赖(当你们全面第三方快递时)
+适用:你们不再提供自营骑手配送,或者想把骑手端做成独立项目。
+
+做法(建议按阶段,不要硬删表):
+- 阶段 A:停止业务写入 `ml_delivery_tasks`(页面/接口不再创建任务)。
+- 阶段 B:订单详情页的物流展示只依赖 `platform_express_*`。
+- 阶段 C:将 `ml_delivery_tasks` 标为 legacy(只读保留一段时间),最终再评估是否归档/删除。
+
+为什么不建议立刻删:
+- 历史数据、结算对账、纠纷复盘可能仍需要旧数据。
+
+在“不做自营骑手”的前提下,最小要求:
+- 新增第三方快递表后,业务代码只写入/读取 `platform_express_*`,不要再创建/更新 `ml_delivery_tasks`。
+- 旧表保留只读(或仅用于历史查询/清理脚本),后续再评估归档策略。
+
+### 4.3 如果两种配送方式要并存(同城骑手 + 快递)
+建议用“履约类型”做分流,而不是混表:
+- 同城/自营:继续用 `ml_delivery_tasks`(任务流)
+- 快递:用 `platform_express_*`(运单轨迹)
+- 订单详情页按订单的履约类型选择展示模块(或同时展示多个包裹)
+
+---
+
+## 5. 迁移/改库执行清单(建议写成 migration)
+
+### 5.1 建表落库
+- 把 `express_tracking_mock_platform.sql` 的 A 部分(platform)整理为一份 migration(放到 `mall_sql/migrations/`),避免:
+ - 重复创建 `set_updated_at()`(项目若已有同名函数)
+ - 把 `mock_*` 测试表混进生产 schema
+
+### 5.2 对接 `ml_orders`
+- 建议:
+ - `platform_express_waybills.order_id REFERENCES public.ml_orders(id)`
+ - `CREATE INDEX ... ON platform_express_waybills(order_id)`
+- 不建议:
+ - 把运单号/承运方直接塞进 `ml_orders`(会导致一单多包裹很难处理)
+
+### 5.3 数据回填(可选)
+如果你们历史上已经保存过“承运方/运单号”在别处(例如订单扩展表/发货日志),可做一次性回填:
+- 按订单生成 waybill 行
+- 再按运单触发一次轨迹拉取
+
+### 5.4 权限与隐私
+- 买家/商家/客服看到的轨迹必须“同源”,但展示要按角色脱敏;敏感字段(如手机号、原始回文)按文档约束控制。
+
+---
+
+## 6. 你接下来要怎么改数据库(最短路线)
+
+如果你现在的目标是:商家可选承运方 + 录入运单号 + 用户能看轨迹时间线。
+
+最短路线:
+1) 在主库落 `platform_express_waybills` / `platform_express_tracking_events` / `platform_express_event_raw`
+2) 发货绑定时写 `platform_express_waybills(order_id, order_no, carrier, tracking_no)`
+3) 第三方事件入库写 `platform_express_tracking_events`
+4) 订单详情页按 `order_id` 查 waybills,再查 events 渲染时间线
+
+---
+
+(如你确认要我进一步把“建议的小幅增强”直接写成 SQL migration 文件,我可以在 `mall_sql/migrations/` 里新增一份只包含 platform 表的 migration,并确保不引入 `mock_*` 表。)
diff --git a/pages/mall/delivery/doc/需求文档/生产表说明_platform_express.md b/pages/mall/delivery/doc/需求文档/生产表说明_platform_express.md
new file mode 100644
index 00000000..7b2789b4
--- /dev/null
+++ b/pages/mall/delivery/doc/需求文档/生产表说明_platform_express.md
@@ -0,0 +1,148 @@
+# 生产表说明:platform_express_*(第三方快递轨迹平台侧)
+
+本文档对应生产迁移脚本:`pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql`。
+
+## 1. 设计目标与边界
+
+- 目标:为第三方承运(韵达/圆通/中通等)提供统一的“运单 + 轨迹事件 + 原始回调留痕”入库模型,支撑用户端/商家端/平台后台同源展示。
+- 边界:
+ - 仅覆盖第三方快递轨迹,不包含自营骑手/配送任务(`ml_delivery_*`)。
+ - 生产仅使用 `platform_express_*` 三表;不包含任何 `mock_*` 测试表。
+
+## 2. 表关系概览
+
+- `platform_express_waybills`(运单主表)
+ - 1 条运单对应 N 条轨迹事件(`platform_express_tracking_events`)。
+- `platform_express_tracking_events`(轨迹事件事实表)
+ - 时间线展示、状态判断、告警统计的主来源。
+ - 对同一运单使用 `(waybill_id, dedupe_key)` 做幂等去重。
+- `platform_express_event_raw`(原始接收留痕表)
+ - 记录每一次 webhook/轮询/人工补录的原始请求/响应内容与验签结果、解析错误,用于审计与排障。
+
+## 3. 字段说明
+
+### 3.1 `public.platform_express_waybills`(运单主表)
+
+用途:
+- 以 `(carrier, tracking_no)` 为唯一键管理“运单实体”,可选关联订单;承载“当前状态/预计到达/最近同步时间”等运单级摘要信息。
+
+约束/触发器/索引:
+- 唯一约束:`uk_platform_express_waybill (carrier, tracking_no)`
+- 外键:`order_id -> public.ml_orders(id)`,`ON DELETE SET NULL`
+- `updated_at` 触发器:`trigger_platform_express_waybills_updated_at`(UPDATE 时自动刷新)
+- 常用索引:
+ - `idx_platform_express_waybills_order_id (order_id)`
+ - `idx_platform_express_waybills_order_no (order_no)`
+ - `idx_platform_express_waybills_tracking_no (tracking_no)`
+ - `idx_platform_express_waybills_status (current_status_code)`
+
+字段表:
+
+| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
+|---|---|---:|---|---|
+| id | UUID | 否 | PK,`uuid_generate_v4()` | 运单主键 |
+| order_id | UUID | 是 | FK -> `ml_orders(id)`,删除置空 | 关联订单主键(推荐) |
+| order_no | VARCHAR(64) | 是 | | 订单号(展示/兜底,用于无法拿到 `order_id` 的场景) |
+| carrier | VARCHAR(32) | 否 | 参与唯一约束 | 承运方编码(如 `YUNDA`/`YTO`/`ZTO` 等;也可接入聚合方编码) |
+| tracking_no | VARCHAR(64) | 否 | 参与唯一约束 | 运单号/快递单号 |
+| source | VARCHAR(16) | 否 | 默认 `'mock'` | 运单数据来源:`mock`/`carrier`/`aggregator` 等 |
+| current_status_code | VARCHAR(32) | 否 | 默认 `'PENDING'` | 运单当前平台状态(用于列表页/摘要),通常由最新事件映射得到 |
+| current_status_text | TEXT | 是 | | 运单当前状态文本(可为最新事件文本的“清洗版”或平台自定义) |
+| eta | TIMESTAMPTZ | 是 | | 预计送达时间(可选;来自承运方/聚合方或平台预测) |
+| last_synced_at | TIMESTAMPTZ | 是 | | 最近一次与承运方/聚合方同步时间(用于健康度与补偿轮询判断) |
+| created_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 创建时间 |
+| updated_at | TIMESTAMPTZ | 否 | 默认 `NOW()`;触发器维护 | 更新时间(UPDATE 自动刷新) |
+
+
+### 3.2 `public.platform_express_tracking_events`(轨迹事件表)
+
+用途:
+- 存储每一个轨迹节点(揽收/到站/运输中/派送/签收/异常等),作为时间线展示与状态机判断的事实来源。
+- 保留第三方原文(`event_code`/`event_text`/`raw_payload`),同时写入平台统一状态(`status_code`)。
+- 通过 `dedupe_key` 实现幂等:重复回调/轮询不会重复插入。
+
+约束/索引:
+- 外键:`waybill_id -> platform_express_waybills(id)`,`ON DELETE CASCADE`
+- 唯一约束:`uk_platform_express_event_dedupe (waybill_id, dedupe_key)`
+- 常用索引:
+ - `idx_platform_express_events_waybill_time (waybill_id, event_time)`(按运单查时间线)
+ - `idx_platform_express_events_tracking_time (tracking_no, event_time)`(按运单号查)
+ - `idx_platform_express_events_status (status_code)`(按状态统计/筛选)
+ - `idx_platform_express_events_received_at (received_at)`(按接收时间排障)
+ - `gin_platform_express_events_raw_payload`(`raw_payload` JSONB GIN,便于排障检索)
+
+字段表:
+
+| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
+|---|---|---:|---|---|
+| id | UUID | 否 | PK,`uuid_generate_v4()` | 事件主键 |
+| waybill_id | UUID | 否 | FK,级联删除 | 所属运单 |
+| carrier | VARCHAR(32) | 否 | | 承运方编码(冗余字段,便于查询/对账) |
+| tracking_no | VARCHAR(64) | 否 | | 运单号(冗余字段,便于查询/对账) |
+| event_id | VARCHAR(128) | 是 | | 第三方事件唯一 ID(有些承运方会提供;优先用于幂等) |
+| event_time | TIMESTAMPTZ | 否 | | 事件发生时间(时间线排序的主依据) |
+| event_code | VARCHAR(64) | 否 | | 第三方事件码/节点码(保留原值) |
+| event_text | TEXT | 否 | | 第三方事件文本(保留原值;展示前可做脱敏/清洗) |
+| status_code | VARCHAR(32) | 否 | | 平台统一状态码(由 `event_code/event_text` 规则映射得出) |
+| node_name | VARCHAR(128) | 是 | | 节点/网点名称(如“xx转运中心”) |
+| location | TEXT | 是 | | 地理位置文本(如省市区/网点地址;不建议存精确经纬度,除非明确需求) |
+| description | TEXT | 是 | | 平台补充说明(如“疑似退回件,已通知商家”) |
+| evidence_urls | JSONB | 否 | 默认 `[]` | 证据/附件 URL 列表(签收图、面单、异常凭证等) |
+| raw_payload | JSONB | 是 | | 第三方原始事件载荷(用于审计/排障;注意权限控制) |
+| received_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 平台接收时间(用于排查延迟/乱序) |
+| source | VARCHAR(16) | 否 | 默认 `'webhook'` | 数据进入平台的方式:`webhook`/`poll`/`manual` |
+| dedupe_key | VARCHAR(256) | 否 | 参与唯一约束 | 幂等去重键(建议:有 `event_id` 用 `event_id`;否则构造 `tracking_no|event_code|event_time` 并按需要拼接 `node/location`) |
+| created_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 入库时间 |
+
+幂等与乱序建议:
+- 幂等:插入事件时以 `(waybill_id, dedupe_key)` 唯一约束兜底;遇到冲突可 `ON CONFLICT DO NOTHING`。
+- 乱序:允许旧事件晚到入库;前端展示按 `event_time` 排序。
+- 运单摘要(`platform_express_waybills.current_status_*`)建议由“最新事件(按 event_time)映射结果”更新,避免用 `received_at` 作为最新依据。
+
+
+### 3.3 `public.platform_express_event_raw`(原始接收留痕表)
+
+用途:
+- 记录每一次 webhook 回调或轮询结果的“原始内容 + 安全审计 + 解析结果”。
+- 用于:验签问题定位、第三方字段变更回溯、解析失败重放、争议审计。
+
+索引:
+- `idx_platform_express_raw_received_at (received_at)`
+- `idx_platform_express_raw_tracking_no (tracking_no)`
+- `idx_platform_express_raw_signature_valid (signature_valid)`
+- `gin_platform_express_raw_body`(`body` JSONB GIN,便于按字段排障检索)
+
+字段表:
+
+| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
+|---|---|---:|---|---|
+| id | UUID | 否 | PK,`uuid_generate_v4()` | 原始接收记录主键 |
+| received_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 平台收到请求/响应的时间 |
+| source | VARCHAR(16) | 否 | 默认 `'webhook'` | 来源:`webhook`/`poll`/`manual` |
+| client_id | VARCHAR(64) | 是 | | 调用方/渠道标识(如回调应用 ID、聚合方商户号等) |
+| carrier | VARCHAR(32) | 是 | | 承运方编码(可从路由/请求体解析;可能为空) |
+| tracking_no | VARCHAR(64) | 是 | | 运单号(可能为空:如批量回调或解析失败) |
+| signature_valid | BOOLEAN | 是 | | 验签是否通过(解析失败或未验签可为空) |
+| signature | TEXT | 是 | | 签名原文(如 header 签名) |
+| ts_header | TEXT | 是 | | 时间戳 header(用于防重放;字段名取决于对接规范) |
+| request_id | VARCHAR(64) | 是 | | 请求追踪 ID(平台生成或对方传入) |
+| remote_ip | INET | 是 | | 来源 IP(用于安全审计/黑白名单) |
+| headers | JSONB | 是 | | 请求头(建议按需过滤敏感头再落库) |
+| body | JSONB | 是 | | 原始请求体/响应体(用于回溯与重放) |
+| parse_error | TEXT | 是 | | 解析错误信息(成功解析则为空) |
+| dedupe_key | VARCHAR(256) | 是 | | 原始请求去重辅助键(可选:如 `hash(headers+body)`) |
+
+权限与合规建议(实现侧):
+- `raw_payload`、`platform_express_event_raw.body/headers` 可能包含敏感信息(手机号、地址、签名等),建议仅后台/运维具备读取权限,并记录审计日志。
+
+## 4. 常见查询与写入路径(建议)
+
+- 发货/绑定运单:
+ - Upsert `platform_express_waybills`(按 `(carrier, tracking_no)`);有订单关联时写入 `order_id/order_no`。
+- 接收回调/轮询:
+ - 先插入 `platform_express_event_raw` 做留痕(无论解析成功与否)。
+ - 解析出事件后写入 `platform_express_tracking_events`;用 `dedupe_key` 做幂等。
+ - 视需要更新 `platform_express_waybills.current_status_*`、`last_synced_at`。
+- 订单详情页展示:
+ - 通过 `order_id` 查 `platform_express_waybills`。
+ - 再按 `waybill_id` 查 `platform_express_tracking_events`,按 `event_time` 升序组装时间线。
diff --git a/pages/mall/delivery/doc/需求文档/配送模块需求文档.md b/pages/mall/delivery/doc/需求文档/配送模块需求文档.md
index a773fcfb..c3a7fe59 100644
--- a/pages/mall/delivery/doc/需求文档/配送模块需求文档.md
+++ b/pages/mall/delivery/doc/需求文档/配送模块需求文档.md
@@ -20,12 +20,18 @@
多端归属与页面边界(统一口径):
- 本需求产出的“物流能力”不是一个独立的骑手/配送员端 App,而是作为同一套物流模块能力,分别嵌入到三个端(按权限展示不同颗粒度):
- 1) 消费者端(C 端):订单详情的物流时间线/物流详情(只读为主,展示 `event_text` + `status_code`,不展示 `raw_payload`)。
- 2) 商家端(B 端):发货与运单绑定(选择承运方、录入/回填 `tracking_no`)+ 查看物流(不包含对接配置与敏感调试信息)。
+ 1) 消费者端(C 端):订单详情的物流时间线/物流详情(只读为主,展示 `status_code` + 轨迹文案;可在“派送中”等末端环节提供“联系配送”能力,但涉及手机号等敏感信息需脱敏/虚拟号策略,且仅对收件人可见;不展示 `raw_payload`)。
+ 2) 商家端(B 端):发货与运单绑定(选择承运方、录入/回填 `tracking_no`)+ 查看物流概览(以“到达某地/中转/派送/签收/异常”等大致进度为主;不展示配送员手机号等个人敏感信息;不包含对接配置与敏感调试信息)。
3) 管理端(平台后台:客服/履约/对接运维):订单/运单查询、Webhook 接收日志、接入配置、(可选)监控告警与审计能力。
- 三端展示字段口径以统一事件模型与前端字段契约为准(见 `接口规范.md`、`前端字段清单.md`),避免出现“不同端展示不一致/状态口径不一致”。
- 若未来要做“自营配送/同城骑手端”(接单、导航、到店取货、送达签收等),需单独立项与另写需求文档,不在本需求范围内。
+敏感信息展示差异(必须遵循):
+- 轨迹事件以同一份“事实数据源”入库与治理(`event_code/event_text/status_code` 等同源),但不同端的展示需按角色过滤与脱敏。
+- 配送员/快递员手机号:
+ - 商家端:不展示。
+ - 消费者端:仅在末端环节(如 `OUT_FOR_DELIVERY`)按需提供,并必须脱敏(如 138****8000)或采用虚拟号/中转号方案;避免把第三方 `event_text` 原文中的手机号明文直接透传到推送消息或页面。
+
## 2. 目标
- 适用阶段:主要用于真实第三方承运方未签约/未联调/不可控时的替代数据源;即便后续已接入真实承运方,也保留用于回归测试、故障注入与排障复现(生产环境默认关闭)。
- 提供一个可配置的 Mock 承运方服务,模拟多承运方(圆通/韵达/中通/申通)轨迹事件与签收凭证。
@@ -233,7 +239,7 @@
端侧体验要求(安卓/Web):
- 刷新策略:页面进入时拉取一次平台侧轨迹;用户可手动“刷新物流”;显示 `last_synced_at`。
- 离线降级:无网络时展示上次缓存的时间线并提示“网络不可用”。
-- 权限与隐私:手机号脱敏;`raw_payload` 仅客服/运维可见并记录审计;不展示配送员精确地址/定位。
+- 权限与隐私:手机号脱敏;配送员/快递员手机号仅对收件人可见且需脱敏/虚拟号策略(商家端不展示);`raw_payload` 仅客服/运维可见并记录审计;不展示配送员精确地址/定位。
## 9. 验收标准
- 能创建运单并运行“标准签收”场景,向平台推送至少 6 个关键节点(揽收/到达中转/在途/到达目的地/派送中/签收)。
diff --git a/pages/mall/delivery/test/api-simulator.uvue b/pages/mall/delivery/test/api-simulator.uvue
new file mode 100644
index 00000000..4a18616f
--- /dev/null
+++ b/pages/mall/delivery/test/api-simulator.uvue
@@ -0,0 +1,291 @@
+
+
+
+
+
+ 1. 选择目标订单 (已发货)
+
+
+ {{ item.order_no }}
+ {{ item.carrier }}: {{ item.tracking_no }}
+
+
+
+
+
+ 2. 构造回调数据 (JSON Payload)
+
+ 运单号:
+
+
+
+ 快递公司:
+
+
+
+ 物流状态:
+
+ {{ currentStatusLabel }}
+
+
+
+ 轨迹描述文字:
+
+
+
+
+
+ 接口发送原始数据预览:
+
+ {{ jsonString }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/consumer-logistics-detail.uvue b/pages/mall/delivery/test/consumer-logistics-detail.uvue
new file mode 100644
index 00000000..f528ec3a
--- /dev/null
+++ b/pages/mall/delivery/test/consumer-logistics-detail.uvue
@@ -0,0 +1,494 @@
+
+
+
+
+
+
+
+
+
+
+ {{ order.carrier }}快递
+ 运单号: {{ order.tracking_no }}
+
+
+
+
+
+
+
+ 物流追踪
+
+
+
+
+
+
+
+
+
+
+
+
+ 📍
+ {{ event.location }}
+
+
+
+
+
+
+
+
+
+ 暂无物流动态
+
+
+
+
+
+
+ 订单信息
+
+ 订单编号
+ {{ order.order_no }}
+
+
+ 下单时间
+ {{ order.created_at }}
+
+
+ 实付金额
+ ¥{{ order.amount || '0.00' }}
+
+
+ 收货地址
+ {{ order.address }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/consumer-order-list.uvue b/pages/mall/delivery/test/consumer-order-list.uvue
new file mode 100644
index 00000000..5f81d2eb
--- /dev/null
+++ b/pages/mall/delivery/test/consumer-order-list.uvue
@@ -0,0 +1,225 @@
+
+
+
+
+
+ 全部
+ 进行中
+ 已签收
+
+
+
+
+
+
+
+ 📦
+
+ {{ getLatestMsg(order.order_no) }}
+ 订单号: {{ order.order_no }}
+
+ >
+
+
+
+
+
+
+ 📂
+ 暂无相关包裹信息
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/index.uvue b/pages/mall/delivery/test/index.uvue
new file mode 100644
index 00000000..e6f0e124
--- /dev/null
+++ b/pages/mall/delivery/test/index.uvue
@@ -0,0 +1,122 @@
+
+
+
+
+
+ 商家后台 (Merchant)
+
+ 订单列表与发货
+ 查看待发货订单、绑定运单号
+
+
+ 订单详情与物流轨迹
+ 物流时间线展示、签收凭证预览
+
+
+
+
+ 平台后台 (Platform/Ops)
+
+ 运单/轨迹查询 (排障)
+ 按运单号查询、查看 Raw Payload、Mock 控制
+
+
+ Webhook 接收日志
+ 核对回调、验签状态、入库结果
+
+
+ 对接配置中心
+ 承运商管理、Mock 服务开关、密钥配置
+
+
+
+
+ 消费者端 (Consumer/APP)
+
+ 我的快递 (物流列表)
+ 查看多个包裹状态、实时动态摘要
+
+
+ 用户物流详情页
+ 单包裹高保真轨迹、联系派送员
+
+
+
+
+ 模拟与开发工具 (Tools)
+
+ 第三方 API 推送模拟
+ 手动构造 Webhook Payload 发送给系统
+
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/merchant-order-detail.uvue b/pages/mall/delivery/test/merchant-order-detail.uvue
new file mode 100644
index 00000000..18b1a548
--- /dev/null
+++ b/pages/mall/delivery/test/merchant-order-detail.uvue
@@ -0,0 +1,269 @@
+
+
+
+
+ {{ getStatusText(order.status) }}
+ {{ order.carrier }}: {{ order.tracking_no }}
+ 待商家发货
+
+
+
+
+
+
+ 收货人
+ {{ order.receiver_name }}
+
+
+ 手机号
+ {{ order.receiver_masked_phone }}
+
+
+ 地址
+ {{ order.address }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ seg }}
+
+ {{ event.event_time }}
+
+
+
+
+
+
+
+
+
+ 暂无物流动态
+
+
+
+
+
+
+
+
+
+ 订单号
+ {{ order.order_no }}
+
+
+ 下单时间
+ {{ order.created_at }}
+
+
+ 支付金额
+ ¥{{ order.amount }}
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/merchant-order-list.uvue b/pages/mall/delivery/test/merchant-order-list.uvue
new file mode 100644
index 00000000..932fe6f5
--- /dev/null
+++ b/pages/mall/delivery/test/merchant-order-list.uvue
@@ -0,0 +1,327 @@
+
+
+
+
+
+
+ 全部
+ 待发货
+ 已发货
+
+
+
+
+
+
+
+
+ 下单时间:
+ {{ order.created_at }}
+
+
+ 收件人:
+ {{ order.receiver_name }} {{ order.receiver_masked_phone }}
+
+
+ 运单号:
+ {{ order.tracking_no }} ({{ order.carrier }})
+
+
+
+
+
+
+
+ 暂无订单
+
+
+
+
+
+
+
+
+
+ 选择承运商
+
+ {{ currentCarrier || '请选择' }}
+
+
+
+ 运单号
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/mock-service.uts b/pages/mall/delivery/test/mock-service.uts
new file mode 100644
index 00000000..63dfd37d
--- /dev/null
+++ b/pages/mall/delivery/test/mock-service.uts
@@ -0,0 +1,496 @@
+/**
+ * 配送模块模拟数据服务 (Mock Service)
+ * 依据: pages/mall/delivery/doc/需求文档/
+ * 涵盖: 商家端订单、平台端轨迹、Webhook 日志与场景模拟
+ */
+
+export interface MockOrder {
+ order_no: string
+ status: string
+ created_at: string
+ receiver_name: string
+ receiver_masked_phone: string
+ address: string
+ amount: string
+ carrier: string
+ tracking_no: string
+ last_synced_at?: string
+}
+
+export interface MockTrackingEvent {
+ event_id: string
+ event_time: string
+ event_code: string
+ event_text: string
+ status_code: string
+ node_name?: string
+ location?: string
+ evidence_urls: string[]
+ raw_payload?: string
+}
+
+export interface MockWebhookLog {
+ time: string
+ carrier: string
+ tracking_no: string
+ event_code: string
+ success: boolean
+ result_text: string
+ payload: UTSJSONObject
+}
+
+export interface MockCarrierOption {
+ code: string
+ name: string
+}
+
+class MockService {
+ private orders: MockOrder[] = [
+ {
+ order_no: 'ORD20260205001',
+ status: 'PENDING',
+ created_at: '2026-02-05 10:00',
+ receiver_name: '张三',
+ receiver_masked_phone: '138****8000',
+ address: '北京市朝阳区某某街道100号',
+ amount: '299.00',
+ tracking_no: '',
+ carrier: ''
+ },
+ {
+ order_no: 'ORD20260205002',
+ status: 'DELIVERED',
+ created_at: '2026-02-05 09:30',
+ receiver_name: '李四',
+ receiver_masked_phone: '139****1234',
+ address: '上海市浦东新区某某路200号',
+ amount: '158.50',
+ tracking_no: 'YD987654321',
+ carrier: '韵达',
+ last_synced_at: '2026-02-05 14:35'
+ },
+ {
+ order_no: 'ORD20260205003',
+ status: 'SHIPPED',
+ created_at: '2026-02-04 15:00',
+ receiver_name: '王五',
+ receiver_masked_phone: '137****5566',
+ address: '广州市天河区某某大厦15楼',
+ amount: '88.00',
+ tracking_no: 'ZT123456789',
+ carrier: '中通',
+ last_synced_at: '2026-02-05 10:00'
+ },
+ {
+ order_no: 'ORD20260205004',
+ status: 'OUT_FOR_DELIVERY',
+ created_at: '2026-02-05 08:00',
+ receiver_name: '赵六',
+ receiver_masked_phone: '135****0011',
+ address: '杭州市西湖区某某创意园',
+ amount: '450.00',
+ tracking_no: 'SF666888999',
+ carrier: '顺丰',
+ last_synced_at: '2026-02-06 09:00'
+ },
+ {
+ order_no: 'ORD20260205005',
+ status: 'EXCEPTION',
+ created_at: '2026-02-03 12:00',
+ receiver_name: '孙七',
+ receiver_masked_phone: '136****9988',
+ address: '成都市武侯区某某软件园',
+ amount: '120.00',
+ tracking_no: 'YT555444333',
+ carrier: '圆通',
+ last_synced_at: '2026-02-04 18:00'
+ }
+ ]
+
+ // 全局 Mock 配置项,支持页面间同步
+ public isTestMode: boolean = true
+ public autoPush: boolean = true
+ public mockUrl: string = 'http://192.168.1.100:3000/mock/v1'
+
+ // 持久化存储轨迹:按订单号存储
+ private trackingHistory: Map = new Map()
+ private webhookLogs: MockWebhookLog[] = []
+
+ constructor() {
+ this.initDefaultHistory()
+ this.initDefaultLogs()
+ }
+
+ private initDefaultLogs() {
+ this.webhookLogs = [
+ {
+ time: '14:35:22', carrier: '韵达', tracking_no: 'YD987654321',
+ event_code: 'DELIVERED', success: true, result_text: '成功入库',
+ payload: {
+ trackingNo: 'YD987654321',
+ status: 'DELIVERED',
+ msg: '您的快件已由本人签收'
+ } as UTSJSONObject
+ }
+ ]
+ }
+
+ private initDefaultHistory() {
+ const tip = "【物流问题无需找商家或平台,请致电(95338)(专属热线:400-811-1111)更快解决】"
+
+ // 已签收订单轨迹
+ this.trackingHistory.set('ORD20260205002', [
+ {
+ event_id: 'e210',
+ event_time: '2026-02-05 18:30',
+ event_code: 'DELIVERED',
+ event_text: '您的快件已由本人签收。感谢使用韵达快递,期待再次为您服务!',
+ status_code: 'DELIVERED',
+ evidence_urls: ['https://img-shop.gmugmu.com/mock/pod_sample.png']
+ },
+ {
+ event_id: 'e209',
+ event_time: '2026-02-05 15:25',
+ event_code: 'OUT_FOR_DELIVERY',
+ event_text: '派送员张师傅(13800138000)正在派件(事先呼我,勿找商家)',
+ status_code: 'OUT_FOR_DELIVERY',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e208',
+ event_time: '2026-02-05 15:24',
+ event_code: 'ARRIVED_HUB',
+ event_text: '【朝阳分部】已收入',
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e207',
+ event_time: '2026-02-05 15:23',
+ event_code: 'ARRIVED',
+ event_text: '您的快件已经到达【朝阳区】' + tip,
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e200',
+ event_time: '2026-02-05 09:46',
+ event_code: 'SHIPPED',
+ event_text: '包裹正在等待揽收',
+ status_code: 'SHIPPED',
+ evidence_urls: []
+ }
+ ])
+
+ // 运输中订单轨迹
+ this.trackingHistory.set('ORD20260205003', [
+ {
+ event_id: 'e305',
+ event_time: '2026-02-05 10:00',
+ event_code: 'TRANSIT',
+ event_text: '快件离开【广州分拣中心】,已发往【天河分部】',
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e301',
+ event_time: '2026-02-04 15:30',
+ event_code: 'PICKED',
+ event_text: '包裹已揽收',
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ }
+ ])
+
+ // 派送中订单轨迹
+ this.trackingHistory.set('ORD20260205004', [
+ {
+ event_id: 'e405',
+ event_time: '2026-02-06 09:00',
+ event_code: 'OUT_FOR_DELIVERY',
+ event_text: '派送员王师傅(13700137000)正在派件',
+ status_code: 'OUT_FOR_DELIVERY',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e401',
+ event_time: '2026-02-05 18:00',
+ event_code: 'ARRIVED',
+ event_text: '快件到达【杭州西湖分拨中心】',
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ }
+ ])
+
+ // 异常订单轨迹
+ this.trackingHistory.set('ORD20260205005', [
+ {
+ event_id: 'e505',
+ event_time: '2026-02-04 18:00',
+ event_code: 'EXCEPTION',
+ event_text: '【包裹异常】由于天气原因,快件将在分拨中心稍作停留',
+ status_code: 'EXCEPTION',
+ evidence_urls: []
+ },
+ {
+ event_id: 'e501',
+ event_time: '2026-02-03 14:00',
+ event_code: 'PICKED',
+ event_text: '包裹已揽收',
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ }
+ ])
+ }
+
+ getAvailableCarriers(): MockCarrierOption[] {
+ return [
+ { code: 'YUNDA', name: '韵达' },
+ { code: 'YTO', name: '圆通' },
+ { code: 'ZTO', name: '中通' },
+ { code: 'STO', name: '申通' }
+ ]
+ }
+
+ getMockOrders(): MockOrder[] {
+ return this.orders
+ }
+
+ bindShipment(orderNo: string, carrier: string, trackingNo: string): MockOrder | null {
+ const order = this.orders.find(o => o.order_no === orderNo)
+ if (!order) return null
+
+ order.carrier = carrier
+ order.tracking_no = trackingNo
+ order.status = 'SHIPPED'
+ order.last_synced_at = this.formatDate(new Date())
+
+ // 初始化轨迹
+ this.trackingHistory.set(orderNo, [
+ {
+ event_id: 'init_' + Date.now(),
+ event_time: order.last_synced_at,
+ event_code: 'CREATED',
+ event_text: '商家已发货,等待快递公司揽收',
+ status_code: '已发货',
+ evidence_urls: []
+ }
+ ])
+ return order
+ }
+
+ getMockTracking(id: string): MockTrackingEvent[] {
+ const order = this.orders.find(o => o.order_no === id || o.tracking_no === id)
+ if (order != null && this.trackingHistory.has(order.order_no)) {
+ return this.trackingHistory.get(order.order_no)!
+ }
+ return []
+ }
+
+ /**
+ * 生成符合消费者端高保真展示的物流轨迹
+ */
+ generateFullProcess(id: string) {
+ const order = this.orders.find(o => o.order_no === id || o.tracking_no === id)
+ if (!order) return
+
+ const now = new Date()
+ const getPastTime = (days: number, hours: number): string => {
+ const d = new Date(now.getTime() - (days * 24 + hours) * 3600 * 1000)
+ const YY = d.getFullYear()
+ const M = (d.getMonth() + 1).toString().padStart(2, '0')
+ const DD = d.getDate().toString().padStart(2, '0')
+ const h = d.getHours().toString().padStart(2, '0')
+ const m = d.getMinutes().toString().padStart(2, '0')
+ return `${YY}-${M}-${DD} ${h}:${m}`
+ }
+
+ const tip = "【物流问题无需找商家或平台,请致电(95338)(专属热线:400-811-1111)更快解决】"
+
+ const fullProcess: MockTrackingEvent[] = [
+ {
+ event_id: 'f7',
+ event_time: getPastTime(0, 1),
+ event_code: 'OUT_FOR_DELIVERY',
+ event_text: '派送员张师傅(13800138000)正在派件(事先呼我,勿找商家)',
+ status_code: 'OUT_FOR_DELIVERY',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f6',
+ event_time: getPastTime(0, 4),
+ event_code: 'TRANSIT',
+ event_text: '【朝阳分部】已收入',
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f5',
+ event_time: getPastTime(0, 12),
+ event_code: 'ARRIVED',
+ event_text: '您的快件已经到达【北京朝阳区】' + tip,
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f4',
+ event_time: getPastTime(1, 2),
+ event_code: 'DEPARTED',
+ event_text: '您的快件离开【顺义转运中心】,已发往【北京朝阳区】',
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f3',
+ event_time: getPastTime(1, 10),
+ event_code: 'ARRIVED_HUB',
+ event_text: '您的快件已经到达【顺义转运中心】' + tip,
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f2',
+ event_time: getPastTime(1, 20),
+ event_code: 'PICKED',
+ event_text: '您的快件在【北京海淀区】已揽收,揽收人:李师傅(13911112222)' + tip,
+ status_code: 'ARRIVED_HUB',
+ evidence_urls: []
+ },
+ {
+ event_id: 'f1',
+ event_time: getPastTime(2, 1),
+ event_code: 'SHIPPED',
+ event_text: '包裹正在等待揽收',
+ status_code: 'SHIPPED',
+ evidence_urls: []
+ }
+ ]
+
+ this.trackingHistory.set(order.order_no, fullProcess)
+ order.status = 'OUT_FOR_DELIVERY'
+ order.last_synced_at = this.formatDate(now)
+ }
+
+ runScenario(waybillNo: string, scenario: string) {
+ // 兼容逻辑:优先按运单号搜,搜不到按订单号搜
+ let order = this.orders.find(o => o.tracking_no === waybillNo)
+ if (!order) {
+ order = this.orders.find(o => o.order_no === waybillNo)
+ }
+ if (!order) return
+
+ if (scenario === 'full') {
+ this.generateFullProcess(order.order_no)
+ } else if (scenario === 'exception') {
+ const history = this.getMockTracking(order.order_no)
+ history.unshift({
+ event_id: 'ex_' + Date.now(),
+ event_time: this.formatDate(new Date()),
+ event_code: 'EXCEPTION',
+ event_text: '【包裹异常】由于天气原因,快件将在分拨中心稍作停留',
+ status_code: 'EXCEPTION',
+ evidence_urls: []
+ })
+ order.status = 'EXCEPTION'
+ } else {
+ // 默认:模拟一个新的在途节点
+ let history = this.getMockTracking(order.order_no)
+
+ // 如果该订单还没轨迹(比如刚发货),先初始化数组
+ if (!this.trackingHistory.has(order.order_no)) {
+ this.trackingHistory.set(order.order_no, [])
+ history = this.trackingHistory.get(order.order_no)!
+ }
+
+ const now = new Date()
+ history.unshift({
+ event_id: 'st_' + Date.now(),
+ event_time: this.formatDate(now),
+ event_code: 'TRANS_UPDATE',
+ event_text: '快件已到达新的中转场进行分拣,准备发往目的地',
+ status_code: 'IN_TRANSIT',
+ evidence_urls: []
+ })
+ order.last_synced_at = this.formatDate(now)
+ }
+ }
+
+ /**
+ * 模拟从云端同步最新轨迹(使刷新按钮生效)
+ */
+ async syncFromCloud(orderNo: string): Promise {
+ const order = this.orders.find(o => o.order_no === orderNo)
+ if (!order || order.status === 'DELIVERED') return false
+
+ // 模拟网络延迟
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ this.runScenario(order.tracking_no, 'step')
+ resolve(true)
+ }, 800)
+ })
+ }
+
+ getMockWebhookLogs(): MockWebhookLog[] {
+ return this.webhookLogs
+ }
+
+ /**
+ * 核心功能:模拟第三方回调接口
+ * 模拟外部物流平台向本系统发送轨迹更新 API
+ */
+ pushWebhookData(payload: UTSJSONObject): boolean {
+ const tracking_no = payload['tracking_no'] as string
+ const status_code = payload['status_code'] as string
+ const event_text = payload['event_text'] as string
+ const carrier = payload['carrier'] as string || '顺丰'
+
+ // 1. 记录原始日志
+ const now = new Date()
+ const log: MockWebhookLog = {
+ time: this.formatDate(now).split(' ')[1],
+ carrier: carrier,
+ tracking_no: tracking_no,
+ event_code: status_code,
+ success: true,
+ result_text: '接收成功',
+ payload: payload
+ }
+ this.webhookLogs.unshift(log)
+
+ // 2. 更新系统内部轨迹
+ const order = this.orders.find(o => o.tracking_no === tracking_no)
+ if (order != null) {
+ if (!this.trackingHistory.has(order.order_no)) {
+ this.trackingHistory.set(order.order_no, [])
+ }
+ const history = this.trackingHistory.get(order.order_no)!
+ history.unshift({
+ event_id: 'wb_' + Date.now(),
+ event_time: this.formatDate(now),
+ event_code: status_code,
+ event_text: event_text,
+ status_code: status_code,
+ evidence_urls: []
+ })
+ order.status = status_code
+ order.last_synced_at = this.formatDate(now)
+ return true
+ }
+
+ log.success = false
+ log.result_text = '未找到对应的运单号'
+ return false
+ }
+
+ private formatDate(date: Date): string {
+ const Y = date.getFullYear()
+ const M = (date.getMonth() + 1).toString().padStart(2, '0')
+ const D = date.getDate().toString().padStart(2, '0')
+ const h = date.getHours().toString().padStart(2, '0')
+ const m = date.getMinutes().toString().padStart(2, '0')
+ return `${Y}-${M}-${D} ${h}:${m}`
+ }
+}
+
+export const mockService = new MockService()
diff --git a/pages/mall/delivery/test/platform-config-center.uvue b/pages/mall/delivery/test/platform-config-center.uvue
new file mode 100644
index 00000000..20da4cfb
--- /dev/null
+++ b/pages/mall/delivery/test/platform-config-center.uvue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+ {{ isTestMode ? '测试 (MOCK 模式开启)' : '生产 (已连接真实承运商)' }}
+
+
+
+
+
+ MOCK 服务配置
+
+ Server 地址
+
+
+
+ Webhook 接收密钥 (Secret)
+
+
+
+ 自动推送场景
+
+
+
+
+
+
+
+
+
+
+
+ {{ carrier.name }} ({{ carrier.code }})
+ 回调: {{ carrier.callback_status ? '在线' : '离线' }} | 轮询: {{ carrier.polling ? '开' : '关' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/platform-tracking-query.uvue b/pages/mall/delivery/test/platform-tracking-query.uvue
new file mode 100644
index 00000000..a2d32949
--- /dev/null
+++ b/pages/mall/delivery/test/platform-tracking-query.uvue
@@ -0,0 +1,278 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 基本信息
+
+
+ 运单号:
+ {{ waybillInfo.tracking_no }}
+
+
+ 承运商:
+ {{ waybillInfo.carrier }}
+
+
+ 订单号:
+ {{ waybillInfo.order_no }}
+
+
+ 当前状态:
+ {{ waybillInfo.status }}
+
+
+
+
+
+
+ 轨迹列表 (内网入库)
+
+
+ {{ formatTime(event.event_time) }}
+
+
+ {{ event.event_text }}
+
+ CODE: {{ event.event_code }}
+ MAP: {{ event.status_code }}
+
+
+ {{ JSON.stringify(event.raw_payload, null, 2) }}
+
+ {{ showRaw[index] ? '折叠原始报文' : '查看原始报文' }}
+
+
+
+
+
+
+ Mock 控制台 (测试辅助)
+
+
+
+
+
+
+
+
+
+ 未找到相关运单记录
+
+
+
+
+
+
+
diff --git a/pages/mall/delivery/test/platform-webhook-logs.uvue b/pages/mall/delivery/test/platform-webhook-logs.uvue
new file mode 100644
index 00000000..f20e3f4b
--- /dev/null
+++ b/pages/mall/delivery/test/platform-webhook-logs.uvue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+ 99.8%
+ 成功率
+
+
+ 24
+ 验签失败
+
+
+ 1.2s
+ 平均延迟
+
+
+
+
+
+
+
+
+
+ 单号:
+ {{ log.tracking_no }}
+
+
+ 事件:
+ {{ log.event_code }}
+
+
+ 状态:
+ {{ log.result_text }}
+
+
+
+
+ 查看 Payload
+ 尝试重新入库
+
+
+
+
+
+
+
+
+
+
+ {{ currentPayload }}
+
+
+
+
+
+
+
+
+
+