@@ -2,7 +2,7 @@
import { computed , nextTick , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { useRoute , useRouter } from 'vue-router'
import { ElMessage , ElMessageBox , type FormInstance , type FormItemRule , type FormRules , type UploadRequestOptions } from 'element-plus'
import { AlertTriangle , Box , ChevronDown , ChevronUp , Download , FilePlus2 , FileText , GripVertical , ImagePlus , MapPin , PackageCheck , Pencil , Plus , RotateCcw , Send , SlidersHorizontal , Trash2 , Upload } from '@lucide/vue'
import { AlertTriangle , ChevronDown , ChevronUp , Download , FilePlus2 , FileText , GripVertical , ImagePlus , MapPin , PackageCheck , Pencil , Plus , RotateCcw , Send , SlidersHorizontal , Trash2 , Upload } from '@lucide/vue'
import { FIRST _LEG _ORDER _STATUSES , type FirstLegOrderStatus } from '@chongchong/shared'
import QueryToolbar , { type QueryField , type QueryModel } from '@/components/common/QueryToolbar.vue'
import DataTable , { type DataTableColumn } from '@/components/common/DataTable.vue'
@@ -66,9 +66,13 @@ const workspace = useWorkspaceTabsStore()
const auth = useAuthStore ( )
const route = useRoute ( )
const router = useRouter ( )
const props = withDefaults ( defineProps < { createPage ? : boolean } > ( ) , { createPage : false } )
const props = withDefaults ( defineProps < { createPage ? : boolean ; detailPage ? : boolean ; showCreateAction ? : boolean } > ( ) , { createPage : false , detailPage : false , showCreateAction : true } )
const isCreatePage = computed ( ( ) => props . createPage )
const isDetailPage = computed ( ( ) => props . detailPage )
const routeDraftId = computed ( ( ) => typeof route . params . draftId === 'string' ? route . params . draftId : '' )
const routeDetailId = computed ( ( ) => typeof route . params . orderId === 'string' ? route . params . orderId : '' )
const routeCopySourceId = computed ( ( ) => typeof route . query . copyFrom === 'string' ? route . query . copyFrom : '' )
const routeCopySourceIsDraft = computed ( ( ) => route . query . copyDraft === '1' )
const canViewCompanyProfile = computed ( ( ) => auth . hasPermission ( 'company:profile:view' ) )
const companyAuditLoaded = ref ( false )
const companySubmissionBlocked = computed ( ( ) => companyAuditLoaded . value && tenantStore . company . auditStatus !== 'ACTIVE' )
@@ -78,13 +82,12 @@ const canSubmitOrder = computed(() => auth.hasPermission('order:first-leg:submit
const canConfirmInboundFee = computed ( ( ) => auth . hasPermission ( 'order:first-leg:edit' ) )
const availableWarehouses = computed ( ( ) => store . warehouses . filter ( ( warehouse ) => warehouse . warehouseType === 'DOMESTIC' ) )
const availableDestinationWarehouses = computed ( ( ) => store . warehouses . filter ( ( warehouse ) => warehouse . warehouseType === form . destinationType ) )
const filters = ref < QueryModel > ( { orderNo : '' , receivingWarehouseId : '' , channelName : '' , cargoKeyword : '' , createdRange : [ ] , status : '' , destinationType : '' , trackingNumber : '' } )
const filters = ref < QueryModel > ( { receivingWarehouseId : '' , channelName : '' , cargoKeyword : '' , createdRange : [ ] , status : '' , destinationType : '' , trackingNumber : '' } )
const appliedFilters = ref < QueryModel > ( { ... filters . value } )
const queryFields = computed < QueryField [ ] > ( ( ) => [
{ key : 'orderNo ' , label : '订单号 ' , type : 'input' , placeholder : '请输入订单号 ' , props : { 'aria-label' : '订单号 ' } } ,
{ key : 'cargoKeyword ' , label : '入仓码 / SKU ' , type : 'input' , placeholder : '请输入入仓码或 SKU ' , props : { 'aria-label' : '入仓码或 SKU ' } } ,
{ key : 'receivingWarehouseId' , label : '收货仓' , type : 'select' , placeholder : '全部' , options : availableWarehouses . value . map ( ( item ) => ( { label : item . name , value : item . id } ) ) , props : { 'aria-label' : '收货仓' } } ,
{ key : 'channelName' , label : '渠道名称' , type : 'select' , placeholder : '全部' , options : [ ... new Set ( store . orders . map ( ( item ) => item . channelName ) . filter ( Boolean ) ) ] . map ( ( name ) => ( { label : name , value : name } ) ) , props : { 'aria-label' : '渠道名称' } } ,
{ key : 'cargoKeyword' , label : '入仓码 / SKU' , type : 'input' , placeholder : '请输入入仓码或 SKU' , props : { 'aria-label' : '入仓码或 SKU' } } ,
{ key : 'createdRange' , label : '创建时间' , type : 'daterange' , placeholder : '请选择创建时间' , props : { 'aria-label' : '创建时间' } } ,
{ key : 'status' , label : '当前状态' , type : 'select' , placeholder : '全部' , options : FIRST _LEG _ORDER _STATUSES . map ( ( item ) => ( { label : item . label , value : item . code } ) ) , props : { 'aria-label' : '当前状态' } } ,
{ key : 'destinationType' , label : '目的仓类型' , type : 'select' , placeholder : '全部' , options : [ { label : 'FBA 仓' , value : 'FBA' } , { label : '海外仓' , value : 'OVERSEAS' } , { label : '私人地址' , value : 'PRIVATE' } ] , props : { 'aria-label' : '目的仓类型' } } ,
@@ -112,10 +115,9 @@ const batchImportError = ref('')
const fixedOrderColumns : DataTableColumn [ ] = [
{ key : 'selection' , type : 'selection' , width : 48 , fixed : 'left' , reserveSelection : true } ,
{ key : 'actions' , label : '操作' , width : 284 , fixed : 'left' , align : 'left' , headerAlign : 'left' , slotName : 'actions' } ,
{ key : 'or der ' , label : '订单号 ' , width : 240 , fixed : 'left' , align : 'left' , headerAlign : 'left' , showOverflowTooltip : true , slotName : 'or der ' } ,
{ key : 'inboundC ode' , label : '入仓码 ' , width : 230 , fixed : 'left' , align : 'left' , headerAlign : 'left' , showOverflowTooltip : true , slotName : 'inboundC ode' } ,
]
const selectableOrderColumns : DataTableColumn [ ] = [
{ key : 'inboundCode' , label : '入仓码' , width : 230 , align : 'left' , headerAlign : 'left' , showOverflowTooltip : true , slotName : 'inboundCode' } ,
{ key : 'status' , label : '当前订单状态' , width : 126 , align : 'center' , headerAlign : 'center' , slotName : 'status' } ,
{ key : 'channel' , label : '渠道' , width : 150 , align : 'left' , headerAlign : 'left' , showOverflowTooltip : true , slotName : 'channel' } ,
{ key : 'transportMode' , label : '运输方式' , width : 104 , align : 'center' , headerAlign : 'center' , slotName : 'transportMode' } ,
@@ -147,7 +149,8 @@ const selectableOrderColumns: DataTableColumn[] = [
{ key : 'updatedAt' , label : '更新时间' , width : 164 , align : 'center' , headerAlign : 'center' , slotName : 'updatedAt' } ,
]
const allSelectableColumnKeys = selectableOrderColumns . map ( ( column ) => String ( column . key ) )
const visibleColumnKeys = ref < string [ ] > ( [ ... allSelectableColumnKeys ] )
const defaultVisibleColumnKeys = [ 'status' , 'channel' , 'transportMode' , 'pickupMethod' , 'receivingWarehouse' , 'createdAt' ]
const visibleColumnKeys = ref < string [ ] > ( [ ... defaultVisibleColumnKeys ] )
const orderedColumnKeys = ref < string [ ] > ( [ ... allSelectableColumnKeys ] )
const draggedColumnKey = ref ( '' )
const orderedSelectableColumns = computed ( ( ) => orderedColumnKeys . value
@@ -179,7 +182,7 @@ function dropOrderColumn(targetKey: string) {
function resetOrderColumns ( ) {
orderedColumnKeys . value = [ ... allSelectableColumnKeys ]
visibleColumnKeys . value = [ ... allSelectableColumnKeys ]
visibleColumnKeys . value = [ ... defaultVisibleColumnKeys ]
}
const tablePage = ref ( 1 )
const tablePageSize = ref ( 10 )
@@ -190,11 +193,13 @@ const selectedOrders = ref<FirstLegOrder[]>([])
const selectedOrderCount = computed ( ( ) => selectedOrders . value . length )
const selectedDraftOrders = computed ( ( ) => selectedOrders . value . filter ( ( order ) => order . status === 'DRAFT' ) )
const orderTableVersion = ref ( 0 )
type OrderQueue = 'all' | 'fee' | 'customs' | 'draft'
const activeOrderQueue = ref < OrderQueue > ( 'all' )
const editorOpen = ref ( false )
const orderFormRef = ref < FormInstance > ( )
const detailOpen = ref ( false )
const editingId = ref < string > ( )
const openingEditor = ref ( false )
const copyingOrderId = ref ( '' )
const detailId = ref < string > ( )
const saving = ref ( false )
const detailLoading = ref ( false )
@@ -430,9 +435,6 @@ function quickOrderDraft() {
const form = reactive < FirstLegOrderDraft > ( quickOrderDraft ( ) )
const serializedDraft = computed ( ( ) => JSON . stringify ( form ) )
const editorDirty = computed ( ( ) => ( editorOpen . value || isCreatePage . value ) && serializedDraft . value !== editorBaseline . value )
const woodenCargoLines = computed ( ( ) => form . items
. map ( ( item , index ) => item . woodenProduct ? index + 1 : null )
. filter ( ( index ) : index is number => index != null ) )
const unsavedRouteKey = props . createPage ? 'first-leg-order-create' : 'first-leg-orders'
useUnsavedWorkRegistration ( {
@@ -442,11 +444,10 @@ useUnsavedWorkRegistration({
dirty : editorDirty ,
} )
const filteredOrders = computed ( ( ) => store . orders . filter ( ( order ) => {
function matchesAppliedFilters ( order : FirstLegOrder ) {
const statusValue = appliedFilters . value . status
const createdRangeValue = appliedFilters . value . createdRange
const textValue = ( key : string ) => typeof appliedFilters . value [ key ] === 'string' ? String ( appliedFilters . value [ key ] ) . trim ( ) . toLowerCase ( ) : ''
const orderNo = textValue ( 'orderNo' )
const receivingWarehouseId = textValue ( 'receivingWarehouseId' )
const channelName = textValue ( 'channelName' )
const cargoKeyword = textValue ( 'cargoKeyword' )
@@ -454,7 +455,6 @@ const filteredOrders = computed(() => store.orders.filter((order) => {
const trackingNumber = textValue ( 'trackingNumber' )
const status = typeof statusValue === 'string' ? statusValue as FirstLegOrderStatus | '' : ''
const createdRange = Array . isArray ( createdRangeValue ) ? createdRangeValue . filter ( ( value ) : value is string => typeof value === 'string' ) : [ ]
const matchesOrderNo = ! orderNo || String ( order . orderNo ? ? '' ) . toLowerCase ( ) . includes ( orderNo )
const matchesWarehouse = ! receivingWarehouseId || order . receivingWarehouseId . toLowerCase ( ) === receivingWarehouseId
const matchesChannel = ! channelName || order . channelName . toLowerCase ( ) === channelName
const matchesCargo = ! cargoKeyword || [ order . businessSummary . inboundCode , ... order . skuNos , ... order . items . map ( ( item ) => item . sku ) ]
@@ -463,13 +463,34 @@ const filteredOrders = computed(() => store.orders.filter((order) => {
const matchesTrackingNumber = ! trackingNumber || order . trackingNumber . toLowerCase ( ) . includes ( trackingNumber )
const matchesStatus = ! status || order . status === status
const matchesDate = createdRange . length !== 2 || ( order . createdAt . slice ( 0 , 10 ) >= createdRange [ 0 ] && order . createdAt . slice ( 0 , 10 ) <= createdRange [ 1 ] )
return matchesOrderNo && matchesWarehouse && matchesChannel && matchesCargo && matchesDestinationType && matchesTrackingNumber && matchesStatus && matchesDate
return matchesWarehouse && matchesChannel && matchesCargo && matchesDestinationType && matchesTrackingNumber && matchesStatus && matchesDate
}
const queryMatchedOrders = computed ( ( ) => store . orders . filter ( matchesAppliedFilters ) )
const orderQueueCounts = computed ( ( ) => ( {
all : queryMatchedOrders . value . length ,
fee : queryMatchedOrders . value . filter ( ( order ) => order . businessSummary . customerFeeConfirmationRequired ) . length ,
customs : queryMatchedOrders . value . filter ( ( order ) => Boolean ( eligibleCustomsDetail ( order ) ) ) . length ,
draft : queryMatchedOrders . value . filter ( ( order ) => order . status === 'DRAFT' ) . length ,
} ) )
const filteredOrders = computed ( ( ) => queryMatchedOrders . value . filter ( ( order ) => {
if ( activeOrderQueue . value === 'fee' ) return order . businessSummary . customerFeeConfirmationRequired
if ( activeOrderQueue . value === 'customs' ) return Boolean ( eligibleCustomsDetail ( order ) )
if ( activeOrderQueue . value === 'draft' ) return order . status === 'DRAFT'
return true
} ) )
const pagedOrders = computed ( ( ) => {
const start = ( tablePage . value - 1 ) * tablePageSize . value
return filteredOrders . value . slice ( start , start + tablePageSize . value )
} )
function selectOrderQueue ( queue : OrderQueue ) {
activeOrderQueue . value = queue
tablePage . value = 1
selectedOrders . value = [ ]
orderTableVersion . value += 1
}
function eligibleCustomsDetail ( order : FirstLegOrder ) {
const value = customsByOrder [ order . id ]
if ( order . status !== 'LOADED' || ! value ? . merchantStatus ) return null
@@ -536,7 +557,7 @@ const progressSummaryItems = computed(() => {
{ label : '实测体积' , value : formatMetric ( summary . actualVolumeCbm ? ? inbound ? . actualVolumeCbm , 'm³' ) } ,
{ label : '预装清单' , value : preloadStatus ? ` ${ preloadStatus } ${ summary . preloadItemCount == null ? '' : ` · ${ summary . preloadItemCount } 项 ` } ` : '待更新' } ,
{ label : '拣货进度' , value : pickingProgress } ,
{ label : '柜次进度 ' , value : summary . cabinetNo || summary . containerNo || '—' } ,
{ label : '关联 柜次' , value : summary . cabinetNo || summary . containerNo || '—' } ,
{ label : '船期摘要' , value : transportReference || '待更新' } ,
{ label : '计费重量' , value : formatMetric ( summary . chargeableWeightKg , 'kg' ) } ,
{ label : '后续履约' , value : summary . deliveryTrackingNo || ( summary . deliveryCompletedAt ? ` 海外回传于 ${ formatDateTime ( summary . deliveryCompletedAt ) } ` : '待海外同步' ) } ,
@@ -567,7 +588,7 @@ async function loadPageData() {
const destinationRequest = isCreatePage . value && form . destinationType !== 'PRIVATE'
? loadDestinationWarehouses ( form . destinationType )
: Promise . resolve ( )
const orderPageRequest = isCreatePage . value
const orderPageRequest = isCreatePage . value || isDetailPage . value
? store . loadWarehouseType ( 'DOMESTIC' , true )
: store . load ( { includeDrafts : true } )
const addressRequest = isCreatePage . value ? loadOrderAddresses ( ) : Promise . resolve ( )
@@ -595,6 +616,8 @@ async function loadPageData() {
if ( addressResult . status === 'rejected' ) supportingDataErrors . value . push ( ` 企业地址簿加载失败: ${ errorMessage ( addressResult . reason ) } ` )
await autofillWarehouseSelections ( )
if ( isCreatePage . value && routeDraftId . value ) await loadRouteDraft ( routeDraftId . value )
else if ( isCreatePage . value && routeCopySourceId . value ) await loadCopiedOrder ( routeCopySourceId . value , routeCopySourceIsDraft . value )
if ( isDetailPage . value && routeDetailId . value ) await loadDetailPage ( routeDetailId . value )
supportingDataLoading . value = false
}
@@ -611,6 +634,39 @@ async function loadRouteDraft(id: string) {
markEditorClean ( )
}
async function loadCopiedOrder ( id : string , sourceIsDraft : boolean ) {
try {
const detailedOrder = sourceIsDraft ? await store . loadDraftDetail ( id ) : await store . loadDetail ( id )
const draft = copyDraftFromOrder ( detailedOrder )
if ( draft . destinationType !== 'PRIVATE' ) await loadDestinationWarehouses ( draft . destinationType )
const warehouseAvailable = availableWarehouses . value . some ( ( item ) => item . id === draft . receivingWarehouseId )
if ( ! warehouseAvailable ) {
draft . receivingWarehouseId = ''
draft . channelCode = ''
draft . channelName = ''
}
if ( draft . destinationType !== 'PRIVATE' ) {
const destinationAvailable = store . warehouses . some ( ( item ) => item . id === draft . destinationWarehouseId && item . warehouseType === draft . destinationType )
if ( ! destinationAvailable ) {
draft . destinationWarehouseId = ''
draft . destinationWarehouseName = ''
draft . amazonWarehouseCode = ''
}
}
editingId . value = undefined
replaceForm ( draft )
await Promise . all ( form . items . map ( ensureProductImagePreview ) )
if ( warehouseAvailable ) await loadSelectedWarehouse ( draft . receivingWarehouseId )
resetEditorValidation ( )
markEditorClean ( )
ElMessage . success ( '订单数据已复制,请核对后提交' )
} catch ( error ) {
supportingDataErrors . value . push ( ` 复制订单数据失败: ${ errorMessage ( error ) } ` )
}
}
async function loadDestinationWarehouses ( type : 'FBA' | 'OVERSEAS' | 'PRIVATE' ) {
if ( type === 'PRIVATE' ) return
destinationWarehousesLoading . value = true
@@ -635,12 +691,6 @@ function batchImportErrorMessage(error: unknown) {
] . filter ( Boolean ) . join ( ' · ' )
}
function getOrderCargoSummary ( order : FirstLegOrder ) {
if ( order . items . length ) return ` ${ order . items [ 0 ] ? . cargoNameCn || '货物' } ${ order . items . length > 1 ? ` 等 ${ order . items . length } 项 ` : '' } `
if ( order . businessSummary . totalPackageCount != null ) return ` ${ order . businessSummary . totalPackageCount } 箱 `
return '待更新'
}
onMounted ( ( ) => {
void loadPageData ( )
} )
@@ -659,15 +709,13 @@ watch(isCreatePage, (isCreate, wasCreate) => {
if ( wasCreate === false ) void loadPageData ( )
} , { immediate : true } )
watch ( detailOpen , ( open ) => {
if ( ! open && ! feeConfirmationOpen . value ) {
resetInboundBillingState ( )
if ( detailOrder . value ) releaseProductImagePreviews ( detailOrder . value . items )
}
watch ( routeDetailId , ( orderId , previousOrderId ) => {
if ( ! isDetailPage . value || ! orderId || orderId === previousOrderId ) return
void loadDetailPage ( orderId )
} )
watch ( feeConfirmationOpen , ( open ) => {
if ( ! open && ! detailOpen . value ) resetInboundBillingState ( )
if ( ! open && ! isDetailPage . value ) resetInboundBillingState ( )
} )
onBeforeUnmount ( ( ) => {
@@ -819,6 +867,21 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
}
}
function copyDraftFromOrder ( order : FirstLegOrder ) : FirstLegOrderDraft {
const draft = draftFromOrder ( order )
return {
... draft ,
inboundCode : '' ,
items : draft . items . map ( ( item ) => ( {
... item ,
id : ` copied-cargo- ${ ++ cargoSequence } ` ,
productImagePreviewUrl : '' ,
productImageUploading : false ,
productImageError : '' ,
} ) ) ,
}
}
function selectChannel ( code : string ) {
const channel = channelOptions . value . find ( ( item ) => item . channelCode === code )
form . channelName = channel ? . channelName ? ? ''
@@ -1196,8 +1259,12 @@ async function loadSelectedWarehouse(id: string | number | boolean | undefined)
async function autofillWarehouseSelections ( ) {
if ( ! isCreatePage . value || editingId . value ) return
if ( ! form . receivingWarehouseId && availableWarehouses . value . length === 1 ) {
form . receivingWarehouseId = availableWarehouses . value [ 0 ] . id
if ( ! form . receivingWarehouseId ) {
const yiwuWarehouse = availableWarehouses . value . find ( ( warehouse ) => warehouse . code . toUpperCase ( ) . includes ( 'YIWU' ) )
? ? availableWarehouses . value . find ( ( warehouse ) => warehouse . name . includes ( '义乌' ) )
if ( yiwuWarehouse ) form . receivingWarehouseId = yiwuWarehouse . id
}
if ( form . receivingWarehouseId ) {
await loadSelectedWarehouse ( form . receivingWarehouseId )
}
if ( form . destinationType !== 'PRIVATE' && ! form . destinationWarehouseId && availableDestinationWarehouses . value . length === 1 ) {
@@ -1211,6 +1278,9 @@ function getOrderRowActions(row: object): TableAction[] {
const actions : TableAction [ ] = [
{ key : 'detail' , label : '详情' , run : ( ) => openDetail ( order ) } ,
]
if ( canCreateOrder . value ) {
actions . push ( { key : 'copy' , label : '复制下单' , loading : copyingOrderId . value === order . id , run : ( ) => copyOrder ( order ) } )
}
if ( canConfirmInboundFee . value && order . businessSummary . customerFeeConfirmationRequired ) {
actions . push ( { key : 'fee' , label : '确认计费重' , run : ( ) => openFeeConfirmation ( order ) } )
}
@@ -1223,6 +1293,22 @@ function getOrderRowActions(row: object): TableAction[] {
return actions
}
async function copyOrder ( order : FirstLegOrder ) {
if ( copyingOrderId . value || ! canCreateOrder . value ) return
copyingOrderId . value = order . id
try {
await router . push ( {
name : 'first-leg-order-create' ,
query : {
copyFrom : order . id ,
... ( order . status === 'DRAFT' ? { copyDraft : '1' } : { } ) ,
} ,
} )
} finally {
copyingOrderId . value = ''
}
}
async function openCreate ( ) {
if ( availableWarehouses . value . length === 0 ) {
ElMessage . warning ( supportingDataLoading . value
@@ -1266,7 +1352,6 @@ async function openEdit(order: FirstLegOrder) {
replaceForm ( draft )
await Promise . all ( form . items . map ( ensureProductImagePreview ) )
if ( warehouseAvailable ) void loadSelectedWarehouse ( detailedOrder . receivingWarehouseId )
detailOpen . value = false
editorOpen . value = true
resetEditorValidation ( )
} catch ( error ) {
@@ -1277,15 +1362,18 @@ async function openEdit(order: FirstLegOrder) {
}
async function openDetail ( order : FirstLegOrder ) {
await router . push ( { name : 'first-leg-order-detail' , params : { orderId : order . id } } )
}
async function loadDetailPage ( orderId : string ) {
feeConfirmationOpen . value = false
detailId . value = order . id
detailId . value = orderId
detailCargoPage . value = 1
detailOpen . value = true
detailLoading . value = true
detailError . value = ''
void loadInboundBilling ( order )
try {
const detailedOrder = await store . loadDetail ( order . id )
const detailedOrder = await store . loadDetail ( orderId )
void loadInboundBilling ( detailedOrder )
// Load previews from the persisted file IDs after the store has replaced the detail row.
await Promise . all ( ( detailOrder . value ? . items ? ? detailedOrder . items ) . map ( ensureProductImagePreview ) )
} catch ( error ) {
@@ -1297,7 +1385,6 @@ async function openDetail(order: FirstLegOrder) {
}
function openFeeConfirmation ( order : FirstLegOrder ) {
detailOpen . value = false
resetInboundBillingState ( )
inboundBillingUnavailableMessage . value = '未推送计费重'
feeConfirmationOpen . value = true
@@ -1305,8 +1392,7 @@ function openFeeConfirmation(order: FirstLegOrder) {
}
async function retryDetail ( ) {
const order = detailId . value ? store . getById ( detailId . value ) : undefined
if ( order ) await openDetail ( order )
if ( detailId . value ) await loadDetailPage ( detailId . value )
}
function resetInboundBillingState ( ) {
@@ -1322,7 +1408,7 @@ function resetInboundBillingState() {
function isCurrentInboundBillingRequest ( sequence : number , orderId : string ) {
return sequence === inboundBillingRequestSequence
&& inboundBillingOrderId . value === orderId
&& ( detailOpen . value || feeConfirmationOpen . value )
&& ( isDetailPage . value || feeConfirmationOpen . value )
}
async function loadInboundBilling ( order : FirstLegOrder , unavailableMessage = '还未测量推送' ) {
@@ -1400,6 +1486,27 @@ function closeEditor() {
} )
}
async function resetCreateForm ( ) {
if ( saving . value ) return
if ( editorDirty . value ) {
try {
await ElMessageBox . confirm ( '确认清空当前填写的订单内容并恢复默认值?' , '重置订单' , {
type : 'warning' ,
confirmButtonText : '确认重置' ,
cancelButtonText : '继续填写' ,
} )
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return
throw error
}
}
editingId . value = undefined
replaceForm ( emptyDraft ( ) )
await autofillWarehouseSelections ( )
resetEditorValidation ( )
await router . replace ( { name : 'first-leg-order-create' } )
}
function addCargoItem ( ) {
form . items . push ( createEmptyCargoItem ( ` new-cargo- ${ ++ cargoSequence } ` ) )
resetEditorValidation ( )
@@ -1624,7 +1731,6 @@ function exportOrderInformation() {
exportingOrders . value = true
try {
const rows = selectedOrders . value . map ( ( order ) => [
displayListValue ( order . orderNo ) ,
displayListValue ( order . businessSummary . inboundCode ) ,
displayListValue ( order . channelName ) ,
order . transportMode ? getFirstLegTransportModeLabel ( order . transportMode ) : '-' ,
@@ -1651,7 +1757,7 @@ function exportOrderInformation() {
formatExportDateTime ( order . createdAt ) ,
] )
downloadCsv ( '订单信息.csv' , [ [
'订单号' , ' 入仓码', '渠道' , '运输方式' , '交货方式' , '预约提货时间' , '目的仓' , '目的仓类型' , '当前订单状态' , '箱数' , '快递公司' , '快递单号' , 'SKUS' ,
'入仓码' , '渠道' , '运输方式' , '交货方式' , '预约提货时间' , '目的仓' , '目的仓类型' , '当前订单状态' , '箱数' , '快递公司' , '快递单号' , 'SKUS' ,
'总毛重' , '总净重' , '总体积' , '总数量' , '计费单价' , '计费体积' , '计费重量' , '备注' ,
'货件编号' , '跟踪编号' , '国内仓入库时间' , '创建时间' ,
] , ... rows ] )
@@ -1792,6 +1898,7 @@ function viewImportedDrafts() {
}
async function validateEditorForm ( ) {
form . customsInspection = form . items . some ( ( item ) => item . woodenProduct ) ? true : null
try {
const policy = await getMerchantInboundCodePolicy ( )
inboundCodePolicy . value = policy
@@ -1870,7 +1977,7 @@ async function submitOrder() {
const knownCompanyAuditStatus = companyAuditLoaded . value ? tenantStore . company . auditStatus : undefined
const savedOrder = await store . saveDraft ( cloneDraft ( form ) , tenantStore . company . companyName , editingId . value )
editingId . value = savedOrder . id
const order = await store . submitSaved ( savedOrder . id , knownCompanyAuditStatus )
await store . submitSaved ( savedOrder . id , knownCompanyAuditStatus )
markEditorClean ( )
editorOpen . value = false
if ( isCreatePage . value ) {
@@ -1879,8 +1986,7 @@ async function submitOrder() {
ElMessage . success ( '订单已提交,等待平台审核' )
return
}
detailId . value = order . id
detailOpen . value = true
await loadPageData ( )
ElMessage . success ( '订单已提交,等待平台审核' )
} catch ( error ) {
ElMessage . error ( error instanceof Error ? error . message : '订单提交失败' )
@@ -1898,13 +2004,11 @@ async function searchOrders(model: QueryModel) {
tablePage . value = 1
try {
const statusValue = typeof model . status === 'string' ? model . status as FirstLegOrderStatus | '' : ''
const orderNo = typeof model . orderNo === 'string' ? model . orderNo . trim ( ) : ''
const cargoKeyword = typeof model . cargoKeyword === 'string' ? model . cargoKeyword . trim ( ) : ''
const range = Array . isArray ( createdRange ) ? createdRange . filter ( ( value ) : value is string => typeof value === 'string' ) : [ ]
const query = {
keyword : undefined ,
inboundCode : cargoKeyword || undefined ,
orderNo : orderNo || undefined ,
receivingWarehouseId : typeof model . receivingWarehouseId === 'string' && model . receivingWarehouseId ? model . receivingWarehouseId : undefined ,
mainStatus : statusValue && statusValue !== 'DRAFT' ? statusValue : undefined ,
draftOnly : statusValue === 'DRAFT' ,
@@ -1926,17 +2030,25 @@ async function searchOrders(model: QueryModel) {
< / script >
< template >
< section class = "first-leg-page" : class = "{ 'is-create-route': isCreatePage, 'client-ui': !isCreatePage }" >
< template v-if = "!isCreatePage" >
< section class = "first-leg-page" : class = "{ 'is-create-route': isCreatePage, 'is-detail-route': isDetailPage, 'client-ui': !isCreatePage }" >
< template v-if = "!isCreatePage && !isDetailPage " >
< QueryToolbar v-model = "filters" class="first-leg-query-toolbar" layout="grid" :fields="queryFields" ariaLabel="订单查询" @search="searchOrders" / >
< div class = "client-card order-table-card" >
< DataTable v -model :page = "tablePage" v -model :page-size = "tablePageSize" :data = "pagedOrders" :columns = "visibleOrderColumns" :key = "orderTableVersion" :loading = "store.loading" :total = "filteredOrders.length" : page -sizes = " [ 10 , 20 , 50 ] " row -key = " id " empty -text = " 暂无订单 " class = "prototype-order-table" :row-actions = "getOrderRowActions" @ selection -change = " handleOrderSelectionChange " >
< template # toolbar > < strong > 订单列表 < / strong > < span > 共 { { filteredOrders . length } } 条 < / span > < span v-if = "selectedOrderCount" > 已选 {{ selectedOrderCount }} 条 < / span > < / template >
< template # toolbar >
< nav class = "order-queue-tabs" aria -label = " 订单事项分类 " >
< button type = "button" : class = "{ active: activeOrderQueue === 'all' }" @click ="selectOrderQueue('all')" > 全部 < / button >
< button type = "button" : class = "{ active: activeOrderQueue === 'fee' }" @click ="selectOrderQueue('fee')" > 确认计费重 < span v-if = "orderQueueCounts.fee" class="queue-count" > {{ orderQueueCounts.fee }} < / span > < / button >
< button type = "button" : class = "{ active: activeOrderQueue === 'customs' }" @click ="selectOrderQueue('customs')" > 上传资料 < span v-if = "orderQueueCounts.customs" class="queue-count" > {{ orderQueueCounts.customs }} < / span > < / button >
< button type = "button" : class = "{ active: activeOrderQueue === 'draft' }" @click ="selectOrderQueue('draft')" > 草稿 < span v-if = "orderQueueCounts.draft" class="queue-count" > {{ orderQueueCounts.draft }} < / span > < / button >
< / nav >
< span v-if = "selectedOrderCount" > 已选 {{ selectedOrderCount }} 条 < / span >
< / template >
< template # toolbar -actions >
< el-popover placement = "bottom-end" :width = "320" trigger = "click" popper -class = " order -column -popper " >
< template # reference >
< el-button class = "column-settings-trigger" aria -label = " 设置订单列表显示列 " title = "设置显示列" > < SlidersHorizontal :size = "18" / > < / el-button >
< el-button class = "column-settings-trigger" aria -label = " 设置订单列表显示列 " title = "设置显示列" > < SlidersHorizontal :size = "18" / > 显示字段筛选 < / el-button >
< / template >
< div class = "column-settings-panel" >
< p > 拖动调整顺序 , 也可使用上下移动按钮 。 < / p >
@@ -1964,7 +2076,7 @@ async function searchOrders(model: QueryModel) {
< el-button :loading = "exportingOrders" : disabled = "selectedOrderCount === 0" @click ="exportOrderInformation" > < Download :size = "16" / > 导出订单信息 < / el-button >
< el-button v-if = "canSubmitOrder" :loading="batchSubmitting" :disabled="selectedDraftOrders.length === 0 || companySubmissionBlocked" @click="submitSelectedDraftOrders"><Send :size="17" / > 批量提交草稿 < / el-button >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" @click="openBatchImport"><Upload :size="17" / > 批量导入订单 < / el-button >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><FilePlus2 :size="17" / > 创建订单 < / el-button >
< el-button v-if = "props.showCreateAction && auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><FilePlus2 :size="17" / > 创建订单 < / el-button >
< / template >
< template # before -table >
@@ -1988,18 +2100,15 @@ async function searchOrders(model: QueryModel) {
< / div >
< / template >
< template # order = "{ row }" >
< button class = "order-number-link table-code" type = "button" : title = "(row as FirstLegOrder).orderNo || '—'" @click ="openDetail(row as FirstLegOrder)" > {{ ( row as FirstLegOrder ) .orderNo | | ' — ' }} < / button >
< / template >
< template # inboundCode = "{ row }" >
< button
v-if = "(row as FirstLegOrder).businessSummary.inboundCode"
class = "inbound-code-link table-ellipsis table-code"
class = "inbound-code-primary table-ellipsis table-code"
type = "button"
: title = "`查看入仓码 ${(row as FirstLegOrder).businessSummary.inboundCode} 的关联摘要 `"
@click ="openInboundCodeAssociation((row as FirstLegOrder).businessSummary.inboundCode )"
: title = "`查看入仓码 ${(row as FirstLegOrder).businessSummary.inboundCode} 的订单详情 `"
@click ="openDetail(row as FirstLegOrder )"
> { { ( row as FirstLegOrder ) . businessSummary . inboundCode } } < / button >
< span v-else > — < / span >
< span v-else class = "pending-inbound-code" > { { ( row as FirstLegOrder ) . status === 'DRAFT' ? '提交后生成' : '待生成' } } < / span >
< / template >
< template # channel = "{ row }" > < span class = "table-ellipsis" : title = "displayListValue((row as FirstLegOrder).channelName)" > { { displayListValue ( ( row as FirstLegOrder ) . channelName ) } } < / span > < / template >
< template # transportMode = "{ row }" > { { ( row as FirstLegOrder ) . transportMode ? getFirstLegTransportModeLabel ( ( row as FirstLegOrder ) . transportMode ) : '-' } } < / template >
@@ -2032,33 +2141,10 @@ async function searchOrders(model: QueryModel) {
< template # updatedAt = "{ row }" > { { formatDateTime ( ( row as FirstLegOrder ) . updatedAt ) } } < / template >
< template # empty >
< el-empty :description = "emptyDescription" >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><Plus :size="16" / > { { pageDataLoading ? '加载收货仓中' : ( availableWarehouses . length ? '创建第一笔订单' : '暂无可用收货仓' ) } } < / el-button >
< el-button v-if = "props.showCreateAction && auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><Plus :size="16" / > { { pageDataLoading ? '加载收货仓中' : ( availableWarehouses . length ? '创建第一笔订单' : '暂无可用收货仓' ) } } < / el-button >
< / el-empty >
< / template >
< / DataTable >
< div v-if = "!store.loadError" v-loading="store.loading" class="mobile-order-surface" >
< 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 >
< button v-if = "order.businessSummary.inboundCode" class="order-reference inbound-code-link" type="button" @click="openInboundCodeAssociation(order.businessSummary.inboundCode)" > 入仓码 {{ order.businessSummary.inboundCode }} < / button >
< span v-else class = "order-reference" > 入仓码 — < / span >
< span class = "order-reference" > { { order . referenceNo || '跟踪编号 —' } } < / span >
< dl > < div > < dt > 目的地 < / dt > < dd > { { order . destination || '待更新' } } < / dd > < / div > < div > < dt > 目的仓类型 < / dt > < dd > { { destinationTypeLabels [ order . destinationType ] } } < / dd > < / div > < div > < dt > 收货仓 < / dt > < dd > { { order . receivingWarehouseName || '—' } } < / dd > < / div > < div > < dt > 货物摘要 < / dt > < dd > { { getOrderCargoSummary ( order ) } } < / dd > < / div > < div > < dt > 运输方式 < / dt > < dd > { { order . transportMode ? getFirstLegTransportModeLabel ( order . transportMode ) : '待更新' } } < / dd > < / div > < div > < dt > 交货方式 < / dt > < dd > { { pickupMethodLabels [ order . pickupMethod ] } } < / dd > < / div > < div > < dt > 预约提货时间 < / dt > < dd > { { order . pickupMethod === 'PICKUP' && order . requestedPickupAt ? formatDateTime ( order . requestedPickupAt ) : '—' } } < / dd > < / div > < div > < dt > 柜号 / 柜次 < / dt > < dd > { { order . businessSummary . cabinetNo || order . businessSummary . containerNo || '—' } } < / dd > < / div > < div v-if = "order.businessSummary.vesselName || order.businessSummary.voyageNo"><dt>船名 / 航次</dt><dd>{{ [order.businessSummary.vesselName, order.businessSummary.voyageNo].filter(Boolean).join(' · ') }}</dd></div><div v-if="order.businessSummary.sailingAt" > < dt > 预计船期 < / dt > < dd > { { formatDateTime ( order . businessSummary . sailingAt ) } } < / dd > < / div > < / dl >
< footer >
< span > 更新于 { { formatDateTime ( order . updatedAt ) } } < / span >
< div >
< el-button link type = "primary" @click ="openDetail(order)" > 详情 < / el -button >
< el-button v-if = "canConfirmInboundFee && order.businessSummary.customerFeeConfirmationRequired" link type="primary" @click="openFeeConfirmation(order)" > 确认计费重 < / el -button >
< el-button v-if = "canEditOrder && canClientEditFirstLegOrder(order.status)" link type="primary" :loading="openingEditor" @click="openEdit(order)" > 编辑 < / el -button >
< el-button v-if = "eligibleCustomsDetail(order)" link type="primary" @click="openCustomsDocuments(order)" > 报关资料 < / el -button >
< / div >
< / footer >
< / article >
< / div >
< el-empty v-else :description = "emptyDescription" >
< el-button v-if = "auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><Plus :size="16" / > { { pageDataLoading ? '加载收货仓中' : ( availableWarehouses . length ? '创建第一笔订单' : '暂无可用收货仓' ) } } < / el-button >
< / el-empty >
< / div >
< / div >
< / template >
@@ -2069,10 +2155,10 @@ async function searchOrders(model: QueryModel) {
< / div >
< el-alert v-if = "isCreatePage && !supportingDataLoading && availableWarehouses.length === 0" title="当前没有可用的平台收货仓" description="请联系平台维护并启用收货仓后再创建订单。" type="warning" :closable="false" show -icon / >
< FirstLegOrderEditorShell v-model = "editorOpen" :page="isCreatePage" :title="editingId ? '编辑订单草稿' : '创建订单'" :before-close="requestCloseEditor" @back="closeEditor" >
< FirstLegOrderEditorShell v-model = "editorOpen" :page="isCreatePage" :title="editingId ? '编辑订单草稿' : (routeCopySourceId ? '复制订单' : '创建订单') " :before-close="requestCloseEditor" @back="closeEditor" >
< el -form ref = "orderFormRef" v-loading = "isCreatePage && supportingDataLoading" :model="form" :rules="orderFormRules" label-position="top" class="order-form prototype-order-form" :class="{ 'is-dialog-order-form': !isCreatePage }" scroll -to -error >
< section class = "order-form-section prototype-section" >
< div class = "order-section-heading" > < div > < h3 > 客户下单 < / h3 > < p > 先选择货物如何送到国内收货仓 , 并填写本次运输安排 。 < / p > < / div > < / div >
< section class = "order-form-section prototype-section order-basic-section " >
< div class = "order-section-heading" > < span class = "order-step-badge" > 01 < / span > < h3 > 客户下单 < / h3 > < span class = "order-section-context" > 国内交货与运输信息 < / span > < / div >
< div class = "prototype-form-flow shipping-form-flow" >
< el-form-item label = "履约类型" prop = "fulfillmentType" required class = "prototype-form-row prototype-form-row--tabs" > < el-radio-group v-model = "form.fulfillmentType" class="segmented-options" @change="changeFulfillmentType"><el-radio-button value="STANDARD">普通头程</el-radio-button><el-radio-button value="ONE_CLICK_DROPSHIP">一件代发</el-radio-button></el-radio-group><small v-if="form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" class="field-guidance" > 测量完成后不办理国内库存入库 , 将直接推送 PDA 合托 。 < / small > < / el -form -item >
< el-form-item v-if = "form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.lxCode" maxlength="128" placeholder="请输入LX入库单号" /><small class="field-guidance" > 用于绑定领星入库单 , 提交后可在尾程订单 、 入库单和出库单中查询 。 < / small > < / el -form -item >
@@ -2105,14 +2191,13 @@ async function searchOrders(model: QueryModel) {
< / el -form -item >
< el-form-item label = "买单 / 退税" prop = "taxMode" required > < el-radio-group v-model = "form.taxMode"><el-radio value="BUY_DECLARATION">买单</el-radio><el-radio value="TAX_REFUND" > 退税 < / el -radio > < / el-radio-group > < / el-form-item >
< el-form-item label = "是否整柜" prop = "transportMode" required > < el-radio-group v-model = "form.transportMode"><el-radio value="FCL">整柜</el-radio><el-radio value="LCL" > 散货 < / el -radio > < / el-radio-group > < / el-form-item >
< el-form-item label = "是否商检" prop = "customsInspection" > < el-select v-model = "form.customsInspection" clearable placeholder="暂不选择"><el-option label="是" :value="true" /><el-option label="否" :value="false" /></el-select><small v-if="woodenCargoLines.length && form.customsInspection !== true" class="field-guidance field-guidance--error" > 第 {{ woodenCargoLines.join ( ' 、 ' ) }} 条货物为木制品 , 请将 “ 是否商检 ” 选择为 “ 是 ” 。 < / small > < / 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.customerRemark" type="textarea" :rows="3" resize="vertical" maxlength="300" show-word-limit placeholder="选填,例如:入仓前请电话联系" / > < / el-form-item >
< / div >
< / section >
< section class = "order-form-section prototype-section destination-section" >
< div class = "order-section-heading destination-section-heading" > < div > < h3 > 目的地信息 < / h3 > < / div > < / div >
< div class = "order-section-heading destination-section-heading" > < span class = "order-step-badge" > 02 < / span > < h3 > 目的地信息 < / h3 > < span class = "order-section-context" > 美国目的仓与收件信息 < / span > < / div >
< div class = "prototype-form-flow destination-form-flow" >
< el-form-item label = "目的仓类型" prop = "destinationType" required class = "prototype-form-row prototype-form-row--tabs destination-type-row" > < el-radio-group v-model = "form.destinationType" class="segmented-options destination-type-options" @change="changeDestinationType"><el-radio-button v-if="form.fulfillmentType !== 'ONE_CLICK_DROPSHIP'" value="FBA">FBA 仓</el-radio-button><el-radio-button value="OVERSEAS">海外仓</el-radio-button><el-radio-button v-if="form.fulfillmentType !== 'ONE_CLICK_DROPSHIP'" value="PRIVATE" > 私人仓 < / el -radio -button > < / el-radio-group > < / el-form-item >
@@ -2150,7 +2235,7 @@ async function searchOrders(model: QueryModel) {
< table class = "cargo-edit-table" >
< thead > < tr >
< th class = "cargo-check-cell" > < el-checkbox :model-value = "allCargoSelected" :indeterminate = "someCargoSelected" aria -label = " 全选货物 " @change ="toggleAllCargoItems" / > < / th > < th class = "cargo-index-cell" > # < / th >
< th > SKU NO . < small > SKU必填 < / small > < / th > < th > 中文品名 < / th > < th class = "cargo-image-column" > 图片 < / th > < th > 英文品名 < / th > < th > 报关 HS CODE < / th > < th > 清关 HS CODE < / th > < th > 包装类型 < / th > < th > 材质 < / th > < th class = "cargo-switch-column" > 危险品 < / th > < th class = "cargo-switch-column" > 木制品 < small > 勾选后须 商检 < / small > < / th >
< th > SKU NO . < small > { { form . destinationType === 'FBA' || form . fulfillmentType === 'ONE_CLICK_DROPSHIP' ? '必填' : '选填' } } < / small > < / th > < th > 中文品名 < / th > < th class = "cargo-image-column" > 图片 < / th > < th > 英文品名 < / th > < th > 报关 HS CODE < / th > < th > 清关 HS CODE < / th > < th > 包装类型 < / th > < th > 材质 < / th > < th class = "cargo-switch-column" > 危险品 < / th > < th class = "cargo-switch-column" > 木制品 < small > 勾选后自动 商检 < / small > < / th >
< th class = "cargo-number-column" > 箱规长 cm < / th > < th class = "cargo-number-column" > 箱规宽 cm < / th > < th class = "cargo-number-column" > 箱规高 cm < / th > < th class = "cargo-number-column" > 每箱数量 < / th > < th class = "cargo-number-column" > 箱数 < / th > < th class = "cargo-number-column" > < span class = "required-field-mark" aria -hidden = " true " > * < / span > 总毛重 < / th > < th class = "cargo-number-column" > < span class = "required-field-mark" aria -hidden = " true " > * < / span > 总体积 < / th > < th class = "cargo-number-column" > 总数量 < / th > < th class = "cargo-number-column" > 总净重 < / th >
< th > 清关申报单价 USD < / th > < th > 清关申报金额 USD < / th > < th > 用途 < / th > < th > 型号 < / th > < th > 品牌 < / th > < th class = "cargo-dimensions-column" > 产品规格 长 × 宽 × 高 < / th > < th > 货件编号 < small > 货件编号必填 < / small > < / th > < th > 跟踪编号 < small > 跟踪编号必填 < / small > < / th > < th > UPC NO . < / th > < th > 备注 < / th > < th class = "cargo-operation-column" > 操作 < / th >
< / tr > < / thead >
@@ -2161,7 +2246,7 @@ async function searchOrders(model: QueryModel) {
< td class = "cargo-image-column" > < div class = "cargo-table-image" > < img v-if = "item.productImagePreviewUrl" :src="item.productImagePreviewUrl" :alt="`${item.cargoNameCn || `货物 ${index + 1}`}产品图片`" /><el-upload v-else :show-file-list="false" accept=".jpg,.jpeg,.png,image/jpeg,image/png" :disabled="item.productImageUploading" :http-request="(options: UploadRequestOptions) => uploadCargoProductImage(item, options)"><el-button text :loading="item.productImageUploading" aria-label="上传图片"><ImagePlus :size="17" /></el-button></el-upload><el-button v-if="item.productImagePreviewUrl" class="cargo-image-remove" text type="danger" aria-label="移除图片" @click="removeCargoProductImage(item)"><Trash2 :size="12" / > < / el-button > < / div > < / td >
< td > < el-input v-model = "item.cargoNameEn" maxlength="128" placeholder="English Name" /></td><td><el-input v-model="item.hsCode" maxlength="20" placeholder="HS Code" /></td><td><el-input v-model="item.customsHsCode" maxlength="20" placeholder="HS Code" / > < / td >
< td > < span v-if = "isAdditionalMixedSku(item, index)" class="shared-carton-value">同箱</span><el-form-item v-else :prop="`items.${index}.packageType`" required :rules="requiredSelectRules('请选择包装类型')"><el-select v-model="item.packageType" @change="syncPackingGroupFields(item)"><el-option label="纸箱" value="CARTON" /><el-option label="托盘" value="PALLET" /><el-option label="木箱" value="WOODEN_CASE" /></el-select></el-form-item></td><td><el-input v-model="item.material" maxlength="128" placeholder="材质" / > < / td >
< td class = "cargo-switch-column" > < el-switch v-model = "item.dangerousGoods" :aria-label="`第 ${index + 1} 条货物是否危险品`" /></td><td class="cargo-switch-column"><div class="cargo-switch-cell"><el-switch v-model="item.woodenProduct" :aria-label="`第 ${index + 1} 条货物是否木制品`" /><small v-if="item.woodenProduct && form.customsInspection !== true" > 第 {{ index + 1 }} 条须 商检< / small > < / div > < / td >
< td class = "cargo-switch-column" > < el-switch v-model = "item.dangerousGoods" :aria-label="`第 ${index + 1} 条货物是否危险品`" /></td><td class="cargo-switch-column"><div class="cargo-switch-cell"><el-switch v-model="item.woodenProduct" :aria-label="`第 ${index + 1} 条货物是否木制品`" /><small v-if="item.woodenProduct" > 自动 商检< / small > < / div > < / td >
< td > < span v-if = "isAdditionalMixedSku(item, index)" class="shared-carton-value">同上</span><el-input-number v-else v-model="item.lengthCm" :min="0" :precision="2" :controls="false" placeholder="长" @change="syncPackingGroupFields(item)" /></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同上</span><el-input-number v-else v-model="item.widthCm" :min="0" :precision="2" :controls="false" placeholder="宽" @change="syncPackingGroupFields(item)" /></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同上</span><el-input-number v-else v-model="item.heightCm" :min="0" :precision="2" :controls="false" placeholder="高" @change="syncPackingGroupFields(item)" / > < / td >
< td > < el-form-item :prop = "`items.${index}.unitsPerPackage`" required : rules = "positiveNumberRules('每箱数量必须大于 0', true)" > < el-input-number v-model = "item.unitsPerPackage" :min="1" :controls="false" placeholder="每箱数量" @change="syncSkuQuantity(item)" /></el-form-item></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同箱</span><el-form-item v-else :prop="`items.${index}.packageCount`" required :rules="positiveNumberRules('箱数必须大于 0', true)"><el-input-number v-model="item.packageCount" :min="1" :controls="false" placeholder="箱数" @change="syncPackingGroupFields(item)" /></el-form-item></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同箱</span><el-form-item v-else :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('总毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="3" :controls="false" placeholder="总毛重" @change="syncPackingGroupFields(item)" /></el-form-item></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同箱</span><el-form-item v-else :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('总体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="4" :controls="false" placeholder="总体积" @change="syncPackingGroupFields(item)" /></el-form-item></td><td><span v-if="isAdditionalMixedSku(item, index)" class="derived-sku-value">{{ item.pieces }}</span><el-form-item v-else :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('总数量必须大于 0', true)"><el-input-number v-model="item.pieces" :min="1" :controls="false" placeholder="总数量" /></el-form-item></td><td><span v-if="isAdditionalMixedSku(item, index)" class="shared-carton-value">同箱</span><el-input-number v-else v-model="item.netWeightKg" :min="0" :precision="3" :controls="false" placeholder="净重" @change="syncPackingGroupFields(item)" / > < / td >
< td > < el-input-number v-model = "item.unitDeclaredValue" :min="0" :precision="2" :controls="false" placeholder="申报单价" /></td><td><el-input-number v-model="item.declaredValue" :min="0" :precision="2" :controls="false" placeholder="申报总价" /></td><td><el-input v-model="item.usage" maxlength="128" placeholder="用途" /></td><td><el-input v-model="item.model" maxlength="64" placeholder="型号" /></td><td><el-input v-model="item.brand" maxlength="64" placeholder="品牌" / > < / td >
@@ -2170,7 +2255,7 @@ async function searchOrders(model: QueryModel) {
< / tr > < / tbody >
< / table >
< / div >
< div class = "cargo-table-hint" > < span v-if = "form.fulfillmentType === 'ONE_CLICK_DROPSHIP'" > 每个箱组默认装一个 SKU ; 同一箱内有多个 SKU 时点击 “ 添加 SKU ” , 箱数 、 箱规 、 重量和体积只填写一次 。 < / span > < span v-else > SKU NO. 、 货件编号 、 跟踪编号为 FBA 货物必填项 , UPC NO. 选填 < / span > < / div >
< div class = "cargo-table-hint" > < span v-if = "form.fulfillmentType === 'ONE_CLICK_DROPSHIP'">一件代发必须填写 SKU;每个箱组默认装一个 SKU,同一箱内有多个 SKU 时点击“添加 SKU”,箱数、箱规、重量和体积只填写一次。</span><span v-else-if="form.destinationType === 'FBA'" > SKU NO. 、 货件编号 、 跟踪编号为 FBA 货物必填项 , UPC NO. 选填 < / span > < span v-else > SKU NO. 和 UPC NO. 均为选填项 < / span > < / div >
< / section >
< section class = "order-form-section prototype-section cost-estimate-section" aria -label = " 费用预估 " >
@@ -2208,7 +2293,7 @@ async function searchOrders(model: QueryModel) {
< / div >
< / section >
< / el-form >
< 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 >
< template # footer > < div class = "editor-actions" > < el-button v-if = "isCreatePage" :disabled= "saving" @click="resetCreateForm"><RotateCcw :size="16" />重置</el-button><el-button v-else :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')" class="submit-order-button" type="primary" :loading="saving" @click="submitOrder"><Send :size="17 " / > { { companySubmissionBlocked ? '提交前需完成资料审核' : '提交订单' } } < / el-button > < / div > < / template >
< / FirstLegOrderEditorShell >
< el-dialog v-model = "batchImportOpen" title="Excel 批量创建订单草稿" width="680px" destroy -on -close append -to -body >
@@ -2241,7 +2326,7 @@ async function searchOrders(model: QueryModel) {
< template # footer > < div class = "editor-actions" > < el-button v-if = "batchImportResult?.result.succeeded && auth.hasPermission('order:first-leg:list')" :disabled="batchImportSubmitting" @click="viewImportedDrafts">查看订单草稿</el-button><el-button :disabled="batchImportSubmitting" @click="batchImportOpen = false">关闭</el-button><el-button type="primary" :loading="batchImportSubmitting" :disabled="!batchImportFile" @click="submitBatchImport" > 开始导入并创建草稿 < / el -button > < / div > < / template >
< / el-dialog >
< el-dialog v-model = "d etailOpen " class="first-leg-detail-dialog" title="订单详情" width="94vw" top="3vh" destroy -on -close append -to -body >
< section v-if = "isD etailPage " class="first-leg-detail-page" aria-label="订单详情" >
< div v-loading = "detailLoading" class="order-detail-content" >
< div v-if = "detailError" class="order-load-state is-compact" role="alert" >
< AlertTriangle :size = "24" / >
@@ -2250,13 +2335,16 @@ 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 >
< el-tag :type = "g etFirstLegStatusTone(detailOrder.status)" effect = "light" > { { getFirstLegStatusLabel ( detailOrder . status ) } } < / el-tag >
< div class = "detail-heading-main" >
< el-button link type = "primary" class = "d etail-back-action" @click ="router.push({ name: 'first-leg-orders' })" > ← 返回列表 < / el -button >
< div > < h2 > { { detailOrder . businessSummary . inboundCode || ( detailOrder . status === 'DRAFT' ? '入仓码提交后生成' : '入仓码待生成' ) } } < / h2 > < p > { { getFirstLegTransportModeLabel ( detailOrder . transportMode ) } } · { { detailOrder . origin || '起运地待更新' } } → { { detailOrder . destination || '目的地待更新' } } < / p > < / div >
< / div >
< div class = "detail-heading-actions" > < el-tag :type = "getFirstLegStatusTone(detailOrder.status)" effect = "light" > { { getFirstLegStatusLabel ( detailOrder . status ) } } < / el-tag > < el-button v-if = "canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" type="primary" plain :loading="openingEditor" @click="openEdit(detailOrder)"><Pencil :size="16" / > 编辑草稿 < / el-button > < / div >
< / div >
< section class = "detail-section" >
< h3 > 订单与运输 < / h3 >
< dl class = "order-detail-grid" >
< div > < dt > 入仓码 < / dt > < dd > < button v-if = "detailOrder.businessSummary.inboundCode" class="inbound-code-link table-code" type="button" @click="openInboundCodeAssociation(detailOrder.businessSummary.inboundCode)" > {{ detailOrder.businessSummary.inboundCode }} < / button > < span v-else > — < / span > < / dd > < / div >
< div > < dt > 入仓码 < / dt > < dd > < button v-if = "detailOrder.businessSummary.inboundCode" class="inbound-code-link table-code" type="button" @click="openInboundCodeAssociation(detailOrder.businessSummary.inboundCode)" > {{ detailOrder.businessSummary.inboundCode }} < / button > < span v-else > {{ detailOrder.status = = = ' DRAFT ' ? ' 提交后生成 ' : ' 待生成 ' }} < / span > < / 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 >
@@ -2277,10 +2365,10 @@ async function searchOrders(model: QueryModel) {
< / 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 >
< dl class = "party-detail-grid" >
< div > < dt > 发货方 < / dt > < dd > < strong > { { detailOrder . shipperPartySnapshot ? . name || detailOrder . shipper || '未提供' } } < / strong > < span v-if = "detailOrder.shipperPartySnapshot" > {{ detailOrder.shipperPartySnapshot.contactName | | ' 未填写联系人 ' }} · {{ detailOrder.shipperPartySnapshot.phone | | ' 未填写电话 ' }} · {{ detailOrder.shipperPartySnapshot.email | | ' 未填写邮箱 ' }} < br / > { { detailOrder . shipperPartySnapshot . countryRegion } } { { detailOrder . shipperPartySnapshot . province } } { { detailOrder . shipperPartySnapshot . city } } { { detailOrder . shipperPartySnapshot . address } } { { detailOrder . shipperPartySnapshot . postalCode } } < / span > < / dd > < / div >
< div > < dt > 收货方 < / dt > < dd > < strong > { { detailOrder . consigneePartySnapshot ? . name || detailOrder . consignee || detailOrder . destinationWarehouseName || '未提供' } } < / strong > < span v-if = "detailOrder.consigneePartySnapshot" > {{ detailOrder.consigneePartySnapshot.contactName | | ' 未填写联系人 ' }} · {{ detailOrder.consigneePartySnapshot.phone | | ' 未填写电话 ' }} · {{ detailOrder.consigneePartySnapshot.email | | ' 未填写邮箱 ' }} < br / > { { detailOrder . consigneePartySnapshot . countryRegion } } { { detailOrder . consigneePartySnapshot . province } } { { detailOrder . consigneePartySnapshot . city } } { { detailOrder . consigneePartySnapshot . address } } { { detailOrder . consigneePartySnapshot . postalCode } } < / span > < span v-else > 平台目的仓地址以最终履约安排为准 < / span > < / dd > < / div >
< / dl >
< 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" >
@@ -2289,28 +2377,28 @@ async function searchOrders(model: QueryModel) {
< div v-for = "item in progressSummaryItems" :key="item.label" > < dt > { { item . label } } < / dt > < dd > { { item . value } } < / dd > < / div >
< / dl >
< / section >
< section v-if = "detailOrder.status !== 'DRAFT'" class="detail-section " >
< h3 > 柜次进度 < / h3 >
< div v-if = "cabinetP rogressItems.length" class="cabinet-progress-list " >
< article v-for = "cabinet in cabinetProgressItems" :key="cabinet.cabinetId" class="cabinet-progress-card" >
< header > < strong > { { cabinet . cabinetNo || '—' } } < / strong > < el-tag size = "small" effect = "plain" > { { cabinet . status || '待更新' } } < / el-tag > < / header >
< dl >
< div > < dt > 柜号 < / dt > < dd > { { cabinet . cabinetNo || '— ' } } < / dd > < / div >
< div > < dt > 船名 / 航次 < / dt > < dd > { { [ cabinet . vesselName , cabinet . voyageNo ] . filter ( Boolean ) . join ( ' · ' ) || '待更新' } } < / dd > < / div >
< div > < dt > 预计船期 < / dt > < dd > { { cabinet . sailingAt ? formatDateTime ( cabinet . sailingAt ) : '待更新' } } < / dd > < / div >
< / dl >
< / article >
< / div >
< el-empty v-else description = "暂无柜次分配信息" :image-size = "50" / >
< / section >
< OrderProgressPanel :order = "detailOrder" >
< template # cabinet >
< div class = "cabinet-p rogress-detail " >
< strong > 柜次明细 < / strong >
< el-table v-if = "cabinetProgressItems.length" :data="cabinetProgressItems" border size="small" class="cabinet-progress-table" row-key="cabinetId" >
< el -table -column prop = "cabinetNo" label = "柜号 / 柜次" min -width = " 150 " > < template # default = "{ row }" > < span class = "table-code" > { { row . cabinetNo || '—' } } < / span > < / template > < / el-table-column >
< el-table-column label = "船名 / 航次" min -width = " 180 " > < template # default = "{ row }" > { { [ row . vesselName , row . voyageNo ] . filter ( Boolean ) . join ( ' · ' ) || '待更新 ' } } < / template > < / el-table-column >
< el-table-column label = "预计船期" width = "170" > < template # default = "{ row }" > { { row . sailingAt ? formatDateTime ( row . sailingAt ) : '待更新' } } < / template > < / el-table-column >
< el-table-column prop = "status" label = "当前节点" width = "120" > < template # default = "{ row }" > < el-tag size = "small" effect = "plain" > { { row . status || '待更新' } } < / el-tag > < / template > < / el-table-column >
< / el-table >
< p v-else class = "detail-inline-empty" > 尚未分配柜次 , 后续柜次节点将在这里同步 。 < / p >
< / div >
< / template >
< / OrderProgressPanel >
< 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 >
< dl v-if = "detailOrder.expressCompanyName || detailOrder.trackingNumber" class="express-information"><div><dt>快递公司</dt><dd>{{ detailOrder.expressCompanyName || '待平台补充' }}</dd></div><div><dt>快递单号</dt><dd class="table-code" > {{ detailOrder.trackingNumber | | ' 待平台补充 ' }} < / dd > < / div > < / dl >
< 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 >
< dl v-if = "detailSummary" class="cargo-summary" > < div > < dt > 明细 < / dt > < dd > { { detailOrder . items . length } } 项 < / dd > < / div > < div > < dt > 件数 < / dt > < dd > { { detailSummary . pieces } } < / dd > < / div > < div > < dt > 毛重 < / dt > < dd > { { detailSummary . grossWeightKg . toLocaleString ( ) } } kg < / dd > < / div > < div > < dt > 体积 < / dt > < dd > { { detailSummary . volumeCbm } } m³ < / dd > < / div > < / dl >
< div v-if = "detailOrder.items.length" class="cargo-detail-table-wrap" >
< el -table :data = "pagedDetailCargoItems" border stripe class = "cargo-detail-table" row -key = " id " >
< el-table-column type = "index" label = "#" width = "54" align = "center" : index = "(index: number) => (detailCargoPage - 1) * detailCargoPageSize + index + 1" / >
@@ -2354,7 +2442,6 @@ async function searchOrders(model: QueryModel) {
< / div >
< el-empty v-else description = "暂无货物明细" :image-size = "56" / >
< / section >
< OrderProgressPanel :order = "detailOrder" / >
< InboundBillingPanel
:data = "inboundBilling"
:loading = "inboundBillingLoading"
@@ -2367,8 +2454,7 @@ async function searchOrders(model: QueryModel) {
/ >
< / template >
< / div >
< template # footer > < div v-if = "detailOrder" class="editor-actions"><el-button @click="detailOpen = false">关闭</el-button><el-button v-if="canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" :loading="openingEditor" @click="openEdit(detailOrder)"><Pencil :size="16" / > 编辑草稿 < / el-button > < / div > < / template >
< / el-dialog >
< / section >
< InboundBillingPanel
v-if = "feeConfirmationOpen"
dialog -only
@@ -2398,9 +2484,10 @@ async function searchOrders(model: QueryModel) {
< / template >
< style scoped >
. first - leg - page { display : flex ; flex - direction : column ; gap : var ( -- client - section - gap ) ; }
. first - leg - page { display : flex ; min - width : 0 ; flex - direction : column ; gap : var ( -- client - section - gap ) ; }
: global ( . client - main : has ( > . first - leg - page : not ( . is - create - route ) ) > . client - page - heading ) { margin - bottom : 0 ; }
: global ( . client - main : has ( > . first - leg - page : not ( . is - create - route ) ) > . client - page - heading > div ) { position : absolute ; width : 1 px ; height : 1 px ; margin : - 1 px ; padding : 0 ; overflow : hidden ; border : 0 ; clip : rect ( 0 0 0 0 ) ; white - space : nowrap ; }
: global ( . client - main : has ( > . first - leg - page . is - detail - route ) > . client - page - heading ) { display : none ; }
. 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 ; }
@@ -2413,6 +2500,12 @@ async function searchOrders(model: QueryModel) {
. supporting - data - error span { overflow - wrap : anywhere ; color : var ( -- muted ) ; font - size : 11 px ; line - height : 1.55 ; }
. order - row - actions , . editor - actions { display : flex ; justify - content : flex - end ; gap : 8 px ; }
. order - table - card { padding : 0 ; overflow : hidden ; border : 1 px solid var ( -- client - border ) ; border - radius : var ( -- client - radius ) ; box - shadow : none ; }
. order - queue - tabs { display : flex ; align - items : center ; gap : 4 px ; }
. order - queue - tabs button { position : relative ; min - height : 34 px ; padding : 0 12 px ; border : 1 px solid transparent ; border - radius : 7 px ; background : transparent ; color : # 667085 ; cursor : pointer ; font : inherit ; font - weight : 600 ; }
. order - queue - tabs button : hover { color : var ( -- client - action ) ; }
. order - queue - tabs button . active { border - color : # cfe0ff ; background : # f3f7ff ; color : var ( -- client - action ) ; }
. order - queue - tabs button : focus - visible { outline : 2 px solid var ( -- primary ) ; outline - offset : 2 px ; }
. queue - count { position : absolute ; display : grid ; min - width : 18 px ; height : 18 px ; place - items : center ; padding : 0 4 px ; border : 2 px solid # fff ; border - radius : 999 px ; background : # ef5350 ; color : # fff ; font - size : 10 px ; line - height : 1 ; right : - 4 px ; top : - 8 px ; }
. batch - import - dialog { display : flex ; flex - direction : column ; gap : 18 px ; }
. batch - import - actions { display : flex ; min - width : 0 ; align - items : center ; flex - wrap : wrap ; gap : 10 px ; }
. batch - import - actions > span { min - width : 0 ; overflow : hidden ; color : var ( -- muted ) ; font - size : 12 px ; text - overflow : ellipsis ; white - space : nowrap ; }
@@ -2421,7 +2514,7 @@ async function searchOrders(model: QueryModel) {
. batch - import - result > span , . batch - import - result li { color : var ( -- muted ) ; font - size : 12 px ; line - height : 1.55 ; }
. batch - import - result - actions { display : flex ; margin - top : 4 px ; }
. batch - import - result ul { max - height : 180 px ; margin : 4 px 0 0 ; padding - left : 20 px ; overflow - y : auto ; }
. column - settings - trigger { width : 40 px ; min - width : 40 px ; height : 36 px ; padding : 0 ; color : # 53627 b ; }
. column - settings - trigger { min - width : 116 px ; height : 36 px ; padding : 0 12 px ; color : # 53627 b ; }
: global ( . order - column - popper . el - popover ) { padding : 0 ; border - color : # e3e8f0 ; border - radius : 4 px ; box - shadow : 0 8 px 24 px rgba ( 29 , 45 , 74 , .12 ) ; }
: global ( . order - column - popper . column - settings - panel ) { padding : 14 px 10 px 10 px ; }
: global ( . order - column - popper . column - settings - panel > p ) { margin : 0 8 px 10 px ; color : # 778398 ; font - size : 12 px ; line - height : 1.5 ; }
@@ -2443,33 +2536,61 @@ async function searchOrders(model: QueryModel) {
. order - load - state strong { color : var ( -- navy ) ; font - size : 14 px ; }
. order - load - state span { overflow - wrap : anywhere ; color : var ( -- muted ) ; font - size : 12 px ; line - height : 1.55 ; }
. order - load - state . is - compact { min - height : 180 px ; }
. order - detail - content { min - height : 180 px ; }
: global ( . first - leg - detail - dialog . el - dialog ) { display : flex ; max - width : 1500 px ; height : 94 vh ; margin - bottom : 0 ; flex - direction : column ; overflow : hidden ; border - radius : 6 px ; }
: global ( . first - leg - detail - dialog . el - dialog _ _header ) { flex : 0 0 auto ; margin - right : 0 ; padding : 17 px 22 px ; border - bottom : 1 px solid var ( -- border ) ; }
: global ( . first - leg - detail - dialog . el - dialog _ _title ) { color : var ( -- navy ) ; font - size : 16 px ; font - weight : 700 ; }
: global ( . first - leg - detail - dialog . el - dialog _ _headerbtn ) { top : 8 px ; right : 10 px ; width : 44 px ; height : 44 px ; }
: global ( . first - leg - detail - dialog . el - dialog _ _body ) { min - height : 0 ; flex : 1 1 auto ; overflow - y : auto ; padding : 22 px 24 px 28 px ; overscroll - behavior : contain ; }
: global ( . first - leg - detail - dialog . el - dialog _ _footer ) { flex : 0 0 auto ; padding : 12 px 22 px ; border - top : 1 px solid var ( -- border ) ; background : # fff ; }
. mobile - order - surface { display : none ; min - height : 220 px ; }
. mobile - order - list { display : none ; }
. order - number - link { display : block ; width : 100 % ; max - width : 100 % ; overflow : hidden ; padding : 0 ; border : 0 ; background : none ; color : var ( -- client - action ) ; cursor : pointer ; font - size : var ( -- client - font - size ) ; font - weight : 700 ; line - height : 18 px ; text - align : left ; text - overflow : ellipsis ; white - space : nowrap ; }
. first - leg - detail - page { min - width : 0 ; padding : 10 px 14 px 20 px ; overflow : hidden ; border : 1 px solid var ( -- client - border ) ; border - radius : var ( -- client - radius ) ; background : # fff ; }
. order - detail - content { min - height : 360 px ; }
. inbound - code - primary { display : block ; width : 100 % ; max - width : 100 % ; overflow : hidden ; padding : 0 ; border : 0 ; background : none ; color : var ( -- client - action ) ; cursor : pointer ; font - size : var ( -- client - font - size ) ; font - weight : 700 ; line - height : 18 px ; text - align : left ; text - overflow : ellipsis ; white - space : nowrap ; }
. pending - inbound - code { color : var ( -- muted ) ; font - size : 12 px ; }
. inbound - code - link { max - width : 100 % ; overflow : hidden ; padding : 0 ; border : 0 ; background : transparent ; color : var ( -- client - action ) ; cursor : pointer ; text - align : left ; text - decoration : underline ; text - decoration - color : transparent ; text - underline - offset : 3 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. inbound - code - link : hover , . inbound - code - link : focus - visible { text - decoration - color : currentColor ; }
. inbound - code - link : focus - visible { outline : 2 px solid var ( -- primary ) ; outline - offset : 2 px ; }
. order - number - link : focus - visible { outline : 2 px solid var ( -- primary ) ; outline - offset : 3 px ; }
. inbound - code - primary : 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 - row - actions { align - items : center ; justify - content : center ; gap : 6 px ; white - space : nowrap ; }
. order - row - actions : deep ( . el - button ) { margin - left : 0 ; padding : 3 px 1 px ; font - size : 11 px ; }
. is - create - route { gap : 0 ; }
. is - create - route . first - leg - editor - page { width : 100 % ; }
. is - create - route . supporting - data - error , . is - create - route > . el - alert { margin - bottom : 14 px ; }
. is - create - route . prototype - order - form { width : 100 % ; margin : 0 ; padding : 0 0 16 px ; }
. is - create - route . prototype - section : not ( . cargo - section ) : not ( . cost - estimate - section ) : not ( . destination - section ) { width : min ( 100 % , 900 px ) ; }
. is - create - route . prototype - order - form { display : grid ; width : 100 % ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; align - items : start ; gap : 10 px ; margin : 0 ; padding : 0 0 10 px ; }
. is - create - route . prototype - section { position : relative ; min - width : 0 ; margin : 0 ; padding : 0 16 px 14 px ; overflow : hidden ; border : 1 px solid # d9e4ea ; border - radius : 8 px ; background : # fff ; box - shadow : 0 8 px 26 px rgba ( 24 , 55 , 75 , .065 ) ; transition : border - color .22 s ease , box - shadow .22 s ease ; }
. is - create - route . order - basic - section : : before , . is - create - route . destination - section : : before { position : absolute ; z - index : 2 ; top : 0 ; right : 0 ; left : 0 ; height : 3 px ; background : linear - gradient ( 90 deg , # 16 a394 , # 176 b87 55 % , transparent 92 % ) ; content : '' ; }
. is - create - route . destination - section : : before { background : linear - gradient ( 90 deg , # 2563 eb , # 4 f46e5 55 % , transparent 92 % ) ; }
. is - create - route . order - basic - section : focus - within { border - color : rgba ( 22 , 163 , 148 , .45 ) ; box - shadow : 0 12 px 34 px rgba ( 22 , 113 , 113 , .1 ) ; }
. is - create - route . destination - section : focus - within { border - color : rgba ( 37 , 99 , 235 , .4 ) ; box - shadow : 0 12 px 34 px rgba ( 37 , 99 , 235 , .1 ) ; }
. is - create - route . prototype - section + . prototype - section { margin - top : 0 ; }
. is - create - route . cargo - section , . is - create - route . cost - estimate - section { grid - column : 1 / - 1 ; }
. is - create - route . order - basic - section > . order - section - heading , . is - create - route . destination - section > . order - section - heading { box - sizing : border - box ; align - items : center ; margin : 0 - 16 px 12 px ; padding : 10 px 16 px ; border - bottom : 1 px solid # e7ecf2 ; background : linear - gradient ( 90 deg , # f8fbfc , # fff 58 % ) ; }
. is - create - route . destination - section > . order - section - heading { background : linear - gradient ( 90 deg , # f7f9ff , # fff 58 % ) ; }
. is - create - route . order - basic - section > . order - section - heading h3 , . is - create - route . destination - section > . order - section - heading h3 { margin : 0 ; color : var ( -- navy ) ; font - size : 17 px ; font - weight : 700 ; line - height : 24 px ; }
. is - create - route . order - step - badge { display : grid ; width : 30 px ; height : 30 px ; flex : 0 0 30 px ; color : # fff ; border - radius : 8 px ; background : linear - gradient ( 135 deg , # 16 a394 , # 176 b87 ) ; box - shadow : 0 5 px 12 px rgba ( 23 , 107 , 135 , .2 ) ; font - size : 11 px ; font - weight : 800 ; place - items : center ; }
. is - create - route . destination - section . order - step - badge { background : linear - gradient ( 135 deg , # 367 ae8 , # 4 f46e5 ) ; box - shadow : 0 5 px 12 px rgba ( 37 , 99 , 235 , .2 ) ; }
. is - create - route . order - section - context { margin - left : auto ; color : # 8 a98a8 ; font - size : 11 px ; font - weight : 500 ; }
. is - create - route . cargo - section > . order - section - heading , . is - create - route . cost - estimate - section > . order - section - heading { align - items : center ; margin : 0 - 16 px 10 px ; padding : 9 px 16 px ; border - bottom : 1 px solid # e7ecf2 ; }
. is - create - route . order - section - heading p { margin : 0 ; color : # 8 a98a8 ; }
. is - create - route . prototype - form - flow , . is - create - route . destination - form - flow { width : 100 % ; max - width : none ; }
. is - create - route . prototype - form - row , . is - create - route . prototype - form - row -- wide , . is - create - route . prototype - form - row -- compact , . is - create - route . prototype - form - row -- short , . is - create - route . prototype - form - row -- tabs { grid - template - columns : 108 px minmax ( 0 , 1 fr ) ; }
. is - create - route . prototype - form - row : deep ( . el - form - item _ _label ) { display : flex ; height : 36 px ; min - height : 36 px ; align - items : center ; justify - content : flex - end ; padding : 0 10 px 0 0 ; font - size : 13 px ; line - height : 1.35 ; text - align : right ; }
. is - create - route . segmented - options , . is - create - route . destination - type - options { width : 100 % ; max - width : 560 px ; background : # f3f6f9 ; }
. is - create - route . order - basic - section . segmented - options : deep ( . el - radio - button _ _original - radio : checked + . el - radio - button _ _inner ) { background : linear - gradient ( 105 deg , # 1596 a6 , # 176 b87 ) ; }
. is - create - route . destination - section . segmented - options : deep ( . el - radio - button _ _original - radio : checked + . el - radio - button _ _inner ) { background : linear - gradient ( 105 deg , # 367 ae8 , # 4 f46e5 ) ; box - shadow : 0 4 px 10 px rgba ( 37 , 99 , 235 , .2 ) ; }
. is - create - route . compact - transport - grid { width : 100 % ; margin - top : 4 px ; }
. is - create - route . compact - transport - grid > : deep ( . el - form - item ) , . is - create - route . compact - transport - grid > : deep ( . el - form - item : nth - child ( 1 ) ) , . is - create - route . compact - transport - grid > : deep ( . el - form - item : nth - child ( 6 ) ) , . is - create - route . compact - transport - grid > : deep ( . el - form - item : nth - child ( 7 ) ) { grid - template - columns : 108 px minmax ( 0 , 1 fr ) ; }
. is - create - route . compact - transport - grid > : deep ( . el - form - item _ _label ) { display : flex ; height : 36 px ; min - height : 36 px ; align - items : center ; justify - content : flex - end ; padding : 0 10 px 0 0 ; line - height : 1.35 ; text - align : right ; }
. is - create - route . selected - destination { width : auto ; margin - left : 108 px ; }
. is - create - route . shipping - form - flow . prototype - form - row , . is - create - route . destination - form - flow . prototype - form - row , . is - create - route . compact - transport - grid > : deep ( . el - form - item ) { margin - bottom : 5 px ; }
. is - create - route . prototype - form - row : deep ( . el - input _ _wrapper ) , . is - create - route . prototype - form - row : deep ( . el - select _ _wrapper ) , . is - create - route . compact - transport - grid : deep ( . el - input _ _wrapper ) , . is - create - route . compact - transport - grid : deep ( . el - select _ _wrapper ) { min - height : 36 px ; }
. is - create - route . segmented - options : deep ( . el - radio - button _ _inner ) { min - height : 36 px ; padding : 8 px 12 px ; }
. is - create - route . warehouse - options { gap : 8 px ; padding - top : 1 px ; }
. is - create - route . warehouse - options : deep ( . el - radio ) { min - height : 32 px ; margin : 0 ; padding : 5 px 11 px ; border : 1 px solid # d7e0e7 ; border - radius : 7 px ; background : # f8fafb ; color : # 596779 ; transition : color .18 s ease , border - color .18 s ease , background .18 s ease , box - shadow .18 s ease , transform .18 s ease ; }
. is - create - route . warehouse - options : deep ( . el - radio : hover ) { transform : translateY ( - 1 px ) ; border - color : # 91 b8c3 ; background : # f3fafb ; color : var ( -- primary ) ; }
. is - create - route . warehouse - options : deep ( . el - radio _ _input ) { position : absolute ; width : 1 px ; height : 1 px ; overflow : hidden ; opacity : 0 ; pointer - events : none ; }
. is - create - route . warehouse - options : deep ( . el - radio _ _label ) { padding - left : 0 ; color : inherit ; }
. is - create - route . warehouse - options : deep ( . el - radio . is - checked ) { border - color : var ( -- primary ) ; background : linear - gradient ( 105 deg , # 1596 a6 , # 176 b87 ) ; box - shadow : 0 4 px 10 px rgba ( 23 , 107 , 135 , .2 ) ; color : # fff ; }
. is - create - route . cargo - table - heading , . is - create - route . cost - estimate - section . order - section - heading { margin - bottom : 10 px ; }
. order - form { width : min ( 100 % , 1000 px ) ; margin : 0 ; padding : 0 0 12 px ; }
. order - form . is - dialog - order - form { width : 100 % ; max - width : none ; }
. is - dialog - order - form > . cargo - section , . is - dialog - order - form > . cost - estimate - section { width : 100 % ; max - width : none ; }
. order - form : deep ( . el - form - item _ _error ) { position : static ; padding - top : 6 px ; overflow - wrap : anywhere ; line - height : 1.45 ; }
. order - form - section + . order - form - section { margin - top : 30 px ; padding - top : 28 px ; border - top : 1 px solid # edf0f5 ; }
. order - form - section + . order - form - section { border - top : 1 px solid # edf0f5 ; }
. 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 > * , . cargo - form - grid > * { min - width : 0 ; }
@@ -2539,6 +2660,7 @@ async function searchOrders(model: QueryModel) {
. compact - transport - grid : deep ( . el - form - item _ _label ) { color : # 4 d5a6c ; font - size : 13 px ; }
. shipping - form - flow { max - width : 900 px ; }
. prototype - order - form > . prototype - section : first - child { width : 100 % ; }
. is - create - route . prototype - order - form > . prototype - section : first - child > . order - section - heading { margin - top : 0 ; }
. shipping - form - flow . prototype - form - row { margin - bottom : 10 px ; }
. shipping - form - flow . prototype - form - row -- tabs { margin - bottom : 12 px ; }
. compact - transport - grid { display : grid ; width : 100 % ; grid - template - columns : 1 fr ; gap : 0 ; margin : 6 px 0 0 ; }
@@ -2619,51 +2741,49 @@ async function searchOrders(model: QueryModel) {
. dimension - inputs > span { color : var ( -- muted ) ; }
. cargo - detail - image { display : flex ; width : 112 px ; height : 112 px ; align - items : center ; justify - content : center ; margin - top : 12 px ; overflow : hidden ; border : 1 px solid var ( -- border ) ; background : # fff ; color : var ( -- muted ) ; font - size : 10 px ; text - align : center ; }
. cargo - detail - image img { width : 100 % ; height : 100 % ; object - fit : contain ; }
. 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 ; }
. 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 ; }
. order - detail - heading { display : flex ; min - height : 50 px ; align - items : center ; justify - content : space - between ; gap : 16 px ; padding : 5 px 0 ; border - bottom : 1 px solid var ( -- border ) ; }
. detail - heading - main , . detail - heading - actions { display : flex ; min - width : 0 ; align - items : center ; gap : 12 px ; }
. detail - heading - main > div { min - width : 0 ; }
. detail - back - action , . detail - heading - actions { flex : 0 0 auto ; }
. order - detail - heading h2 { margin : 0 ; overflow : hidden ; color : var ( -- navy ) ; font - size : 18 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. order - detail - heading p { margin : 3 px 0 0 ; overflow : hidden ; color : var ( -- muted ) ; font - size : 11 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. order - detail - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; gap : 0 ; margin : 0 ; border - top : 1 px solid var ( -- border ) ; border - left : 1 px solid var ( -- border ) ; }
. order - detail - grid div { display : grid ; min - width : 0 ; min - height : 34 px ; grid - template - columns : 78 px minmax ( 0 , 1 fr ) ; align - items : center ; gap : 7 px ; padding : 3 px 8 px ; border - right : 1 px solid var ( -- border ) ; border - bottom : 1 px solid var ( -- border ) ; }
. order - detail - grid dt { color : var ( -- muted ) ; font - size : 10 px ; white - space : nowrap ; }
. order - detail - grid dd { min - width : 0 ; margin : 0 ; overflow - wrap : anywhere ; font - size : 12 px ; font - weight : 600 ; }
. detail - subline { display : block ; margin - top : 2 px ; color : var ( -- muted ) ; font - size : 9 px ; font - weight : 400 ; line - height : 1.35 ; }
. detail - section { margin - top : 10 px ; }
. detail - section h3 { margin : 0 ; padding : 0 0 5 px ; border - bottom : 1 px solid # eaf0f7 ; color : var ( -- navy ) ; font - size : 13 px ; }
. party - detail - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; margin : 0 ; border - left : 1 px solid var ( -- border ) ; }
. party - detail - grid > div { display : grid ; min - width : 0 ; grid - template - columns : 62 px minmax ( 0 , 1 fr ) ; gap : 7 px ; padding : 5 px 8 px ; border - right : 1 px solid var ( -- border ) ; border - bottom : 1 px solid var ( -- border ) ; }
. party - detail - grid dt { color : var ( -- muted ) ; font - size : 10 px ; }
. party - detail - grid dd { min - width : 0 ; margin : 0 ; }
. party - detail - grid strong { display : block ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 12 px ; }
. party - detail - grid span { display : block ; margin - top : 3 px ; overflow - wrap : anywhere ; color : var ( -- muted ) ; font - size : 10 px ; line - height : 1.5 ; }
. 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 : 7 px ; padding : 8 px 10 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 ) ; }
. fulfillment - summary > div : nth - last - child ( - n + 3 ) { border - bottom : 0 ; }
. fulfillment - summary { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; margin : 0 ; border - left : 1 px solid var ( -- border ) ; }
. fulfillment - summary > div { display : grid ; min - width : 0 ; min - height : 34 px ; grid - template - columns : 70 px minmax ( 0 , 1 fr ) ; align - items : center ; gap : 7 px ; padding : 3 px 8 px ; border - right : 1 px solid var ( -- border ) ; border - bottom : 1 px solid var ( -- border ) ; }
. fulfillment - summary dt { color : var ( -- muted ) ; font - size : 10 px ; }
. fulfillment - summary dd { margin : 5 px 0 0 ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 12 px ; font - weight : 650 ; }
. cabinet - progress - list { display : grid ; gap : 10 px ; }
. cabinet - progress - card { padding : 14 px 15 px ; border : 1 px solid var ( -- border ) ; background : # fbfcfc ; }
. cabinet - progress - card header { display : flex ; align - items : center ; justify - content : space - between ; gap : 10 px ; }
. cabinet - progress - card header strong { color : var ( -- navy ) ; font - size : 13 px ; }
. cabinet - progress - card dl { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; gap : 12 px ; margin : 13 px 0 0 ; }
. cabinet - progress - card dt { color : var ( -- muted ) ; font - size : 10 px ; }
. cabinet - progress - card dd { margin : 4 px 0 0 ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 11 px ; font - weight : 600 ; }
. 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 ) ; }
. fulfillment - summary dd { min - width : 0 ; margin : 0 ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 11 px ; font - weight : 650 ; }
. cabinet - progress - detail { border - top : 1 px solid var ( -- border ) ; }
. cabinet - progress - detail > strong { display : block ; padding : 7 px 0 5 px ; color : var ( -- navy ) ; font - size : 11 px ; }
. cabinet - progress - table { margin - top : 0 ; }
. detail - inline - empty { margin : 0 ; padding : 7 px 9 px ; background : # f7f9fb ; color : var ( -- muted ) ; font - size : 10 px ; }
. express - information { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; margin : 0 ; border - left : 1 px solid var ( -- border ) ; }
. express - information > div { display : grid ; min - width : 0 ; min - height : 40 px ; grid - template - columns : 70 px minmax ( 0 , 1 fr ) ; align - items : center ; gap : 8 px ; padding : 6 px 10 px ; border - right : 1 px solid var ( -- border ) ; border - bottom : 1 px solid var ( -- border ) ; }
. express - information dt { color : var ( -- muted ) ; font - size : 10 px ; }
. express - information dd { min - width : 0 ; margin : 0 ; overflow - wrap : anywhere ; color : var ( -- navy ) ; font - size : 11 px ; font - weight : 650 ; }
. express - empty { display : flex ; align - items : center ; gap : 9 px ; padding : 7 px 9 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 - summary { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ; margin : 0 ; border - left : 1 px solid var ( -- border ) ; }
. cargo - summary div { display : grid ; min - width : 0 ; min - height : 34 px ; grid - template - columns : 54 px minmax ( 0 , 1 fr ) ; align - items : center ; gap : 7 px ; padding : 3 px 8 px ; border - right : 1 px solid var ( -- border ) ; border - bottom : 1 px solid var ( -- border ) ; }
. cargo - summary dt { color : var ( -- muted ) ; font - size : 10 px ; }
. cargo - summary dd { margin : 0 ; color : var ( -- navy ) ; font - size : 11 px ; font - weight : 700 ; }
. cargo - detail - table - wrap { overflow : hidden ; margin - top : 10 px ; border : 1 px solid var ( -- border ) ; border - radius : 5 px ; background : # fff ; }
. cargo - detail - table { width : 100 % ; font - size : 11 px ; }
. cargo - detail - table : deep ( . el - table _ _header th . el - table _ _cell ) { height : 42 px ; padding : 0 ; background : # f7f8fb ; color : # 657187 ; font - size : 11 px ; font - weight : 650 ; white - space : nowrap ; }
@@ -2677,20 +2797,7 @@ async function searchOrders(model: QueryModel) {
: deep ( . el - timeline - item _ _content > span ) { font - size : 11 px ; }
@ media ( max - width : 1280 px ) {
. first - leg - query - toolbar : deep ( . query - toolbar _ _filters ) { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
. prototype - order - table : deep ( > . el - table ) , . prototype - order - table : deep ( > . data - table _ _pagination ) { display : none ; }
. mobile - order - surface { display : block ; }
. mobile - order - list { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 10 px ; padding : 10 px ; }
. mobile - order - card { min - width : 0 ; 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 { min - width : 0 ; overflow : hidden ; padding : 0 ; border : 0 ; background : none ; color : var ( -- primary ) ; font - weight : 750 ; text - overflow : ellipsis ; white - space : nowrap ; }
. 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 , . fulfillment - summary { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
}
@ media ( min - width : 721 px ) and ( max - width : 1050 px ) {
. cargo - form - grid , . cargo - form - grid -- basic { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
@@ -2706,7 +2813,7 @@ async function searchOrders(model: QueryModel) {
. product - image - field { grid - row : auto ; }
. product - dimensions - field , . cargo - remark - field { grid - column : auto ; }
. 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 { border - right : 1 px solid var ( -- border ) ; 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 ; }
. prototype - form - row : deep ( . el - form - item _ _label ) { min - height : auto ; justify - content : flex - start ; padding : 0 0 8 px ; line - height : 1.4 ; }
@@ -2728,38 +2835,23 @@ async function searchOrders(model: QueryModel) {
. price - estimate _ _state . is - error { align - items : flex - start ; flex - direction : column ; }
. form - span - full { grid - column : auto ; }
. editor - actions : deep ( . el - button ) { min - height : 44 px ; }
. prototype - order - table : deep ( > . el - table ) , . prototype - order - table : deep ( > . data - table _ _pagination ) { display : none ; }
. order - load - state { min - height : 240 px ; grid - template - columns : 1 fr ; justify - items : start ; }
. supporting - data - error { grid - template - columns : auto minmax ( 0 , 1 fr ) ; }
. supporting - data - error . el - button { grid - column : 1 / - 1 ; width : 100 % ; min - height : 44 px ; }
. order - load - state . el - button { width : 100 % ; min - height : 44 px ; }
. mobile - order - list { grid - template - columns : 1 fr ; }
. 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 ; }
. express - information { grid - template - columns : auto 1 fr ; }
. express - information > div : last - child { grid - column : 2 ; }
. express - information { grid - template - columns : 1 fr ; }
. 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 ) ; }
. cabinet - progress - card dl { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; }
: global ( . first - leg - detail - dialog . el - dialog ) { width : calc ( 100 vw - 24 px ) ! important ; height : 96 vh ; margin - top : 2 vh ; }
}
@ media ( max - width : 360 px ) {
. cargo - detail - pagination { justify - content : flex - start ; overflow - x : auto ; }
}
@ media ( max - width : 480 px ) {
: global ( . first - leg - detail - dialog . el - dialog ) { width : 100 vw ! important ; height : 100 vh ; margin : 0 ; border - radius : 0 ; }
: global ( . first - leg - detail - dialog . el - dialog _ _body ) { padding : 16 px 12 px 22 px ; }
. first - leg - detail - page { padding - bottom : 22 px ; }
. order - detail - heading { align - items : flex - start ; flex - direction : column ; }
. detail - heading - main { width : 100 % ; align - items : flex - start ; }
. detail - heading - actions { width : 100 % ; justify - content : flex - end ; }
. 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 ; }
. editor - actions : deep ( . el - button : nth - child ( 3 ) ) { grid - column : 1 / - 1 ; }