This commit is contained in:
2026-08-06 20:14:54 +08:00
parent 20f8a436e0
commit ac762ed302
6 changed files with 136 additions and 23 deletions
+5
View File
@@ -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
@@ -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 \?\? ''/)
+10 -6
View File
@@ -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<Record<string, string>> = {
}
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
@@ -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<Pick<
@@ -243,6 +243,19 @@ test('shows fulfillment courier information as read-only client detail', () => {
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('<el-drawer v-model="detailOpen"'), source.indexOf('</el-drawer>', source.indexOf('<el-drawer v-model="detailOpen"')))
for (const heading of ['订单与运输', '收发货信息', '关键业务摘要', '履约快递信息', '货物明细']) {
assert.match(source, new RegExp(`<h3>${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 \}/)
@@ -186,6 +186,32 @@ function formatDateTime(value: string) {
})
}
const pickupMethodLabels: Record<FirstLegOrder['pickupMethod'], string> = {
SELF_EXPRESS: '自行快递到仓',
PICKUP: '平台上门提货',
SELF_TRUCK: '自行送货到仓',
}
const destinationTypeLabels: Record<FirstLegOrder['destinationType'], string> = {
FBA: 'FBA 仓',
OVERSEAS: '海外仓',
PRIVATE: '私人地址',
}
const taxModeLabels: Record<FirstLegOrder['taxMode'], string> = {
BUY_DECLARATION: '买单报关',
TAX_REFUND: '退税报关',
}
const packageTypeLabels: Readonly<Record<string, string>> = {
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) {
<template #footer><div class="editor-actions"><el-button :disabled="saving" @click="closeEditor">返回</el-button><el-button v-if="editingId ? canEditOrder : canCreateOrder" :loading="saving" @click="saveDraft">保存草稿</el-button><el-button v-if="auth.hasPermission('order:first-leg:submit')" type="primary" :loading="saving" @click="submitOrder"><Send :size="16" />{{ companySubmissionBlocked ? '提交前需完成资料审核' : '提交订单' }}</el-button></div></template>
</FirstLegOrderEditorShell>
<el-drawer v-model="detailOpen" class="first-leg-detail" title="订单详情" size="680px">
<el-drawer v-model="detailOpen" class="first-leg-detail" title="订单详情" size="820px">
<div v-loading="detailLoading" class="order-detail-content">
<div v-if="detailError" class="order-load-state is-compact" role="alert">
<AlertTriangle :size="24" />
@@ -976,17 +1002,40 @@ async function searchOrders(model: QueryModel) {
</div>
<template v-else-if="detailOrder">
<div class="order-detail-heading">
<div><span>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }}</span><h2>{{ detailOrder.orderNo || '未提交草稿' }}</h2><p>{{ detailOrder.origin }} → {{ detailOrder.destination }}</p></div>
<div><span>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }}</span><h2>{{ detailOrder.orderNo || '未提交草稿' }}</h2><p>{{ detailOrder.origin || '起运地待更新' }} → {{ detailOrder.destination || '目的地待更新' }}</p></div>
<el-tag :type="getFirstLegStatusTone(detailOrder.status)" effect="light">{{ getFirstLegStatusLabel(detailOrder.status) }}</el-tag>
</div>
<OrderProgressPanel :order="detailOrder" />
<dl class="order-detail-grid">
<div><dt>客户参考号</dt><dd>{{ detailOrder.referenceNo || '-' }}</dd></div><div><dt>运输方式</dt><dd>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }}</dd></div>
<div><dt>发货方</dt><dd>{{ detailOrder.shipperPartySnapshot?.name || detailOrder.shipper }}<span v-if="detailOrder.shipperPartySnapshot" class="detail-subline">{{ detailOrder.shipperPartySnapshot.code }} · {{ detailOrder.shipperPartySnapshot.contactName || '未填写联系人' }} · {{ detailOrder.shipperPartySnapshot.phone || '未填写电话' }}<br />{{ detailOrder.shipperPartySnapshot.countryRegion }} {{ detailOrder.shipperPartySnapshot.province }} {{ detailOrder.shipperPartySnapshot.city }} {{ detailOrder.shipperPartySnapshot.address }}</span></dd></div>
<div><dt>收货方</dt><dd>{{ detailOrder.consigneePartySnapshot?.name || detailOrder.consignee }}<span v-if="detailOrder.consigneePartySnapshot" class="detail-subline">{{ detailOrder.consigneePartySnapshot.code }} · {{ detailOrder.consigneePartySnapshot.contactName || '未填写联系人' }} · {{ detailOrder.consigneePartySnapshot.phone || '未填写电话' }}<br />{{ detailOrder.consigneePartySnapshot.countryRegion }} {{ detailOrder.consigneePartySnapshot.province }} {{ detailOrder.consigneePartySnapshot.city }} {{ detailOrder.consigneePartySnapshot.address }}</span></dd></div>
<div><dt>平台收货仓</dt><dd>{{ detailOrder.receivingWarehouseSnapshot?.name || detailOrder.receivingWarehouseName }}<span v-if="detailOrder.receivingWarehouseSnapshot" class="detail-subline">{{ detailOrder.receivingWarehouseSnapshot.code }} · {{ detailOrder.receivingWarehouseSnapshot.province }}{{ detailOrder.receivingWarehouseSnapshot.city }}{{ detailOrder.receivingWarehouseSnapshot.address }}</span></dd></div><div><dt>负责人</dt><dd>{{ detailOrder.operatorName || '未提供' }}</dd></div>
<div><dt>期望出发</dt><dd>{{ detailOrder.expectedDepartureDate || '-' }}</dd></div><div><dt>ETD / ETA</dt><dd>{{ detailOrder.etd || '-' }} / {{ detailOrder.eta || '-' }}</dd></div>
</dl>
<section class="detail-section">
<h3>订单与运输</h3>
<dl class="order-detail-grid">
<div><dt>客户参考号</dt><dd>{{ displayValue(detailOrder.referenceNo) }}</dd></div>
<div><dt>创建时间</dt><dd>{{ formatDateTime(detailOrder.createdAt) }}</dd></div>
<div><dt>运输方式</dt><dd>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }}</dd></div>
<div><dt>贸易条款</dt><dd>{{ displayValue(detailOrder.tradeTerm) }}</dd></div>
<div><dt>发货方式</dt><dd>{{ pickupMethodLabels[detailOrder.pickupMethod] }}</dd></div>
<div><dt>报关方式</dt><dd>{{ taxModeLabels[detailOrder.taxMode] }}</dd></div>
<div><dt>运输渠道</dt><dd>{{ detailOrder.channelName || detailOrder.channelCode || '-' }}<span v-if="detailOrder.channelName && detailOrder.channelCode" class="detail-subline">{{ detailOrder.channelCode }}</span></dd></div>
<div><dt>承运商</dt><dd>{{ detailOrder.carrierName || '待平台指派' }}</dd></div>
<div><dt>国内收货仓</dt><dd>{{ detailOrder.receivingWarehouseSnapshot?.name || detailOrder.receivingWarehouseName || '-' }}<span v-if="detailOrder.receivingWarehouseSnapshot" class="detail-subline">{{ detailOrder.receivingWarehouseSnapshot.code || '仓库编码待更新' }} · {{ detailOrder.receivingWarehouseSnapshot.province }}{{ detailOrder.receivingWarehouseSnapshot.city }}{{ detailOrder.receivingWarehouseSnapshot.address }}</span></dd></div>
<div><dt>收货要求</dt><dd>{{ displayValue(detailOrder.receivingTime) }}</dd></div>
<div><dt>目的仓类型</dt><dd>{{ destinationTypeLabels[detailOrder.destinationType] }}</dd></div>
<div><dt>目的国家 / 仓库</dt><dd>{{ detailOrder.destinationCountryCode || '-' }} · {{ detailOrder.destinationWarehouseName || '-' }}</dd></div>
<div v-if="detailOrder.destinationType === 'FBA'"><dt>Amazon 仓库代码</dt><dd>{{ displayValue(detailOrder.amazonWarehouseCode) }}</dd></div>
<div v-if="detailOrder.destinationType === 'FBA'"><dt>Shipment ID</dt><dd>{{ displayValue(detailOrder.shipmentId) }}</dd></div>
<div><dt>期望出运</dt><dd>{{ displayValue(detailOrder.expectedDepartureDate) }}</dd></div>
<div><dt>海关查验</dt><dd>{{ detailOrder.customsInspection ? '需要' : '不需要' }}</dd></div>
<div><dt>提交时间</dt><dd>{{ detailOrder.submittedAt ? formatDateTime(detailOrder.submittedAt) : '-' }}</dd></div>
<div><dt>完成时间</dt><dd>{{ detailOrder.completedAt ? formatDateTime(detailOrder.completedAt) : '-' }}</dd></div>
</dl>
</section>
<section class="detail-section">
<h3>收发货信息</h3>
<div class="party-detail-grid">
<article><span>发货方</span><strong>{{ detailOrder.shipperPartySnapshot?.name || detailOrder.shipper || '未提供' }}</strong><p v-if="detailOrder.shipperPartySnapshot">{{ detailOrder.shipperPartySnapshot.contactName || '未填写联系人' }} · {{ detailOrder.shipperPartySnapshot.phone || '未填写电话' }}<br />{{ detailOrder.shipperPartySnapshot.email || '' }}<br />{{ detailOrder.shipperPartySnapshot.countryRegion }} {{ detailOrder.shipperPartySnapshot.province }} {{ detailOrder.shipperPartySnapshot.city }} {{ detailOrder.shipperPartySnapshot.address }} {{ detailOrder.shipperPartySnapshot.postalCode }}</p></article>
<article><span>收货方</span><strong>{{ detailOrder.consigneePartySnapshot?.name || detailOrder.consignee || detailOrder.destinationWarehouseName || '未提供' }}</strong><p v-if="detailOrder.consigneePartySnapshot">{{ detailOrder.consigneePartySnapshot.contactName || '未填写联系人' }} · {{ detailOrder.consigneePartySnapshot.phone || '未填写电话' }}<br />{{ detailOrder.consigneePartySnapshot.email || '' }}<br />{{ detailOrder.consigneePartySnapshot.countryRegion }} {{ detailOrder.consigneePartySnapshot.province }} {{ detailOrder.consigneePartySnapshot.city }} {{ detailOrder.consigneePartySnapshot.address }} {{ detailOrder.consigneePartySnapshot.postalCode }}</p><p v-else>平台目的仓地址以最终履约安排为准</p></article>
</div>
<div v-if="detailOrder.customerRemark" class="customer-remark"><strong>客户备注</strong><p>{{ detailOrder.customerRemark }}</p></div>
</section>
<section v-if="detailOrder.status !== 'DRAFT'" class="detail-section">
<h3>关键业务摘要</h3>
<dl class="fulfillment-summary">
@@ -998,7 +1047,8 @@ async function searchOrders(model: QueryModel) {
<div v-if="detailOrder.expressCompanyName || detailOrder.trackingNumber" class="express-information"><PackageCheck :size="20" /><div><span>快递公司</span><strong>{{ detailOrder.expressCompanyName || '待平台补充' }}</strong></div><div><span>快递单号</span><strong>{{ detailOrder.trackingNumber || '待平台补充' }}</strong></div></div>
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>平台尚未维护快递信息</strong><span>快递公司和快递单号由平台后台维护,客户端仅只读展示。</span></div></div>
</section>
<section class="detail-section"><h3>货物汇总</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><ul class="cargo-detail-list"><li v-for="item in detailOrder.items" :key="item.id"><strong>{{ item.cargoNameCn }}</strong><span>{{ item.sku || '无 SKU' }} · {{ item.pieces }} 件 · {{ item.grossWeightKg }} kg</span></li></ul></section>
<section class="detail-section"><h3>货物明细</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><div v-if="detailOrder.items.length" class="cargo-detail-list"><article v-for="(item, index) in detailOrder.items" :key="item.id"><header><span>货物 {{ index + 1 }}</span><strong>{{ item.cargoNameCn || '未命名货物' }}</strong><small>{{ item.cargoNameEn || '' }}</small></header><dl><div><dt>SKU / UPC</dt><dd>{{ item.sku || '-' }} / {{ item.upc || '-' }}</dd></div><div><dt>HS 编码</dt><dd>{{ item.hsCode || '-' }}</dd></div><div><dt>包装 / 箱数</dt><dd>{{ packageTypeLabels[item.packageType] || item.packageType || '-' }} · {{ item.packageCount }} 箱</dd></div><div><dt>数量</dt><dd>{{ item.pieces }} 件</dd></div><div><dt>毛重</dt><dd>{{ item.grossWeightKg }} kg</dd></div><div><dt>体积</dt><dd>{{ item.volumeCbm }} m³</dd></div><div><dt>申报价值</dt><dd>{{ item.declaredValue ? `${item.currency} ${item.declaredValue.toLocaleString()}` : '-' }}</dd></div><div><dt>危险品</dt><dd>{{ item.dangerousGoods ? '是' : '否' }}</dd></div></dl><p v-if="item.remark">{{ item.remark }}</p></article></div><el-empty v-else description="暂无货物明细" :image-size="56" /></section>
<OrderProgressPanel :order="detailOrder" />
<InboundBillingPanel
:data="inboundBilling"
:loading="inboundBillingLoading"
@@ -1115,6 +1165,13 @@ async function searchOrders(model: QueryModel) {
.detail-subline { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; font-weight: 400; line-height: 1.5; }
.detail-section { margin-top: 22px; }
.detail-section h3 { margin: 0 0 12px; font-size: 15px; }
.party-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.party-detail-grid article { min-width: 0; padding: 15px; border: 1px solid var(--border); background: #fbfcfc; }
.party-detail-grid span { display: block; color: var(--muted); font-size: 10px; }
.party-detail-grid strong { display: block; margin-top: 5px; overflow-wrap: anywhere; color: var(--navy); font-size: 13px; }
.party-detail-grid p, .customer-remark p { margin: 7px 0 0; overflow-wrap: anywhere; color: var(--muted); font-size: 11px; line-height: 1.65; }
.customer-remark { margin-top: 10px; padding: 13px 15px; background: #f2f7f6; }
.customer-remark strong { color: var(--navy); font-size: 11px; }
.fulfillment-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; border: 1px solid var(--border); }
.fulfillment-summary > div { min-width: 0; padding: 13px; border-bottom: 1px solid var(--border); }
.fulfillment-summary > div:not(:nth-child(3n)) { border-right: 1px solid var(--border); }
@@ -1134,9 +1191,17 @@ async function searchOrders(model: QueryModel) {
.cargo-summary div { display: flex; min-height: 74px; align-items: center; justify-content: center; flex-direction: column; gap: 4px; border-right: 1px solid var(--border); }
.cargo-summary div:last-child { border-right: 0; }
.cargo-summary span { color: var(--muted); font-size: 11px; }
.cargo-detail-list { margin: 10px 0 0; padding: 0; list-style: none; }
.cargo-detail-list li { display: flex; justify-content: space-between; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); font-size: 12px; }
.cargo-detail-list span, :deep(.el-timeline-item__content p), :deep(.el-timeline-item__content > span) { color: var(--muted); }
.cargo-detail-list { display: grid; gap: 10px; margin-top: 10px; }
.cargo-detail-list article { padding: 14px 15px; border: 1px solid var(--border); }
.cargo-detail-list header { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: baseline; gap: 10px; }
.cargo-detail-list header span, .cargo-detail-list header small { color: var(--muted); font-size: 10px; }
.cargo-detail-list header strong { overflow-wrap: anywhere; color: var(--navy); font-size: 13px; }
.cargo-detail-list header small { overflow-wrap: anywhere; text-align: right; }
.cargo-detail-list dl { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 11px 16px; margin: 13px 0 0; }
.cargo-detail-list dt { color: var(--muted); font-size: 10px; }
.cargo-detail-list dd { margin: 4px 0 0; overflow-wrap: anywhere; color: var(--navy); font-size: 11px; font-weight: 600; }
.cargo-detail-list article > p { margin: 11px 0 0; padding-top: 10px; border-top: 1px solid var(--border); color: var(--muted); font-size: 11px; }
:deep(.el-timeline-item__content p), :deep(.el-timeline-item__content > span) { color: var(--muted); }
:deep(.el-timeline-item__content p) { margin: 5px 0; font-size: 12px; }
:deep(.el-timeline-item__content > span) { font-size: 11px; }
@media (max-width: 1280px) {
@@ -1167,7 +1232,8 @@ async function searchOrders(model: QueryModel) {
.order-table-toolbar .el-button { width: 100%; min-height: 40px; }
.order-eligibility { flex-wrap: wrap; }
.order-eligibility .el-button { width: 100%; min-height: 44px; }
.order-form-grid, .order-detail-grid, .cargo-primary-grid, .cargo-secondary-grid, .fulfillment-summary { grid-template-columns: 1fr; }
.order-form-grid, .order-detail-grid, .cargo-primary-grid, .cargo-secondary-grid, .party-detail-grid { grid-template-columns: 1fr; }
.fulfillment-summary { grid-template-columns: 1fr; }
.fulfillment-summary > div, .fulfillment-summary > div:not(:nth-child(3n)) { border-right: 0; border-bottom: 1px solid var(--border); }
.fulfillment-summary > div:last-child { border-bottom: 0; }
.prototype-form-row, .prototype-form-row--wide, .prototype-form-row--compact, .prototype-form-row--short, .prototype-form-row--tabs { display: block; margin-bottom: 18px; }
@@ -1207,9 +1273,14 @@ async function searchOrders(model: QueryModel) {
.cargo-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cargo-summary div:nth-child(2) { border-right: 0; }
.cargo-summary div:nth-child(-n+2) { border-bottom: 1px solid var(--border); }
.cargo-detail-list li { flex-direction: column; }
.cargo-detail-list header { grid-template-columns: 1fr; gap: 4px; }
.cargo-detail-list header small { text-align: left; }
.cargo-detail-list dl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
:global(.first-leg-editor), :global(.first-leg-detail) { width: 100% !important; }
}
@media (max-width: 360px) {
.cargo-detail-list dl { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
.editor-actions { display: grid; width: 100%; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.editor-actions :deep(.el-button) { width: 100%; min-width: 0; margin-left: 0; }