Files
medical-mall/pages/mall/delivery/test/mock-service.uts
not-like-juvenile a5e7afacec 创建数据库表格
2026-02-06 16:56:24 +08:00

507 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 配送模块模拟数据服务 (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<string, MockTrackingEvent[]> = 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<boolean> {
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
}
/**
* 核心功能:模拟第三方回调接口 (适配圆通推送协议)
* 对应字段: txLogisticId(订单号), mailNo(票号), infoContent(状态), remark(描述)
*/
pushWebhookData(payload: UTSJSONObject): boolean {
// 兼容圆通协议字段
const tracking_no = (payload['mailNo'] != null) ? payload['mailNo'] as string : (payload['tracking_no'] as string)
const yto_status = (payload['infoContent'] != null) ? payload['infoContent'] as string : (payload['status_code'] as string)
const event_text = (payload['remark'] != null) ? payload['remark'] as string : (payload['event_text'] as string)
const order_id = payload['txLogisticId'] as string || ''
const carrier = payload['carrier'] as string || '圆通速递'
// 状态映射:圆通状态 -> 本系统状态
let status_code = 'IN_TRANSIT'
if (yto_status === 'GOT' || yto_status === 'SEND') status_code = 'IN_TRANSIT'
else if (yto_status === 'SENT') status_code = 'OUT_FOR_DELIVERY'
else if (yto_status === 'SIGNED') status_code = 'DELIVERED'
else if (yto_status === 'FAILED') status_code = 'EXCEPTION'
else status_code = yto_status
// 1. 记录原始日志
const now = new Date()
const log: MockWebhookLog = {
time: this.formatDate(now).split(' ')[1],
carrier: carrier,
tracking_no: tracking_no,
event_code: yto_status, // 保留圆通原始代码
success: true,
result_text: '接收成功',
payload: payload
}
this.webhookLogs.unshift(log)
// 2. 更新系统内部轨迹
const order = this.orders.find(o => o.tracking_no === tracking_no || o.order_no === order_id)
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: 'yto_' + Date.now(),
event_time: payload['acceptTime'] as string || this.formatDate(now),
event_code: yto_status,
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()