From ac762ed302af18f528febe85221b64688901836c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 6 Aug 2026 20:14:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/first-leg/api/firstLeg.ts | 5 + .../first-leg/store/firstLegOrders.test.mjs | 17 ++- src/modules/first-leg/store/firstLegOrders.ts | 16 ++- src/modules/first-leg/types/firstLegOrder.ts | 5 + .../views/FirstLegOrdersView.test.mjs | 13 +++ .../first-leg/views/FirstLegOrdersView.vue | 103 +++++++++++++++--- 6 files changed, 136 insertions(+), 23 deletions(-) diff --git a/src/modules/first-leg/api/firstLeg.ts b/src/modules/first-leg/api/firstLeg.ts index 9c350d3..b4ca866 100644 --- a/src/modules/first-leg/api/firstLeg.ts +++ b/src/modules/first-leg/api/firstLeg.ts @@ -140,8 +140,11 @@ export interface MerchantFirstLegOrder { receivingWarehouseId: number | string receivingWarehouseName: string receivingWarehouseAddress?: string | null + receivingTime?: string | null channelCode: string channelName: string + carrierId?: number | string | null + carrierName?: string | null taxMode: ApiTaxMode containerMode: ApiContainerMode | null customsInspection: boolean @@ -151,6 +154,8 @@ export interface MerchantFirstLegOrder { referenceId: string | null expressCompany?: string | null expressTrackingNo?: string | null + submittedAt?: string | null + completedAt?: string | null version: number createdAt: string updatedAt: string diff --git a/src/modules/first-leg/store/firstLegOrders.test.mjs b/src/modules/first-leg/store/firstLegOrders.test.mjs index 571b207..9c315c8 100644 --- a/src/modules/first-leg/store/firstLegOrders.test.mjs +++ b/src/modules/first-leg/store/firstLegOrders.test.mjs @@ -118,7 +118,7 @@ test('does not expose the contradictory backend rejected state in the client mod }) test('maps the final backend timeline and keeps missing custom nodes empty', () => { - assert.match(storeSource, /detail\.timeline\.flatMap/) + assert.match(storeSource, /\(detail\.timeline \?\? \[\]\)\.flatMap/) assert.match(storeSource, /item\.mainStatus/) assert.match(storeSource, /\(detail\.customNodes \?\? \[\]\)/) assert.match(storeSource, /\.filter\(\(item\) => item\.customerVisible\)/) @@ -127,6 +127,21 @@ test('maps the final backend timeline and keeps missing custom nodes empty', () assert.match(storeSource, /\.sort\(\(left, right\)/) }) +test('keeps unified detail mapping recoverable for historical partial responses', () => { + assert.match(storeSource, /\(detail\.parties \?\? \[\]\)\.find/) + assert.match(storeSource, /\(detail\.statusHistory \?\? \[\]\)\.map/) + assert.match(storeSource, /item\.attachmentIds\?\.length \?\? 0/) + assert.match(storeSource, /if \(!detail\?\.summary \|\| !detail\.order\?\.order\) throw new Error\('订单详情数据不完整,请刷新列表后重试'\)/) +}) + +test('maps customer-visible carrier and order lifecycle snapshots into detail', () => { + assert.match(apiSource, /export interface MerchantFirstLegOrder[\s\S]*carrierId\?: number \| string \| null/) + assert.match(storeSource, /carrierName: order\.carrierName \?\? ''/) + assert.match(storeSource, /receivingTime: order\.receivingTime \?\? ''/) + assert.match(storeSource, /submittedAt: order\.submittedAt \?\? ''/) + assert.match(storeSource, /completedAt: order\.completedAt \?\? ''/) +}) + test('maps backend warehouse snapshots and fulfillment courier fields', () => { assert.match(storeSource, /order\.receivingWarehouseAddress/) assert.match(storeSource, /order\.expressCompany \?\? ''/) diff --git a/src/modules/first-leg/store/firstLegOrders.ts b/src/modules/first-leg/store/firstLegOrders.ts index ef62edc..c44e3a0 100644 --- a/src/modules/first-leg/store/firstLegOrders.ts +++ b/src/modules/first-leg/store/firstLegOrders.ts @@ -153,7 +153,7 @@ function orderWarehouseSnapshot(order: MerchantFirstLegOrder, warehouse?: Platfo } function partySnapshot(detail: MerchantFirstLegOrderDetail, type: 'SENDER' | 'RECEIVER'): FirstLegPartySnapshot | null { - const party = detail.parties.find((item) => item.partyType === type) + const party = (detail.parties ?? []).find((item) => item.partyType === type) if (!party) return null return { id: String(party.id), code: type, name: party.companyName, role: type === 'SENDER' ? 'SHIPPER' : 'CONSIGNEE', @@ -163,7 +163,7 @@ function partySnapshot(detail: MerchantFirstLegOrderDetail, type: 'SENDER' | 'RE } function timeline(detail: MerchantFirstLegOrderDetail): FirstLegOrderTimelineItem[] { - return detail.statusHistory.map((item) => { + return (detail.statusHistory ?? []).map((item) => { const status = clientStatus(item.toStatus) return { id: String(item.id), @@ -222,7 +222,7 @@ const unifiedActionLabels: Readonly> = { } function unifiedTimeline(detail: MerchantUnifiedOrderDetail): FirstLegOrderTimelineItem[] { - const statusItems: FirstLegOrderTimelineItem[] = detail.timeline.flatMap((item, index) => item.mainStatus ? [{ + const statusItems: FirstLegOrderTimelineItem[] = (detail.timeline ?? []).flatMap((item, index) => item.mainStatus ? [{ id: `status-${item.source}-${item.eventType}-${index}`, kind: 'MAIN_STATUS' as const, status: item.mainStatus, @@ -248,7 +248,7 @@ function unifiedTimeline(detail: MerchantUnifiedOrderDetail): FirstLegOrderTimel occurredAt: item.completedAt ?? item.plannedAt ?? '', plannedAt: item.plannedAt ?? '', completed: Boolean(item.completedAt), - attachmentCount: item.attachmentIds.length, + attachmentCount: item.attachmentIds?.length ?? 0, })) return [...statusItems, ...customItems].sort((left, right) => { const timeDifference = Date.parse(left.occurredAt) - Date.parse(right.occurredAt) @@ -280,8 +280,10 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar tradeTerm: order.tradeTerm ?? '', destinationCountryCode: order.destinationCountryCode, destinationWarehouseId: order.destinationWarehouseId ? String(order.destinationWarehouseId) : '', destinationWarehouseName: order.destinationWarehouseName, pickupMethod: order.pickupMethod, channelCode: order.channelCode, - channelName: order.channelName, taxMode: order.taxMode, customsInspection: order.customsInspection, + channelName: order.channelName, carrierId: order.carrierId == null ? '' : String(order.carrierId), carrierName: order.carrierName ?? '', + taxMode: order.taxMode, customsInspection: order.customsInspection, destinationType: order.destinationType, amazonWarehouseCode: order.amazonWarehouseCode ?? '', shipmentId: order.shipmentId ?? '', + receivingTime: order.receivingTime ?? '', submittedAt: order.submittedAt ?? '', completedAt: order.completedAt ?? '', } } @@ -299,12 +301,14 @@ function mapUnifiedOrderRow(summary: MerchantUnifiedOrderRow, warehouses: Platfo createdAt: summary.createdAt, updatedAt: summary.updatedAt, tradeTerm: '', destinationCountryCode: '', destinationWarehouseId: summary.destinationWarehouseId == null ? '' : String(summary.destinationWarehouseId), destinationWarehouseName: summary.destinationWarehouseName ?? '', pickupMethod: 'SELF_EXPRESS', channelCode: '', - channelName: summary.channelName ?? '', taxMode: 'BUY_DECLARATION', customsInspection: false, + channelName: summary.channelName ?? '', carrierId: '', carrierName: '', taxMode: 'BUY_DECLARATION', customsInspection: false, destinationType: 'PRIVATE', amazonWarehouseCode: '', shipmentId: '', + receivingTime: '', submittedAt: '', completedAt: '', } } function mapUnifiedOrderDetail(detail: MerchantUnifiedOrderDetail, warehouses: PlatformReceivingWarehouse[]): FirstLegOrder { + if (!detail?.summary || !detail.order?.order) throw new Error('订单详情数据不完整,请刷新列表后重试') const mapped = mapOrder(detail.order.order, warehouses, detail.order) mapped.id = String(detail.summary.orderId) mapped.status = detail.summary.mainStatus diff --git a/src/modules/first-leg/types/firstLegOrder.ts b/src/modules/first-leg/types/firstLegOrder.ts index 0e40a7d..590f65c 100644 --- a/src/modules/first-leg/types/firstLegOrder.ts +++ b/src/modules/first-leg/types/firstLegOrder.ts @@ -109,11 +109,16 @@ export interface FirstLegOrder { pickupMethod: 'SELF_EXPRESS' | 'PICKUP' | 'SELF_TRUCK' channelCode: string channelName: string + carrierId: string + carrierName: string taxMode: 'BUY_DECLARATION' | 'TAX_REFUND' customsInspection: boolean destinationType: 'FBA' | 'OVERSEAS' | 'PRIVATE' amazonWarehouseCode: string shipmentId: string + receivingTime: string + submittedAt: string + completedAt: string } export type FirstLegOrderDraft = Omit { assert.doesNotMatch(source, /v-model="form\.trackingNumber"/) }) +test('shows the full customer-safe order detail without platform-only controls', () => { + const detailSource = source.slice(source.indexOf('', source.indexOf('${heading}<\\/h3>`)) + } + for (const field of ['贸易条款', '发货方式', '报关方式', '运输渠道', '承运商', '收货要求', '目的仓类型', '海关查验', '提交时间', '完成时间', 'SKU / UPC', 'HS 编码', '申报价值', '危险品']) { + assert.match(source, new RegExp(field)) + } + assert.match(source, /detailOrder\.carrierName \|\| '待平台指派'/) + assert.match(source, /detailOrder\.customerRemark/) + assert.doesNotMatch(detailSource, /内部备注|审核通过|添加备注/) +}) + test('loads merchant inbound billing as an isolated read-only detail section', () => { assert.match(source, /import InboundBillingPanel/) assert.match(source, /import \{ getMerchantInboundBilling \}/) diff --git a/src/modules/first-leg/views/FirstLegOrdersView.vue b/src/modules/first-leg/views/FirstLegOrdersView.vue index ca8973d..92d7ede 100644 --- a/src/modules/first-leg/views/FirstLegOrdersView.vue +++ b/src/modules/first-leg/views/FirstLegOrdersView.vue @@ -186,6 +186,32 @@ function formatDateTime(value: string) { }) } +const pickupMethodLabels: Record = { + SELF_EXPRESS: '自行快递到仓', + PICKUP: '平台上门提货', + SELF_TRUCK: '自行送货到仓', +} +const destinationTypeLabels: Record = { + FBA: 'FBA 仓', + OVERSEAS: '海外仓', + PRIVATE: '私人地址', +} +const taxModeLabels: Record = { + BUY_DECLARATION: '买单报关', + TAX_REFUND: '退税报关', +} +const packageTypeLabels: Readonly> = { + CARTON: '纸箱', + PALLET: '托盘', + WOODEN_CASE: '木箱', + BAG: '袋装', + OTHER: '其他', +} + +function displayValue(value: string | null | undefined) { + return value?.trim() || '-' +} + function emptyDraft(): FirstLegOrderDraft { return { referenceNo: '', @@ -967,7 +993,7 @@ async function searchOrders(model: QueryModel) { - +