@@ -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="6 80px" >
< el-drawer v-model = "detailOpen" class="first-leg-detail" title="订单详情" size="82 0px" >
< 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 : 4 px ; color : var ( -- muted ) ; font - size : 11 px ; font - weight : 400 ; line - height : 1.5 ; }
. detail - section { margin - top : 22 px ; }
. detail - section h3 { margin : 0 0 12 px ; font - size : 15 px ; }
. party - detail - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 12 px ; }
. party - detail - grid article { min - width : 0 ; padding : 15 px ; border : 1 px solid var ( -- border ) ; background : # fbfcfc ; }
. party - detail - grid span { display : block ; color : var ( -- muted ) ; font - size : 10 px ; }
. party - detail - grid strong { display : block ; margin - top : 5 px ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 13 px ; }
. party - detail - grid p , . customer - remark p { margin : 7 px 0 0 ; overflow - wrap : anywhere ; color : var ( -- muted ) ; font - size : 11 px ; line - height : 1.65 ; }
. customer - remark { margin - top : 10 px ; padding : 13 px 15 px ; background : # f2f7f6 ; }
. customer - remark strong { color : var ( -- navy ) ; font - size : 11 px ; }
. fulfillment - summary { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ; margin : 0 ; border : 1 px solid var ( -- border ) ; }
. fulfillment - summary > div { min - width : 0 ; padding : 13 px ; border - bottom : 1 px solid var ( -- border ) ; }
. fulfillment - summary > div : not ( : nth - child ( 3 n ) ) { border - right : 1 px solid var ( -- border ) ; }
@@ -1134,9 +1191,17 @@ async function searchOrders(model: QueryModel) {
. cargo - summary div { display : flex ; min - height : 74 px ; align - items : center ; justify - content : center ; flex - direction : column ; gap : 4 px ; border - right : 1 px solid var ( -- border ) ; }
. cargo - summary div : last - child { border - right : 0 ; }
. cargo - summary span { color : var ( -- muted ) ; font - size : 11 px ; }
. cargo - detail - list { margin : 10 px 0 0 ; padding : 0 ; list - style : none ; }
. cargo - detail - list li { display : flex ; justify - content : space - between ; gap : 12 px ; padding : 11 px 0 ; border - bottom : 1 px solid var ( -- border ) ; font - size : 12 px ; }
. 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 : 10 px ; margin - top : 10 px ; }
. cargo - detail - list article { padding : 14 px 15 px ; border : 1 px solid var ( -- border ) ; }
. cargo - detail - list header { display : grid ; grid - template - columns : auto minmax ( 0 , 1 fr ) auto ; align - items : baseline ; gap : 10 px ; }
. cargo - detail - list header span , . cargo - detail - list header small { color : var ( -- muted ) ; font - size : 10 px ; }
. cargo - detail - list header strong { overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 13 px ; }
. cargo - detail - list header small { overflow - wrap : anywhere ; text - align : right ; }
. cargo - detail - list dl { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; gap : 11 px 16 px ; margin : 13 px 0 0 ; }
. cargo - detail - list dt { color : var ( -- muted ) ; font - size : 10 px ; }
. cargo - detail - list dd { margin : 4 px 0 0 ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 11 px ; font - weight : 600 ; }
. cargo - detail - list article > p { margin : 11 px 0 0 ; padding - top : 10 px ; border - top : 1 px solid var ( -- border ) ; color : var ( -- muted ) ; font - size : 11 px ; }
: deep ( . el - timeline - item _ _content p ) , : deep ( . el - timeline - item _ _content > span ) { color : var ( -- muted ) ; }
: deep ( . el - timeline - item _ _content p ) { margin : 5 px 0 ; font - size : 12 px ; }
: deep ( . el - timeline - item _ _content > span ) { font - size : 11 px ; }
@ media ( max - width : 1280 px ) {
@@ -1167,7 +1232,8 @@ async function searchOrders(model: QueryModel) {
. order - table - toolbar . el - button { width : 100 % ; min - height : 40 px ; }
. order - eligibility { flex - wrap : wrap ; }
. order - eligibility . el - button { width : 100 % ; min - height : 44 px ; }
. order - form - grid , . order - detail - grid , . cargo - primary - grid , . cargo - secondary - grid , . fulfillment - summary { grid - template - columns : 1 fr ; }
. order - form - grid , . order - detail - grid , . cargo - primary - grid , . cargo - secondary - grid , . party - detail - grid { grid - template - columns : 1 fr ; }
. fulfillment - summary { grid - template - columns : 1 fr ; }
. fulfillment - summary > div , . fulfillment - summary > div : not ( : nth - child ( 3 n ) ) { border - right : 0 ; border - bottom : 1 px 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 : 18 px ; }
@@ -1207,9 +1273,14 @@ async function searchOrders(model: QueryModel) {
. cargo - summary { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
. cargo - summary div : nth - child ( 2 ) { border - right : 0 ; }
. cargo - summary div : nth - child ( - n + 2 ) { border - bottom : 1 px solid var ( -- border ) ; }
. cargo - detail - list li { flex - direction : column ; }
. cargo - detail - list header { grid - template - columns : 1 fr ; gap : 4 px ; }
. cargo - detail - list header small { text - align : left ; }
. cargo - detail - list dl { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
: global ( . first - leg - editor ) , : global ( . first - leg - detail ) { width : 100 % ! important ; }
}
@ media ( max - width : 360 px ) {
. cargo - detail - list dl { grid - template - columns : 1 fr ; }
}
@ media ( max - width : 480 px ) {
. editor - actions { display : grid ; width : 100 % ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
. editor - actions : deep ( . el - button ) { width : 100 % ; min - width : 0 ; margin - left : 0 ; }