2026-07-30 17:59:42 +08:00
< script setup lang = "ts" >
import { computed , onBeforeUnmount , onMounted , reactive , ref } from 'vue'
import { onBeforeRouteLeave , useRouter } from 'vue-router'
import { ElMessage , ElMessageBox } from 'element-plus'
import { AlertTriangle , Box , Building2 , CheckCircle2 , Eye , FilePlus2 , MapPin , PackageCheck , Pencil , Plus , RotateCcw , Search , Send , Trash2 } from '@lucide/vue'
import { FIRST_LEG_ORDER_STATUSES , FIRST_LEG_TRANSPORT_MODES , type FirstLegOrderStatus } from '@chongchong/shared'
import { useFirstLegOrdersStore } from '@/stores/firstLegOrders'
import { useTenantAdministrationStore } from '@/stores/tenantAdministration'
import { useAuthStore } from '@/stores/auth'
import { createFirstLegPartySnapshot , type FirstLegPartyRole } from '@/data/firstLegParties'
import { firstLegPartySource } from '@/data/firstLegPartySource'
import type { FirstLegOrder , FirstLegOrderDraft } from '@/types/firstLegOrder'
import {
canClientEditFirstLegOrder ,
createEmptyCargoItem ,
getFirstLegStatusLabel ,
getFirstLegStatusTone ,
getFirstLegTransportModeLabel ,
summarizeFirstLegCargo ,
validateFirstLegOrderDraft ,
} from '@/utils/firstLegOrder'
const store = useFirstLegOrdersStore ()
const tenantStore = useTenantAdministrationStore ()
const auth = useAuthStore ()
const router = useRouter ()
const companyApproved = computed (() => tenantStore . company . auditStatus === 'ACTIVE' )
const canCreateOrder = computed (() => auth . hasPermission ( 'order:first-leg:create' ))
const canEditOrder = computed (() => auth . hasPermission ( 'order:first-leg:edit' ))
const availableWarehouses = computed (() => store . warehouses )
const availableShippers = ref ( firstLegPartySource . listAvailable ( 'SHIPPER' ))
const availableConsignees = ref ( firstLegPartySource . listAvailable ( 'CONSIGNEE' ))
const filters = reactive ({ keyword : '' , status : '' as FirstLegOrderStatus | '' , transportMode : '' })
const dateRange = ref < [ string , string ] | [] > ([])
const editorOpen = ref ( false )
const detailOpen = ref ( false )
const editingId = ref < string >()
const detailId = ref < string >()
const saving = ref ( false )
const detailLoading = ref ( false )
const detailError = ref ( '' )
const editorBaseline = ref ( '' )
let cargoSequence = 0
function emptyDraft () : FirstLegOrderDraft {
return {
referenceNo : '' ,
transportMode : 'FCL' ,
businessType : 'EXPORT' ,
origin : '' ,
destination : '' ,
receivingWarehouseId : '' ,
expectedDepartureDate : '' ,
shipperPartyId : '' ,
shipperPartySnapshot : null ,
shipper : '' ,
consigneePartyId : '' ,
consigneePartySnapshot : null ,
consignee : '' ,
priority : 'NORMAL' ,
customerRemark : '' ,
tradeTerm : 'FOB' ,
destinationCountryCode : 'US' ,
destinationWarehouseName : '' ,
pickupMethod : 'SELF_EXPRESS' ,
channelCode : 'SEA-US-FBA' ,
channelName : '美国头程渠道' ,
taxMode : 'BUY_DECLARATION' ,
customsInspection : false ,
destinationType : 'FBA' ,
amazonWarehouseCode : '' ,
shipmentId : '' ,
items : [ createEmptyCargoItem ( `new-cargo- ${ ++ cargoSequence } ` )],
}
}
const form = reactive < FirstLegOrderDraft >( emptyDraft ())
2026-07-30 18:08:40 +08:00
const serializedDraft = computed (() => JSON . stringify ( form ))
const editorDirty = computed (() => editorOpen . value && serializedDraft . value !== editorBaseline . value )
2026-07-30 17:59:42 +08:00
const filteredOrders = computed (() => store . orders . filter (( order ) => {
const keyword = filters . keyword . trim (). toLowerCase ()
const matchesKeyword = ! keyword || [ order . orderNo , order . referenceNo , order . origin , order . destination , order . items . map (( item ) => item . cargoNameCn ). join ( ' ' )]
. filter ( Boolean )
. some (( value ) => String ( value ). toLowerCase (). includes ( keyword ))
const matchesStatus = ! filters . status || order . status === filters . status
const matchesMode = ! filters . transportMode || order . transportMode === filters . transportMode
const matchesDate = dateRange . value . length !== 2 || ( order . createdAt . slice ( 0 , 10 ) >= dateRange . value [ 0 ] && order . createdAt . slice ( 0 , 10 ) <= dateRange . value [ 1 ])
return matchesKeyword && matchesStatus && matchesMode && matchesDate
}))
const detailOrder = computed (() => detailId . value ? store . getById ( detailId . value ) : undefined )
const detailSummary = computed (() => detailOrder . value ? summarizeFirstLegCargo ( detailOrder . value . items ) : null )
const selectedWarehouse = computed (() => availableWarehouses . value . find (( item ) => item . id === form . receivingWarehouseId ))
2026-07-30 18:08:40 +08:00
const hasActiveFilters = computed (() => Boolean ( filters . keyword . trim () || filters . status || filters . transportMode || dateRange . value . length ))
const emptyDescription = computed (() => hasActiveFilters . value ? '没有符合筛选条件的头程订单' : '还没有头程订单' )
2026-07-30 17:59:42 +08:00
2026-07-30 18:08:40 +08:00
async function loadPageData () {
2026-07-30 17:59:42 +08:00
try {
await Promise . all ([ store . load (), firstLegPartySource . load ()])
availableShippers . value = firstLegPartySource . listAvailable ( 'SHIPPER' )
availableConsignees . value = firstLegPartySource . listAvailable ( 'CONSIGNEE' )
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '订单与往来单位数据加载失败' )
}
2026-07-30 18:08:40 +08:00
}
function handleBeforeUnload ( event : BeforeUnloadEvent ) {
if ( ! editorDirty . value ) return
event . preventDefault ()
event . returnValue = ''
}
onMounted (() => {
window . addEventListener ( 'beforeunload' , handleBeforeUnload )
void loadPageData ()
2026-07-30 17:59:42 +08:00
})
2026-07-30 18:08:40 +08:00
onBeforeUnmount (() => window . removeEventListener ( 'beforeunload' , handleBeforeUnload ))
onBeforeRouteLeave ( async () => {
if ( ! editorDirty . value ) return true
return confirmDiscardChanges ()
})
function markEditorClean () {
editorBaseline . value = serializedDraft . value
}
2026-07-30 17:59:42 +08:00
function replaceForm ( draft : FirstLegOrderDraft ) {
Object . assign ( form , structuredClone ( draft ))
2026-07-30 18:08:40 +08:00
markEditorClean ()
2026-07-30 17:59:42 +08:00
}
function draftFromOrder ( order : FirstLegOrder ) : FirstLegOrderDraft {
return {
referenceNo : order . referenceNo ,
transportMode : order . transportMode ,
businessType : order . businessType ,
origin : order . origin ,
destination : order . destination ,
receivingWarehouseId : order . receivingWarehouseId ,
expectedDepartureDate : order . expectedDepartureDate ,
shipperPartyId : order . shipperPartyId ,
shipperPartySnapshot : order . shipperPartySnapshot ? structuredClone ( order . shipperPartySnapshot ) : null ,
shipper : order . shipper ,
consigneePartyId : order . consigneePartyId ,
consigneePartySnapshot : order . consigneePartySnapshot ? structuredClone ( order . consigneePartySnapshot ) : null ,
consignee : order . consignee ,
priority : order . priority ,
customerRemark : order . customerRemark ,
tradeTerm : order . tradeTerm ,
destinationCountryCode : order . destinationCountryCode ,
destinationWarehouseName : order . destinationWarehouseName ,
pickupMethod : order . pickupMethod ,
channelCode : order . channelCode ,
channelName : order . channelName ,
taxMode : order . taxMode ,
customsInspection : order . customsInspection ,
destinationType : order . destinationType ,
amazonWarehouseCode : order . amazonWarehouseCode ,
shipmentId : order . shipmentId ,
items : structuredClone ( order . items ),
}
}
function selectParty ( role : FirstLegPartyRole , id : string ) {
const party = firstLegPartySource . findAvailable ( id , role )
if ( ! party ) return
const snapshot = createFirstLegPartySnapshot ( party , role )
if ( role === 'SHIPPER' ) {
form . shipperPartyId = party . id
form . shipperPartySnapshot = snapshot
form . shipper = party . name
} else {
form . consigneePartyId = party . id
form . consigneePartySnapshot = snapshot
form . consignee = party . name
}
}
function clearParty ( role : FirstLegPartyRole ) {
if ( role === 'SHIPPER' ) {
form . shipperPartyId = ''
form . shipperPartySnapshot = null
form . shipper = ''
} else {
form . consigneePartyId = ''
form . consigneePartySnapshot = null
form . consignee = ''
}
}
function selectShipper ( id : string ) {
selectParty ( 'SHIPPER' , id )
}
function selectConsignee ( id : string ) {
selectParty ( 'CONSIGNEE' , id )
}
function clearShipper () {
clearParty ( 'SHIPPER' )
}
function clearConsignee () {
clearParty ( 'CONSIGNEE' )
}
2026-07-30 18:08:40 +08:00
async function loadSelectedWarehouse ( id : string ) {
if ( ! id ) return
try {
await store . loadWarehouseDetail ( id )
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '仓库地址与收货时间加载失败' )
}
}
2026-07-30 17:59:42 +08:00
function openCreate () {
if ( availableWarehouses . value . length === 0 ) {
ElMessage . warning ( '当前没有可用的平台收货仓,请联系平台维护仓库后再创建订单' )
return
}
editingId . value = undefined
replaceForm ( emptyDraft ())
editorOpen . value = true
}
function openEdit ( order : FirstLegOrder ) {
if ( ! canEditOrder . value || ! canClientEditFirstLegOrder ( order . status )) return
editingId . value = order . id
replaceForm ( draftFromOrder ( order ))
detailOpen . value = false
editorOpen . value = true
}
async function openDetail ( order : FirstLegOrder ) {
detailId . value = order . id
detailOpen . value = true
detailLoading . value = true
2026-07-30 18:08:40 +08:00
detailError . value = ''
2026-07-30 17:59:42 +08:00
try {
await store . loadDetail ( order . id )
} catch ( error ) {
2026-07-30 18:08:40 +08:00
detailError . value = error instanceof Error ? error . message : '订单详情加载失败'
ElMessage . error ( detailError . value )
2026-07-30 17:59:42 +08:00
} finally {
detailLoading . value = false
}
}
2026-07-30 18:08:40 +08:00
async function retryDetail () {
const order = detailId . value ? store . getById ( detailId . value ) : undefined
if ( order ) await openDetail ( order )
}
async function confirmDiscardChanges () {
try {
await ElMessageBox . confirm ( '当前订单还有未保存的修改。离开后这些内容将丢失。' , '放弃未保存的修改?' , {
type : 'warning' ,
confirmButtonText : '放弃修改' ,
cancelButtonText : '继续编辑' ,
})
return true
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return false
throw error
}
}
async function requestCloseEditor ( done : () => void ) {
if ( saving . value ) return
if ( ! editorDirty . value || await confirmDiscardChanges ()) done ()
}
function closeEditor () {
void requestCloseEditor (() => {
editorOpen . value = false
})
}
2026-07-30 17:59:42 +08:00
function addCargoItem () {
form . items . push ( createEmptyCargoItem ( `new-cargo- ${ ++ cargoSequence } ` ))
}
function removeCargoItem ( index : number ) {
if ( form . items . length === 1 ) {
ElMessage . warning ( '订单至少需要保留一条货物明细' )
return
}
form . items . splice ( index , 1 )
}
async function saveDraft () {
if ( saving . value ) return
const errors = validateFirstLegOrderDraft ( form )
if ( errors . length > 0 ) {
ElMessage . warning ( errors [ 0 ])
return
}
saving . value = true
try {
const order = await store . saveDraft ( structuredClone ( form ), tenantStore . company . companyName , editingId . value )
editingId . value = order . id
2026-07-30 18:09:32 +08:00
markEditorClean ()
2026-07-30 17:59:42 +08:00
editorOpen . value = false
ElMessage . success ( '订单草稿已保存' )
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '草稿保存失败' )
} finally {
saving . value = false
}
}
async function submitOrder () {
if ( saving . value ) return
if ( ! companyApproved . value ) {
try {
await ElMessageBox . confirm ( '企业资料审核通过后才能提交订单。您可以继续保存草稿,或前往企业资料核对并补充信息。' , '暂时无法提交订单' , {
type : 'warning' ,
confirmButtonText : '前往企业资料' ,
cancelButtonText : '继续编辑草稿' ,
})
2026-07-30 18:09:32 +08:00
markEditorClean ()
2026-07-30 17:59:42 +08:00
editorOpen . value = false
await router . push ( '/company/profile' )
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return
throw error
}
return
}
const errors = validateFirstLegOrderDraft ( form )
if ( errors . length > 0 ) {
ElMessage . warning ( errors [ 0 ])
return
}
try {
saving . value = true
const order = await store . submit ( structuredClone ( form ), tenantStore . company . companyName , editingId . value , tenantStore . company . auditStatus )
2026-07-30 18:09:32 +08:00
markEditorClean ()
2026-07-30 17:59:42 +08:00
editorOpen . value = false
detailId . value = order . id
detailOpen . value = true
ElMessage . success ( '订单已提交,等待平台审核' )
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '订单提交失败' )
} finally {
saving . value = false
}
}
function resetFilters () {
filters . keyword = ''
filters . status = ''
filters . transportMode = ''
dateRange . value = []
}
</ script >
< template >
< section class = "first-leg-page" >
< div class = "order-eligibility" : class = "companyApproved ? 'is-approved' : 'is-pending'" role = "status" >
< component : is = "companyApproved ? CheckCircle2 : AlertTriangle" :size = "20" />
< div >< strong >{{ companyApproved ? '企业资料已审核,可正常提交订单' : canCreateOrder ? '企业资料待审核,当前仅可保存订单草稿' : '企业资料待审核,当前订单为只读' }}</ strong >< span >{{ companyApproved ? '提交后订单将进入平台审核。' : canCreateOrder ? '平台审核通过后即可提交;您仍可创建、编辑和保存草稿。' : '您当前拥有只读权限,可以继续查看订单进展。' }}</ span ></ div >
< el-button v-if = "!companyApproved && auth.hasPermission('company:profile:view')" link type="primary" @click="router.push('/company/profile')" > 查看企业资料 </ el -button >
</ div >
< div class = "order-metrics" aria -label =" 订单概览 " >
< article >< span > 全部订单 </ span >< strong >{{ store . orders . length }}</ strong ></ article >
< article >< span > 进行中 </ span >< strong >{{ store . activeCount }}</ strong ></ article >
< article >< span > 待平台审核 </ span >< strong >{{ store . pendingCount }}</ strong ></ article >
</ div >
< div class = "order-toolbar client-card" >
< div class = "order-filters" >
< el-input v-model = "filters.keyword" clearable placeholder="订单号、参考号、路线或品名" aria-label="订单关键词" >
< template # prefix >< Search :size = "16" /></ template >
</ el-input >
< el-select v-model = "filters.status" clearable placeholder="全部状态" aria-label="订单状态" >
< el-option v-for = "item in FIRST_LEG_ORDER_STATUSES" :key="item.code" :label="item.label" :value="item.code" />
</ el-select >
< el-select v-model = "filters.transportMode" clearable placeholder="全部运输方式" aria-label="运输方式" >
< el-option v-for = "item in FIRST_LEG_TRANSPORT_MODES" :key="item.code" :label="item.label" :value="item.code" />
</ el-select >
< el-date-picker v-model = "dateRange" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="创建开始" end-placeholder="创建结束" aria-label="订单创建日期" />
</ div >
< div class = "order-toolbar-actions" >
< el-button @click ="resetFilters" >< RotateCcw :size = "16" /> 重置 </ el-button >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" type="primary" :disabled="availableWarehouses.length === 0" @click="openCreate"><FilePlus2 :size="17" /> 创建订单 </ el-button >
</ div >
</ div >
< div v-loading = "store.loading" class="client-card order-table-card" >
< el-table v-if = "filteredOrders.length" class="desktop-order-table" :data="filteredOrders" row-key="id" >
< el-table-column label = "订单" min -width =" 190 " >
< template # default = "{ row }" >
< button class = "order-number-link" type = "button" @click ="openDetail(row as FirstLegOrder)" >{{ ( row as FirstLegOrder ) .orderNo || ' 未提交草稿 ' }}</ button >
< span class = "order-reference" >{{ ( row as FirstLegOrder ). referenceNo || '无客户参考号' }}</ span >
</ template >
</ el-table-column >
< el-table-column label = "运输路线" min -width =" 190 " >
< template # default = "{ row }" >< strong >{{ ( row as FirstLegOrder ). origin }} → {{ ( row as FirstLegOrder ). destination }}</ strong >< span class = "order-reference" >{{ getFirstLegTransportModeLabel (( row as FirstLegOrder ). transportMode ) }}</ span ></ template >
</ el-table-column >
< el-table-column label = "收货仓" min -width =" 120 " prop = "receivingWarehouseName" />
< el-table-column label = "货物" min -width =" 150 " >
< template # default = "{ row }" >{{ ( row as FirstLegOrder ). items [ 0 ] ? . cargoNameCn || '-' }}< span v-if = "(row as FirstLegOrder).items.length > 1" class="order-reference" > 等 {{ ( row as FirstLegOrder ) .items.length }} 项 </ span ></ template >
</ el -table-column >
< el-table-column label = "状态" width = "120" >
< template # default = "{ row }" >< el-tag : type = "getFirstLegStatusTone((row as FirstLegOrder).status)" effect = "light" >{{ getFirstLegStatusLabel (( row as FirstLegOrder ). status ) }}</ el-tag ></ template >
</ el-table-column >
< el-table-column label = "负责人" prop = "operatorName" width = "110" />
< el-table-column label = "更新时间" prop = "updatedAt" width = "155" />
< el-table-column label = "操作" width = "180" >
< template # default = "{ row }" >
< div class = "order-row-actions" >
< el-button link type = "primary" @click ="openDetail(row as FirstLegOrder)" >< Eye :size = "15" /> 详情 </ el-button >
< el-button v-if = "canEditOrder && canClientEditFirstLegOrder((row as FirstLegOrder).status)" link type="primary" @click="openEdit(row as FirstLegOrder)"><Pencil :size="15" /> 编辑 </ el-button >
</ div >
</ template >
</ el-table-column >
</ el-table >
< div v-if = "filteredOrders.length" class="mobile-order-list" >
< article v-for = "order in filteredOrders" :key="order.id" class="mobile-order-card" >
< header >< button type = "button" @click ="openDetail(order)" >{{ order.orderNo || ' 未提交草稿 ' }}</ button >< el-tag :type = "getFirstLegStatusTone(order.status)" effect = "light" >{{ getFirstLegStatusLabel ( order . status ) }}</ el-tag ></ header >
< span class = "order-reference" >{{ order . referenceNo || '无客户参考号' }}</ span >
< dl >< div >< dt > 运输路线 </ dt >< dd >{{ order . origin }} → {{ order . destination }}</ dd ></ div >< div >< dt > 收货仓 </ dt >< dd >{{ order . receivingWarehouseName }}</ dd ></ div >< div >< dt > 货物 </ dt >< dd >{{ order . items [ 0 ] ? . cargoNameCn || '-' }}< span v-if = "order.items.length > 1" > 等 {{ order.items.length }} 项 </ span ></ dd ></ div >< div >< dt > 负责人 </ dt >< dd >{{ order . operatorName }}</ dd ></ div ></ dl >
< footer >< span > 更新于 {{ order . updatedAt }}</ span >< div >< el-button link type = "primary" @click ="openDetail(order)" >< Eye :size = "15" /> 详情 </ el-button >< el-button v-if = "canEditOrder && canClientEditFirstLegOrder(order.status)" link type="primary" @click="openEdit(order)"><Pencil :size="15" /> 编辑 </ el-button ></ div ></ footer >
</ article >
</ div >
< el-empty v-else description = "没有符合条件的头程订单" >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" type="primary" :disabled="availableWarehouses.length === 0" @click="openCreate"><Plus :size="16" />{{ availableWarehouses . length ? '创建第一笔订单' : '暂无可用收货仓' }}</ el-button >
</ el-empty >
</ div >
< el-drawer v-model = "editorOpen" class="first-leg-editor" :title="editingId ? '编辑订单草稿' : '创建头程订单'" size="760px" destroy -on-close >
< el-form label -position =" top " class = "order-form" >
< section class = "order-form-section" >
< h3 > 运输信息 </ h3 >
< div class = "order-form-grid" >
< el-form-item label = "运输方式" required >< el-select v-model = "form.transportMode"><el-option v-for="item in FIRST_LEG_TRANSPORT_MODES" :key="item.code" :label="item.label" :value="item.code" /></ el-select ></ el-form-item >
< el-form-item label = "业务类型" required >< el-radio-group v-model = "form.businessType"><el-radio-button value="EXPORT">出口</el-radio-button><el-radio-button value="IMPORT" > 进口 </ el -radio-button ></ el-radio-group ></ el-form-item >
< el-form-item label = "起运地" required >< el-input v-model = "form.origin" placeholder="例如:宁波" /></ el-form-item >
< el-form-item label = "目的地" required >< el-input v-model = "form.destination" placeholder="例如:洛杉矶" /></ el-form-item >
< el-form-item label = "平台收货仓" required >< el-select v-model = "form.receivingWarehouseId" filterable placeholder="请选择送货仓库"><el-option v-for="warehouse in availableWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.city}`" :value="warehouse.id" /></ el-select ></ el-form-item >
< el-form-item label = "期望出发日期" >< el-date-picker v-model = "form.expectedDepartureDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></ el-form-item >
< el-form-item label = "客户参考号" >< el-input v-model = "form.referenceNo" maxlength="100" placeholder="贵司内部单号(选填)" /></ el-form-item >
< el-form-item label = "优先级" >< el-select v-model = "form.priority"><el-option label="普通" value="NORMAL" /><el-option label="紧急" value="URGENT" /><el-option label="加急" value="EXPEDITED" /></ el-select ></ el-form-item >
< el-form-item label = "目的国代码" required >< el-input v-model = "form.destinationCountryCode" maxlength="2" placeholder="例如:US" /></ el-form-item >
< el-form-item label = "目的仓名称" required >< el-input v-model = "form.destinationWarehouseName" maxlength="128" placeholder="例如:ONT8" /></ el-form-item >
< el-form-item label = "渠道编码" required >< el-input v-model = "form.channelCode" maxlength="64" /></ el-form-item >
< el-form-item label = "渠道名称" required >< el-input v-model = "form.channelName" maxlength="128" /></ el-form-item >
< el-form-item label = "提货方式" required >< el-select v-model = "form.pickupMethod"><el-option label="自送快递" value="SELF_EXPRESS" /><el-option label="上门提货" value="PICKUP" /><el-option label="自送卡车" value="SELF_TRUCK" /></ el-select ></ el-form-item >
< el-form-item label = "报关模式" required >< el-select v-model = "form.taxMode"><el-option label="买单报关" value="BUY_DECLARATION" /><el-option label="退税报关" value="TAX_REFUND" /></ el-select ></ el-form-item >
< el-form-item label = "目的仓类型" required >< el-select v-model = "form.destinationType"><el-option label="FBA" value="FBA" /><el-option label="海外仓" value="OVERSEAS" /><el-option label="私人地址" value="PRIVATE" /></ el-select ></ el-form-item >
< el-form-item label = "贸易术语" >< el-input v-model = "form.tradeTerm" maxlength="16" placeholder="例如:FOB" /></ el-form-item >
< el-form-item v-if = "form.destinationType === 'FBA'" label="Amazon 仓库编码"><el-input v-model="form.amazonWarehouseCode" maxlength="32" placeholder="例如:ONT8" /></ el-form-item >
< el-form-item v-if = "form.destinationType === 'FBA'" label="Shipment ID"><el-input v-model="form.shipmentId" maxlength="64" /></ el-form-item >
</ div >
< div v-if = "selectedWarehouse" class="selected-warehouse"><MapPin :size="17" />< div >< strong >{{ selectedWarehouse . name }}</ strong >< span >{{ selectedWarehouse . province }}{{ selectedWarehouse . city }} · {{ selectedWarehouse . address }}</ span >< small > 收货时间 : {{ selectedWarehouse . businessHours }}</ small ></ div ></ div >
</ section >
< section class = "order-form-section" >
< div class = "order-section-heading" >< div >< h3 > 收发货单位 </ h3 >< p > 仅可选择平台已启用的往来单位 ; 下方联系人和地址调整只影响本订单快照 。 </ p ></ div ></ div >
< div class = "party-editor-grid" >
< article class = "party-editor" >
< header >< span >< Building2 :size = "17" />< strong > 发货单位 </ strong ></ span >< small > 必填 </ small ></ header >
< el-form-item label = "选择发货单位" required >
< el-select v-model = "form.shipperPartyId" filterable clearable placeholder="请选择已启用的发货单位" @change="selectShipper" @clear="clearShipper" >
< el-option v-for = "party in availableShippers" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" />
</ el-select >
</ el-form-item >
< div v-if = "form.shipperPartySnapshot" class="party-snapshot-fields" >
< div class = "party-name" >< strong >{{ form . shipperPartySnapshot . name }}</ strong >< span >{{ form . shipperPartySnapshot . code }}</ span ></ div >
< el-form-item label = "联系人" >< el-input v-model = "form.shipperPartySnapshot.contactName" maxlength="50" /></ el-form-item >
< el-form-item label = "联系电话" >< el-input v-model = "form.shipperPartySnapshot.phone" maxlength="30" /></ el-form-item >
< el-form-item label = "联系邮箱" >< el-input v-model = "form.shipperPartySnapshot.email" maxlength="100" /></ el-form-item >
< el-form-item label = "本单发货地址" >< el-input v-model = "form.shipperPartySnapshot.address" maxlength="300" /></ el-form-item >
</ div >
< div v-else class = "party-empty" > 选择后可核对本单联系人和发货地址 </ div >
</ article >
< article class = "party-editor" >
< header >< span >< Building2 :size = "17" />< strong > 收货单位 </ strong ></ span >< small > 必填 </ small ></ header >
< el-form-item label = "选择收货单位" required >
< el-select v-model = "form.consigneePartyId" filterable clearable placeholder="请选择已启用的收货单位" @change="selectConsignee" @clear="clearConsignee" >
< el-option v-for = "party in availableConsignees" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" />
</ el-select >
</ el-form-item >
< div v-if = "form.consigneePartySnapshot" class="party-snapshot-fields" >
< div class = "party-name" >< strong >{{ form . consigneePartySnapshot . name }}</ strong >< span >{{ form . consigneePartySnapshot . code }}</ span ></ div >
< el-form-item label = "联系人" >< el-input v-model = "form.consigneePartySnapshot.contactName" maxlength="50" /></ el-form-item >
< el-form-item label = "联系电话" >< el-input v-model = "form.consigneePartySnapshot.phone" maxlength="30" /></ el-form-item >
< el-form-item label = "联系邮箱" >< el-input v-model = "form.consigneePartySnapshot.email" maxlength="100" /></ el-form-item >
< el-form-item label = "本单收货地址" >< el-input v-model = "form.consigneePartySnapshot.address" maxlength="300" /></ el-form-item >
</ div >
< div v-else class = "party-empty" > 选择后可核对本单联系人和收货地址 </ div >
</ article >
</ div >
</ section >
< section class = "order-form-section" >
< div class = "order-section-heading" >< div >< h3 > 货物明细 </ h3 >< p > 总件数 、 重量和体积将在提交时由服务端汇总 。 </ p ></ div >< el-button @click ="addCargoItem" >< Plus :size = "16" /> 添加货物 </ el-button ></ div >
< article v-for = "(item, index) in form.items" :key="item.id" class="cargo-editor-card" >
< header >< strong > 货物 {{ index + 1 }}</ strong >< el-button link type = "danger" : aria -label ="` 删除货物 $ { index + 1 } `" @click ="removeCargoItem(index)" >< Trash2 :size = "16" /> 删除 </ el-button ></ header >
< div class = "cargo-form-grid" >
< el-form-item label = "中文品名" required >< el-input v-model = "item.cargoNameCn" /></ el-form-item >
< el-form-item label = "英文品名" >< el-input v-model = "item.cargoNameEn" /></ el-form-item >
< el-form-item label = "客户 SKU" >< el-input v-model = "item.sku" /></ el-form-item >
< el-form-item label = "包装类型" required >< el-select v-model = "item.packageType"><el-option label="纸箱" value="CARTON" /><el-option label="托盘" value="PALLET" /><el-option label="木箱" value="WOODEN_CASE" /></ el-select ></ el-form-item >
< el-form-item label = "HS Code" >< el-input v-model = "item.hsCode" maxlength="20" /></ el-form-item >
< el-form-item label = "件数" required >< el-input-number v-model = "item.pieces" :min="1" controls-position="right" /></ el-form-item >
< el-form-item label = "箱数" >< el-input-number v-model = "item.packageCount" :min="0" controls-position="right" /></ el-form-item >
< el-form-item label = "毛重 (kg)" required >< el-input-number v-model = "item.grossWeightKg" :min="0" :precision="2" controls-position="right" /></ el-form-item >
< el-form-item label = "体积 (m³)" required >< el-input-number v-model = "item.volumeCbm" :min="0" :precision="3" controls-position="right" /></ el-form-item >
< el-form-item label = "申报货值" >< el-input-number v-model = "item.declaredValue" :min="0" :precision="2" controls-position="right" /></ el-form-item >
< el-form-item label = "币种" >< el-input v-model = "item.currency" maxlength="3" /></ el-form-item >
< el-form-item label = "危险品" >< el-switch v-model = "item.dangerousGoods" /></ el-form-item >
< template v-if = "form.transportMode === 'FCL'" >
< el-form-item label = "箱型" required >< el-select v-model = "item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ']" :key="type" :label="type" :value="type" /></ el-select ></ el-form-item >
< el-form-item label = "柜量" required >< el-input-number v-model = "item.containerCount" :min="0" controls-position="right" /></ el-form-item >
</ template >
< el-form-item label = "货物备注" class = "cargo-remark" >< el-input v-model = "item.remark" maxlength="300" /></ el-form-item >
</ div >
</ article >
</ section >
< section class = "order-form-section" >
< h3 > 订单备注 </ h3 >
< el-form-item label = "客户备注" >< el-input v-model = "form.customerRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="平台工作人员可以看到该备注" /></ el-form-item >
</ section >
</ el-form >
< template # footer >< div class = "editor-actions" >< el-button :disabled = "saving" @click ="editorOpen = false" > 返回 </ 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" />{{ companyApproved ? '提交审核' : '提交前需完成资料审核' }}</ el-button ></ div ></ template >
</ el-drawer >
< el-drawer v-model = "detailOpen" v-loading="detailLoading" class="first-leg-detail" title="订单详情" size="680px" >
< template v-if = "detailOrder" >
< div class = "order-detail-heading" >
< 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 >
< 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 >
< 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 >< el-timeline >< el-timeline-item v-for = "item in [...detailOrder.timeline].reverse()" :key="item.id" :timestamp="item.occurredAt" placement="top" >< strong >{{ item . title }}</ strong >< p >{{ item . description }}</ p >< span >{{ item . operatorName }}</ span ></ el-timeline-item ></ el-timeline ></ section >
</ template >
< template # footer >< div v-if = "detailOrder" class="editor-actions"><el-button @click="detailOpen = false">关闭</el-button><el-button v-if="canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" @click="openEdit(detailOrder)"><Pencil :size="16" /> 编辑草稿 </ el-button ></ div ></ template >
</ el-drawer >
</ section >
</ template >
< style scoped >
. first - leg - page { display : flex ; flex - direction : column ; gap : 14 px ; }
. order - eligibility { display : flex ; align - items : flex - start ; gap : 12 px ; padding : 14 px 16 px ; border : 1 px solid ; }
. order - eligibility svg { flex : 0 0 20 px ; margin - top : 1 px ; }
. order - eligibility div { display : flex ; min - width : 0 ; flex : 1 ; flex - direction : column ; gap : 3 px ; }
. order - eligibility strong { color : var ( -- navy ); font - size : 13 px ; }
. order - eligibility span { color : var ( -- muted ); font - size : 11 px ; line - height : 1.55 ; }
. order - eligibility . is - approved { border - color : # bcd9d2 ; background : # eff8f5 ; color : # 20745 f ; }
. order - eligibility . is - pending { border - color : # e5d2a5 ; background : # fff9eb ; color : # 9 a6a12 ; }
. order - metrics { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr )); gap : 12 px ; }
. order - metrics article { display : flex ; align - items : center ; justify - content : space - between ; min - height : 76 px ; padding : 16 px 20 px ; border : 1 px solid var ( -- border ); background : # fff ; }
. order - metrics span { color : var ( -- muted ); font - size : 12 px ; }
. order - metrics strong { color : var ( -- navy ); font - size : 25 px ; }
. order - toolbar { display : flex ; align - items : stretch ; flex - direction : column ; gap : 12 px ; padding : 16 px ; }
. order - filters { display : grid ; width : 100 % ; grid - template - columns : minmax ( 190 px , 1.35 fr ) minmax ( 130 px , .65 fr ) minmax ( 150 px , .75 fr ) minmax ( 230 px , 1 fr ); gap : 10 px ; }
. order - toolbar - actions , . order - row - actions , . editor - actions { display : flex ; justify - content : flex - end ; gap : 8 px ; }
. order - table - card { padding : 0 ; overflow : hidden ; }
. mobile - order - list { display : none ; }
. order - number - link { display : block ; padding : 0 ; border : 0 ; background : none ; color : var ( -- primary ); cursor : pointer ; font - weight : 700 ; text - align : left ; }
. order - number - link : focus - visible { outline : 2 px solid var ( -- primary ); outline - offset : 3 px ; }
. order - reference { display : block ; margin - top : 4 px ; color : var ( -- muted ); font - size : 11 px ; font - weight : 400 ; }
. order - form { padding : 0 2 px 28 px ; }
. order - form - section + . order - form - section { margin - top : 24 px ; padding - top : 22 px ; border - top : 1 px solid var ( -- border ); }
. order - form - section h3 { margin : 0 0 16 px ; color : var ( -- navy ); font - size : 16 px ; }
. order - form - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); column - gap : 18 px ; }
. order - form - grid : deep (. el - select ), . order - form - grid : deep (. el - date - editor ), . cargo - form - grid : deep (. el - select ), . cargo - form - grid : deep (. el - input - number ) { width : 100 % ; }
. selected - warehouse { display : flex ; gap : 10 px ; margin - top : - 2 px ; padding : 12 px 14 px ; border : 1 px solid # cfe1dd ; background : # f3f8f7 ; color : var ( -- primary ); }
. selected - warehouse svg { flex : 0 0 17 px ; margin - top : 2 px ; }
. selected - warehouse div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 4 px ; }
. selected - warehouse span , . selected - warehouse small { color : var ( -- muted ); font - size : 11 px ; line - height : 1.5 ; }
. party - editor - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 14 px ; }
. party - editor { min - width : 0 ; padding : 16 px ; border : 1 px solid var ( -- border ); background : # f9fbfb ; }
. party - editor > header { display : flex ; align - items : center ; justify - content : space - between ; gap : 12 px ; margin - bottom : 14 px ; }
. party - editor > header span { display : flex ; min - width : 0 ; align - items : center ; gap : 8 px ; color : var ( -- navy ); }
. party - editor > header svg { flex : 0 0 17 px ; color : var ( -- primary ); }
. party - editor > header small { color : var ( -- muted ); font - size : 11 px ; }
. party - editor : deep (. el - select ) { width : 100 % ; }
. party - snapshot - fields { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); column - gap : 12 px ; padding - top : 12 px ; border - top : 1 px solid var ( -- border ); }
. party - name { display : flex ; min - width : 0 ; grid - column : 1 / - 1 ; align - items : baseline ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px ; }
. party - name strong { min - width : 0 ; overflow - wrap : anywhere ; color : var ( -- navy ); font - size : 13 px ; }
. party - name span { flex : 0 0 auto ; color : var ( -- muted ); font - size : 10 px ; }
. party - snapshot - fields . el - form - item : last - child { grid - column : 1 / - 1 ; }
. party - empty { display : grid ; min - height : 150 px ; place - items : center ; border - top : 1 px solid var ( -- border ); color : var ( -- muted ); font - size : 12 px ; text - align : center ; }
. order - section - heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px ; }
. order - section - heading h3 { margin - bottom : 5 px ; }
. order - section - heading p { margin : 0 ; color : var ( -- muted ); font - size : 11 px ; }
. cargo - editor - card { padding : 16 px ; border : 1 px solid var ( -- border ); background : # f9fbfb ; }
. cargo - editor - card + . cargo - editor - card { margin - top : 12 px ; }
. cargo - editor - card header { display : flex ; align - items : center ; justify - content : space - between ; margin - bottom : 12 px ; }
. cargo - form - grid { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr )); column - gap : 14 px ; }
. cargo - remark { grid - column : 1 / - 1 ; }
. order - detail - heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 16 px ; padding - bottom : 20 px ; border - bottom : 1 px solid var ( -- border ); }
. order - detail - heading span { color : var ( -- muted ); font - size : 11 px ; }
. order - detail - heading h2 { margin : 6 px 0 ; font - size : 21 px ; }
. order - detail - heading p { margin : 0 ; color : var ( -- muted ); }
. order - detail - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 0 ; margin : 18 px 0 ; border : 1 px solid var ( -- border ); }
. order - detail - grid div { padding : 13 px ; border - bottom : 1 px solid var ( -- border ); }
. order - detail - grid div : nth - child ( odd ) { border - right : 1 px solid var ( -- border ); }
. order - detail - grid dt { color : var ( -- muted ); font - size : 11 px ; }
. order - detail - grid dd { margin : 5 px 0 0 ; font - size : 13 px ; font - weight : 600 ; }
. 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 ; }
. express - information { display : grid ; grid - template - columns : auto repeat ( 2 , minmax ( 0 , 1 fr )); align - items : center ; gap : 16 px ; padding : 15 px 16 px ; background : # f2f7f6 ; color : var ( -- primary ); }
. express - information > div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 4 px ; }
. express - information span { color : var ( -- muted ); font - size : 11 px ; }
. express - information strong { overflow - wrap : anywhere ; color : var ( -- navy ); font - size : 13 px ; }
. express - empty { display : flex ; align - items : flex - start ; gap : 12 px ; padding : 15 px 16 px ; border : 1 px dashed # cbd5da ; color : var ( -- muted ); }
. express - empty svg { flex : 0 0 20 px ; color : var ( -- primary ); }
. express - empty div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 4 px ; }
. express - empty strong { color : var ( -- navy ); font - size : 13 px ; }
. express - empty span { font - size : 11 px ; line - height : 1.5 ; }
. cargo - summary { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr )); border : 1 px solid var ( -- border ); }
. 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 ); }
: 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 : 1100 px ) { . order - toolbar - actions { align - self : flex - end ; } . order - filters { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); } }
@ media ( max - width : 720 px ) {
. order - eligibility { flex - wrap : wrap ; }
. order - eligibility . el - button { width : 100 % ; min - height : 44 px ; }
. order - metrics { grid - template - columns : 1 fr ; }
. order - metrics article { min - height : 60 px ; }
. order - filters , . order - form - grid , . cargo - form - grid , . order - detail - grid , . party - editor - grid , . party - snapshot - fields { grid - template - columns : 1 fr ; }
. order - toolbar - actions { width : 100 % ; align - self : stretch ; }
. order - toolbar - actions : deep (. el - button ) { min - height : 44 px ; flex : 1 ; }
. editor - actions : deep (. el - button ) { min - height : 44 px ; }
. desktop - order - table { display : none ; }
. mobile - order - list { display : grid ; gap : 10 px ; padding : 10 px ; }
. mobile - order - card { padding : 14 px ; border : 1 px solid var ( -- border ); background : # fff ; }
. mobile - order - card header , . mobile - order - card footer { display : flex ; align - items : center ; justify - content : space - between ; gap : 10 px ; }
. mobile - order - card header button { padding : 0 ; border : 0 ; background : none ; color : var ( -- primary ); font - weight : 750 ; }
. mobile - order - card header button : focus - visible { outline : 2 px solid var ( -- primary ); outline - offset : 3 px ; }
. mobile - order - card dl { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 12 px ; margin : 14 px 0 ; }
. mobile - order - card dl div { min - width : 0 ; }
. mobile - order - card dt , . mobile - order - card footer > span { color : var ( -- muted ); font - size : 10 px ; }
. mobile - order - card dd { overflow : hidden ; margin : 4 px 0 0 ; font - size : 12 px ; font - weight : 600 ; text - overflow : ellipsis ; white - space : nowrap ; }
. mobile - order - card dd span { margin - left : 4 px ; color : var ( -- muted ); font - weight : 400 ; }
. mobile - order - card footer { align - items : flex - end ; padding - top : 10 px ; border - top : 1 px solid var ( -- border ); }
. mobile - order - card footer > div { display : flex ; }
. order - detail - grid div : nth - child ( odd ) { border - right : 0 ; }
. party - name , . party - snapshot - fields . el - form - item : last - child { grid - column : auto ; }
. express - information { grid - template - columns : auto 1 fr ; }
. express - information > div : last - child { grid - column : 2 ; }
. 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 ; }
: global (. first - leg - editor ), : global (. first - leg - detail ) { width : 100 % ! important ; }
}
</ style >