@@ -10,8 +10,7 @@ import FirstLegOrderEditorShell from '@/modules/first-leg/components/FirstLegOrd
import { useFirstLegOrdersStore } from '@/modules/first-leg/store/firstLegOrders'
import { useTenantAdministrationStore } from '@/modules/tenant-management/public'
import { useAuthStore } from '@/modules/auth/public'
import { createFirstLegPartySnapshot , type FirstLegPartyRole } from '@/modules/first-leg/data/firstLegParties'
import { firstLegPartySource } from '@/modules/first-leg/data/firstLegPartySource'
import type { FirstLegPartyRole , FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties'
import type { FirstLegOrder , FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder'
import {
canClientEditFirstLegOrder ,
@@ -36,8 +35,6 @@ const canCreateOrder = computed(() => auth.hasPermission('order:first-leg:create
const canEditOrder = 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 availableShippers = ref ( firstLegPartySource . listAvailable ( 'SHIPPER' ) )
const availableConsignees = ref ( firstLegPartySource . listAvailable ( 'CONSIGNEE' ) )
const filters = ref < QueryModel > ( { keyword : '' , status : '' , transportMode : '' , createdRange : [ ] } )
const appliedFilters = ref < QueryModel > ( { ... filters . value } )
const queryFields : QueryField [ ] = [
@@ -112,8 +109,6 @@ function persistPrivateAddresses() {
const orderFormRules : FormRules = {
transportMode : [ { required : true , message : '请选择运输方式' , trigger : 'change' } ] ,
businessType : [ { required : true , message : '请选择业务类型' , trigger : 'change' } ] ,
origin : [ { required : true , message : '请输入起运地' , trigger : 'blur' } ] ,
destination : [ { required : true , message : '请输入目的地' , trigger : 'blur' } ] ,
receivingWarehouseId : [ { required : true , message : '请选择平台收货仓' , trigger : 'change' } ] ,
destinationCountryCode : [
{ required : true , message : '请输入目的国代码' , trigger : 'blur' } ,
@@ -125,8 +120,23 @@ const orderFormRules: FormRules = {
pickupMethod : [ { required : true , message : '请选择提货方式' , trigger : 'change' } ] ,
taxMode : [ { required : true , message : '请选择报关模式' , trigger : 'change' } ] ,
destinationType : [ { required : true , message : '请选择目的仓类型' , trigger : 'change' } ] ,
shipperPartyId : [ { required : true , message : '请选择发货单位' , trigger : 'change' } ] ,
consigneePartyId : [ { required : true , message : '请选择收货单位' , trigger : 'change' } ] ,
}
function createEmptyPartySnapshot ( role : FirstLegPartyRole ) : FirstLegPartySnapshot {
return {
id : '' ,
code : '' ,
name : '' ,
role ,
contactName : '' ,
phone : '' ,
email : '' ,
countryRegion : role === 'SHIPPER' ? 'CN' : 'US' ,
province : '' ,
city : '' ,
postalCode : '' ,
address : '' ,
}
}
function requiredTextRules ( message : string ) : FormItemRule [ ] {
@@ -231,24 +241,13 @@ async function loadPageData() {
supportingDataLoading . value = true
supportingDataErrors . value = [ ]
companyAuditLoaded . value = false
availableShippers . value = [ ]
availableConsignees . value = [ ]
const companyRequest = canViewCompanyProfile . value
? tenantStore . loadCompanyContext ( )
: Promise . resolve ( )
const [ ordersResult , partiesResult , companyResult ] = await Promise . allSettled ( [
const [ ordersResult , companyResult ] = await Promise . allSettled ( [
store . load ( ) ,
firstLegPartySource . load ( ) ,
companyRequest ,
] )
if ( partiesResult . status === 'fulfilled' ) {
availableShippers . value = firstLegPartySource . listAvailable ( 'SHIPPER' )
availableConsignees . value = firstLegPartySource . listAvailable ( 'CONSIGNEE' )
} else {
supportingDataErrors . value . push ( ` 往来单位加载失败: ${ errorMessage ( partiesResult . reason ) } ` )
}
if ( canViewCompanyProfile . value && companyResult . status === 'fulfilled' ) {
companyAuditLoaded . value = true
} else if ( companyResult . status === 'rejected' ) {
@@ -344,38 +343,6 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
}
}
function selectParty ( role : FirstLegPartyRole , id : string ) {
const party = firstLegPartySource . findAvailable ( id , role )
if ( ! party ) return
const snapshot = createFirstLegPartySnapshot ( party , role )
if ( role === 'SHIPPER' ) {
form . shipperPartyId = party . id
form . shipperPartySnapshot = snapshot
form . shipper = party . name
form . origin = [ snapshot . province , snapshot . city ] . filter ( Boolean ) . join ( ' ' ) || snapshot . countryRegion || snapshot . name
} else {
form . consigneePartyId = party . id
form . consigneePartySnapshot = snapshot
form . consignee = party . name
form . destination = [ snapshot . province , snapshot . city ] . filter ( Boolean ) . join ( ' ' ) || snapshot . countryRegion || snapshot . name
if ( /^[A-Za-z]{2}$/ . test ( snapshot . countryRegion ) ) form . destinationCountryCode = snapshot . countryRegion . toUpperCase ( )
}
}
function clearParty ( role : FirstLegPartyRole ) {
if ( role === 'SHIPPER' ) {
form . shipperPartyId = ''
form . shipperPartySnapshot = null
form . shipper = ''
form . origin = ''
} else {
form . consigneePartyId = ''
form . consigneePartySnapshot = null
form . consignee = ''
form . destination = ''
}
}
function selectChannel ( code : string ) {
const channel = channelOptions . find ( ( item ) => item . code === code )
form . channelName = channel ? . name ? ? ''
@@ -394,11 +361,34 @@ function selectDestinationWarehouse(id: string) {
function changeDestinationType ( ) {
form . destinationWarehouseId = ''
form . destinationWarehouseName = ''
form . destination = ''
form . amazonWarehouseCode = ''
selectedPrivateAddressId . value = ''
form . consigneePartyId = ''
form . consigneePartySnapshot = form . destinationType === 'PRIVATE' ? createEmptyPartySnapshot ( 'CONSIGNEE' ) : null
form . consignee = ''
resetEditorValidation ( )
}
function changePickupMethod ( ) {
if ( form . pickupMethod === 'PICKUP' ) {
form . shipperPartySnapshot ? ? = createEmptyPartySnapshot ( 'SHIPPER' )
} else {
form . shipperPartyId = ''
form . shipperPartySnapshot = null
form . shipper = ''
form . origin = ''
}
resetEditorValidation ( )
}
function syncPrivateDestinationName ( value : string ) {
if ( form . destinationType !== 'PRIVATE' ) return
form . destinationWarehouseName = value
form . destination = value . trim ( )
if ( form . consigneePartySnapshot ) form . consigneePartySnapshot . name = value
}
function selectPrivateAddress ( id : string ) {
const address = privateAddresses . value . find ( ( item ) => item . id === id )
if ( ! address ) return
@@ -412,18 +402,24 @@ function selectPrivateAddress(id: string) {
}
function savePrivateAddress ( ) {
if ( ! form . consigneePartySnapshot || ! form . consigneePartyId || ! form . destinationWarehouseName . trim ( ) || ! form . destinationCountryCode . trim ( ) ) {
ElMessage . warning ( '请先完整填写收货单位、私人地址名称和目的国代码' )
if ( ! form . consigneePartySnapshot || ! form . destinationWarehouseName . trim ( ) || ! form . destinationCountryCode . trim ( )
|| ! form . consigneePartySnapshot . contactName . trim ( ) || ! form . consigneePartySnapshot . phone . trim ( )
|| ! form . consigneePartySnapshot . city . trim ( ) || ! form . consigneePartySnapshot . address . trim ( ) ) {
ElMessage . warning ( '请先完整填写私人地址和收件资料' )
return
}
const snapshot = { ... form . consigneePartySnapshot }
const existing = privateAddresses . value . find ( ( item ) => item . consigneePartyId === form . consigneePartyId && item . consigneePartySnapshot . address === snapshot . address )
snapshot . name = form . destinationWarehouseName . trim ( )
snapshot . countryRegion = form . destinationCountryCode . trim ( ) . toUpperCase ( )
const existing = privateAddresses . value . find ( ( item ) => item . consigneePartySnapshot . address === snapshot . address )
const partyId = existing ? . consigneePartyId || snapshot . id || ` private-party- ${ Date . now ( ) } `
snapshot . id = partyId
const saved : CachedPrivateAddress = {
id : existing ? . id ? ? globalThis . crypto ? . randomUUID ? . ( ) ? ? ` private- ${ Date . now ( ) } ` ,
label : ` ${ form . destinationWarehouseName . trim ( ) } · ${ snapshot . address } ` ,
consigneePartyId : form . consigneePartyId ,
consigneePartyId : partyId ,
consigneePartySnapshot : snapshot ,
consignee : form . consignee ,
consignee : snapshot . name ,
destination : form . destination ,
destinationCountryCode : form . destinationCountryCode . trim ( ) . toUpperCase ( ) ,
destinationWarehouseName : form . destinationWarehouseName . trim ( ) ,
@@ -444,22 +440,6 @@ function removePrivateAddress() {
ElMessage . success ( '已从当前账号的私人地址中移除' )
}
function selectShipper ( id : string ) {
selectParty ( 'SHIPPER' , id )
}
function selectConsignee ( id : string ) {
selectParty ( 'CONSIGNEE' , id )
}
function clearShipper ( ) {
clearParty ( 'SHIPPER' )
}
function clearConsignee ( ) {
clearParty ( 'CONSIGNEE' )
}
async function loadSelectedWarehouse ( id : string | number | boolean | undefined ) {
const warehouseId = typeof id === 'string' ? id : typeof id === 'number' ? String ( id ) : ''
if ( ! warehouseId ) return
@@ -744,10 +724,42 @@ async function searchOrders(model: QueryModel) {
< FirstLegOrderEditorShell v-model = "editorOpen" :page="isCreatePage" :title="editingId ? '编辑订单草稿' : '创建头程订单'" :before-close="requestCloseEditor" @back="closeEditor" >
< el -form ref = "orderFormRef" v-loading = "isCreatePage && (supportingDataLoading || store.loading)" :model="form" :rules="orderFormRules" label-position="top" class="order-form prototype-order-form" scroll -to -error >
< section class = "order-form-section prototype-section" >
< div class = "order-section-heading" > < div > < h3 > 下单信息 < / h3 > < p > 选择送货方式 、 收货仓和运输渠道 。 < / p > < / div > < / div >
< div class = "order -form-grid " >
< el-form-item label = "取货方式 " prop = "pickupMethod" required class = "form-span-full "> < el-radio-group v-model = "form.pickupMethod " class="segmented-options"><el-radio-button value="SELF_EXPRESS">自送货 · 快递 </el-radio-button><el-radio-button value="PICKUP">我司提货 </el-radio-button><el-radio-button value="SELF_TRUCK" > 自送货 · 卡车 < / el -radio -button > < / el-radio-group > < / el-form-item >
< el-form-item label = "平台收货仓" prop = "receivingWarehouseId" required class = "form-span-full" > < el-radio-group v-model = "form.receivingWarehouseId" class="warehouse-options" @change="loadSelectedWarehouse"><el-radio-button v-for="warehouse in availableWarehouses" :key="warehouse.id" :value="warehouse.id" > {{ warehouse.city ? ` $ { warehouse.name } · $ { warehouse.city } ` : warehouse.name }} < / el -radio -button > < / el-radio-group > < / el-form-item >
< div class = "order-section-heading" > < div > < h3 > 下单信息 < / h3 > < p > 选择目的仓后 , 再填写本次运输安排 。 < / p > < / div > < / div >
< div class = "prototype-form-flow destination -form-flow " >
< el-form-item label = "目的仓类型 " prop = "destinationType" required class = "prototype-form-row prototype-form-row--tabs "> < el-radio-group v-model = "form.destinationType " class="segmented-options" @change="changeDestinationType" ><el-radio-button value="FBA">FBA 仓 </el-radio-button><el-radio-button value="OVERSEAS">海外仓 </el-radio-button><el-radio-button value="PRIVATE" > 私人仓 < / el -radio -button > < / el-radio-group > < / el-form-item >
< el-form-item v-if = "form.destinationType === 'PRIVATE'" label="私人地址簿" class="prototype-form-row prototype-form-row--wide address-book-row" >
< div class = "address-book-control" > < el-select v-model = "selectedPrivateAddressId" clearable filterable placeholder="选择当前账号保存的私人地址" @change="selectPrivateAddress"><el-option v-for="address in privateAddresses" :key="address.id" :label="address.label" :value="address.id" /></el-select><el-button @click="savePrivateAddress">保存当前地址</el-button><el-button v-if="selectedPrivateAddressId" link type="danger" @click="removePrivateAddress" > 移除 < / el -button > < / div >
< / el-form-item >
< el-form-item v-if = "form.destinationType !== 'PRIVATE'" :label="form.destinationType === 'FBA' ? 'FBA 仓' : '海外仓'" prop="destinationWarehouseId" required :rules="requiredSelectRules('请选择平台维护的目的仓')" class="prototype-form-row"><el-select v-model="form.destinationWarehouseId" filterable placeholder="请选择当前可送达的目的仓" @change="selectDestinationWarehouse"><el-option v-for="warehouse in availableDestinationWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.code}`" :value="warehouse.id" /></el-select><small v-if="availableDestinationWarehouses.length === 0" class="field-guidance" > 平台暂未维护可用的 {{ form.destinationType = = = ' FBA ' ? ' FBA 仓 ' : ' 海外仓 ' }} , 请联系平台管理员 。 < / small > < / el -form -item >
< div v-if = "form.destinationType !== 'PRIVATE' && form.destinationWarehouseId" class="selected-destination"><MapPin :size="17" / > < span > { { form . destinationWarehouseName } } · { { form . amazonWarehouseCode || '海外仓' } } · { { form . destinationCountryCode } } < / span > < / div >
< template v-if = "form.destinationType === 'PRIVATE' && form.consigneePartySnapshot" >
< el -form -item label = "私人地址名称" prop = "destinationWarehouseName" required class = "prototype-form-row" > < el-input v-model = "form.destinationWarehouseName" maxlength="128" placeholder="例如:洛杉矶客户自有仓" @input="syncPrivateDestinationName" / > < / el-form-item >
< el-form-item label = "收件人姓名" prop = "consigneePartySnapshot.contactName" required :rules = "requiredTextRules('请输入收货联系人姓名')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.consigneePartySnapshot.contactName" maxlength="50" placeholder="请输入收件人姓名" / > < / el-form-item >
< el-form-item label = "收件人电话" prop = "consigneePartySnapshot.phone" required :rules = "requiredTextRules('请输入收货联系电话')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.consigneePartySnapshot.phone" maxlength="30" placeholder="请输入收件人电话" / > < / el-form-item >
< el-form-item label = "收件国家代码" prop = "destinationCountryCode" required class = "prototype-form-row prototype-form-row--short" > < el-input v-model = "form.destinationCountryCode" maxlength="2" placeholder="例如:US" / > < / el-form-item >
< el-form-item label = "州 / 省" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.consigneePartySnapshot.province" maxlength="100" placeholder="请输入州或省" / > < / el-form-item >
< el-form-item label = "城市" prop = "consigneePartySnapshot.city" required :rules = "requiredTextRules('请输入收件城市')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.consigneePartySnapshot.city" maxlength="100" placeholder="请输入城市" / > < / el-form-item >
< el-form-item label = "收件地址" prop = "consigneePartySnapshot.address" required :rules = "requiredTextRules('请输入本单实际收货地址')" class = "prototype-form-row prototype-form-row--wide" > < el-input v-model = "form.consigneePartySnapshot.address" maxlength="300" placeholder="请输入收件详细地址" / > < / el-form-item >
< el-form-item label = "邮编" class = "prototype-form-row prototype-form-row--short" > < el-input v-model = "form.consigneePartySnapshot.postalCode" maxlength="20" placeholder="请输入邮编" / > < / el-form-item >
< / template >
< el-form-item v-if = "form.destinationType === 'FBA'" label="货件编号" prop="shipmentId" required :rules="requiredTextRules('请输入 Shipment ID')" class="prototype-form-row prototype-form-row--compact"><el-input v-model="form.shipmentId" maxlength="64" placeholder="Shipment ID" / > < / el-form-item >
< el-form-item label = "跟踪编号" prop = "referenceNo" : required = "form.destinationType === 'FBA'" : rules = "form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入 Reference ID') : undefined" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? 'Reference ID' : '选填,贵司内部跟踪编号'" / > < / el-form-item >
< div class = "flow-divider" aria -hidden = " true " > < / div >
< el-form-item label = "取货方式" prop = "pickupMethod" required class = "prototype-form-row prototype-form-row--tabs" > < el-radio-group v-model = "form.pickupMethod" class="segmented-options" @change="changePickupMethod"><el-radio-button value="SELF_EXPRESS">快递自送</el-radio-button><el-radio-button value="PICKUP">我司提货</el-radio-button><el-radio-button value="SELF_TRUCK" > 卡车自送 < / el -radio -button > < / el-radio-group > < / el-form-item >
< template v-if = "form.pickupMethod === 'PICKUP' && form.shipperPartySnapshot" >
< el -form -item label = "提货联系人" prop = "shipperPartySnapshot.contactName" required :rules = "requiredTextRules('请输入提货联系人')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.shipperPartySnapshot.contactName" maxlength="50" placeholder="请输入联系人姓名" / > < / el-form-item >
< el-form-item label = "联系电话" prop = "shipperPartySnapshot.phone" required :rules = "requiredTextRules('请输入提货联系电话')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.shipperPartySnapshot.phone" maxlength="30" placeholder="请输入联系电话" / > < / el-form-item >
< el-form-item label = "州 / 省" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.shipperPartySnapshot.province" maxlength="100" placeholder="请输入州或省" / > < / el-form-item >
< el-form-item label = "城市" prop = "shipperPartySnapshot.city" required :rules = "requiredTextRules('请输入提货城市')" class = "prototype-form-row prototype-form-row--compact" > < el-input v-model = "form.shipperPartySnapshot.city" maxlength="100" placeholder="请输入城市" / > < / el-form-item >
< el-form-item label = "提货地址" prop = "shipperPartySnapshot.address" required :rules = "requiredTextRules('请输入提货地址')" class = "prototype-form-row prototype-form-row--wide" > < el-input v-model = "form.shipperPartySnapshot.address" maxlength="300" placeholder="请输入详细提货地址" / > < / el-form-item >
< / template >
< el-form-item label = "国内收货仓" prop = "receivingWarehouseId" required class = "prototype-form-row prototype-form-row--wide" > < el-radio-group v-model = "form.receivingWarehouseId" class="warehouse-options" @change="loadSelectedWarehouse"><el-radio-button v-for="warehouse in availableWarehouses" :key="warehouse.id" :value="warehouse.id" > {{ warehouse.city ? ` $ { warehouse.name } · $ { warehouse.city } ` : warehouse.name }} < / el -radio -button > < / el-radio-group > < / el-form-item >
< / div >
< div v-if = "selectedWarehouse" class="selected-warehouse" aria-label="所选收货仓详情"><MapPin :size="17" / > < div > < span > { { selectedWarehouse . address ? ` ${ selectedWarehouse . province } ${ selectedWarehouse . city } · ${ selectedWarehouse . address } ` : '仓库地址详情暂不可用' } } < / span > < small > { { selectedWarehouse . businessHours ? ` 收货时间: ${ selectedWarehouse . businessHours } ` : '收货时间暂不可用' } } < / small > < / div > < / div >
< div class = "order-form-grid compact-transport-grid" >
< el-form-item label = "渠道" prop = "channelCode" required > < el-select v-model = "form.channelCode" placeholder="请选择运输渠道" @change="selectChannel"><el-option v-if="form.channelCode && !channelOptions.some((channel) => channel.code === form.channelCode)" :label="form.channelName || form.channelCode" :value="form.channelCode" /><el-option v-for="channel in channelOptions" :key="channel.code" :label="channel.name" :value="channel.code" / > < / el-select > < / 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 v-for="item in FIRST_LEG_TRANSPORT_MODES" :key="item.code" :value="item.code" > {{ item.label }} < / el -radio > < / el-radio-group > < / el-form-item >
@@ -755,50 +767,6 @@ async function searchOrders(model: QueryModel) {
< 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" maxlength="1000" placeholder="选填,例如:入仓前请电话联系" / > < / el-form-item >
< / div >
< div v-if = "selectedWarehouse" class="selected-warehouse" aria-label="所选收货仓详情"><MapPin :size="17" / > < div > < span > { { selectedWarehouse . address ? ` ${ selectedWarehouse . province } ${ selectedWarehouse . city } · ${ selectedWarehouse . address } ` : '仓库地址详情暂不可用' } } < / span > < small > { { selectedWarehouse . businessHours ? ` 收货时间: ${ selectedWarehouse . businessHours } ` : '收货时间暂不可用' } } < / small > < / div > < / div >
< / section >
< section class = "order-form-section prototype-section destination-section" >
< div class = "order-section-heading" > < div > < h3 > 发货与目的地 < / h3 > < p > 单位信息会保存为本订单快照 , 后续主数据变更不会影响本单 。 < / p > < / div > < / div >
< el-form-item label = "目的仓类型" prop = "destinationType" required > < el-radio-group v-model = "form.destinationType" class="segmented-options" @change="changeDestinationType"><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="OVERSEAS">海外仓</el-radio-button><el-radio-button value="PRIVATE" > 私人地址 < / el -radio -button > < / el-radio-group > < / el-form-item >
< div v-if = "form.destinationType === 'PRIVATE'" class="private-address-toolbar" >
< el -select v-model = "selectedPrivateAddressId" clearable filterable placeholder="选择当前账号保存的私人地址" @change="selectPrivateAddress" >
< el -option v-for = "address in privateAddresses" :key="address.id" :label="address.label" :value="address.id" / >
< / el-select >
< el-button @click ="savePrivateAddress" > 保存当前地址 < / el -button >
< el-button v-if = "selectedPrivateAddressId" type="danger" plain @click="removePrivateAddress" > 移除缓存 < / el -button >
< / div >
< div class = "party-select-grid" >
< div class = "party-panel" >
< h4 > 发货信息 < / h4 >
< el-alert v-if = "availableShippers.length === 0" title="暂无可用发货单位" description="请先在平台往来单位中启用“发货单位”类型。" type="warning" :closable="false" show -icon / >
< el-form-item label = "发货单位" prop = "shipperPartyId" required > < el-select v-model = "form.shipperPartyId" filterable clearable placeholder="请选择发货单位" @change="selectShipper" @clear="clearShipper"><el-option v-if="form.shipperPartySnapshot && !availableShippers.some((party) => party.id === form.shipperPartyId)" :label="`${form.shipperPartySnapshot.name} · 历史快照`" :value="form.shipperPartyId" disabled /><el-option v-for="party in availableShippers" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" / > < / el-select > < / el-form-item >
< div v-if = "form.shipperPartySnapshot" class="party-contact-grid" >
< el -form -item label = "联系人" prop = "shipperPartySnapshot.contactName" required :rules = "requiredTextRules('请输入发货联系人姓名')" > < el-input v-model = "form.shipperPartySnapshot.contactName" maxlength="50" placeholder="请输入联系人" / > < / el-form-item >
< el-form-item label = "联系电话" prop = "shipperPartySnapshot.phone" required :rules = "requiredTextRules('请输入发货联系电话')" > < el-input v-model = "form.shipperPartySnapshot.phone" maxlength="30" placeholder="请输入联系电话" / > < / el-form-item >
< el-form-item label = "发货地址" prop = "shipperPartySnapshot.address" required :rules = "requiredTextRules('请输入本单实际发货地址')" class = "form-span-full" > < el-input v-model = "form.shipperPartySnapshot.address" maxlength="300" placeholder="请输入本单实际发货地址" / > < / el-form-item >
< / div >
< / div >
< div class = "party-panel" >
< h4 > 目的地信息 < / h4 >
< el-alert v-if = "availableConsignees.length === 0" title="暂无可用收货单位" description="请先在平台往来单位中启用“收货单位”类型。" type="warning" :closable="false" show -icon / >
< el-form-item label = "收货单位" prop = "consigneePartyId" required > < el-select v-model = "form.consigneePartyId" filterable clearable placeholder="请选择收货单位" @change="selectConsignee" @clear="clearConsignee"><el-option v-if="form.consigneePartySnapshot && !availableConsignees.some((party) => party.id === form.consigneePartyId)" :label="`${form.consigneePartySnapshot.name} · 历史快照`" :value="form.consigneePartyId" disabled /><el-option v-for="party in availableConsignees" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" / > < / el-select > < / el-form-item >
< div v-if = "form.consigneePartySnapshot" class="party-contact-grid" >
< el -form -item label = "收件人" prop = "consigneePartySnapshot.contactName" required :rules = "requiredTextRules('请输入收货联系人姓名')" > < el-input v-model = "form.consigneePartySnapshot.contactName" maxlength="50" placeholder="请输入收件人姓名" / > < / el-form-item >
< el-form-item label = "联系电话" prop = "consigneePartySnapshot.phone" required :rules = "requiredTextRules('请输入收货联系电话')" > < el-input v-model = "form.consigneePartySnapshot.phone" maxlength="30" placeholder="请输入收件人电话" / > < / el-form-item >
< el-form-item label = "收件地址" prop = "consigneePartySnapshot.address" required :rules = "requiredTextRules('请输入本单实际收货地址')" class = "form-span-full" > < el-input v-model = "form.consigneePartySnapshot.address" maxlength="300" placeholder="请输入收件详细地址" / > < / el-form-item >
< / div >
< / div >
< / div >
< div class = "destination-fields" >
< el-form-item v-if = "form.destinationType !== 'PRIVATE'" label="平台目的仓" prop="destinationWarehouseId" required :rules="requiredSelectRules('请选择平台维护的目的仓')"><el-select v-model="form.destinationWarehouseId" filterable placeholder="请选择当前可送达的目的仓" @change="selectDestinationWarehouse"><el-option v-for="warehouse in availableDestinationWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.code}`" :value="warehouse.id" /></el-select><small v-if="availableDestinationWarehouses.length === 0" class="field-guidance" > 平台暂未维护可用的 {{ form.destinationType = = = ' FBA ' ? ' FBA 仓 ' : ' 海外仓 ' }} , 请联系平台管理员 。 < / small > < / el -form -item >
< el-form-item label = "目的国代码" prop = "destinationCountryCode" required > < el-input v-model = "form.destinationCountryCode" :readonly="form.destinationType !== 'PRIVATE'" maxlength="2" placeholder="例如:US" / > < / el-form-item >
< el-form-item v-if = "form.destinationType === 'FBA'" label="亚马逊仓代码" prop="amazonWarehouseCode" required :rules="requiredTextRules('请选择有效的 FBA 仓')"><el-input v-model="form.amazonWarehouseCode" readonly placeholder="选择 FBA 仓后自动带出" / > < / el-form-item >
< el-form-item v-else-if = "form.destinationType === 'PRIVATE'" label="私人地址名称" prop="destinationWarehouseName" required><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="例如:洛杉矶客户自有仓" / > < / el-form-item >
< el-form-item v-else label = "目的仓名称" > < el-input v-model = "form.destinationWarehouseName" readonly placeholder="选择海外仓后自动带出" / > < / el-form-item >
< el-form-item v-if = "form.destinationType === 'FBA'" label="货件编号" prop="shipmentId" required :rules="requiredTextRules('请输入 Shipment ID')"><el-input v-model="form.shipmentId" maxlength="64" placeholder="Shipment ID" / > < / el-form-item >
< el-form-item label = "跟踪编号" prop = "referenceNo" : required = "form.destinationType === 'FBA'" : rules = "form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入 Reference ID') : undefined" > < el-input v-model = "form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? 'Reference ID' : '选填,贵司内部跟踪编号'" / > < / el-form-item >
< / div >
< / section >
< section class = "order-form-section prototype-section cargo-section" >
@@ -908,7 +876,7 @@ async function searchOrders(model: QueryModel) {
. order - form - section + . order - form - section { margin - top : 24 px ; padding - top : 22 px ; border - top : 1 px solid var ( -- border ) ; }
. order - form - section h3 { margin : 0 0 16 px ; color : var ( -- navy ) ; font - size : 16 px ; }
. order - form - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; column - gap : 18 px ; }
. order - form - grid > * , . destination - fields > * , . party - contact - grid > * , . cargo - primary - grid > * , . cargo - secondary - grid > * { min - width : 0 ; }
. order - form - grid > * , . cargo - primary - grid > * , . cargo - secondary - grid > * { min - width : 0 ; }
. order - form : deep ( . el - select ) , . order - form : deep ( . el - date - editor ) , . order - form : deep ( . el - input - number ) { width : 100 % ; }
. form - span - full { grid - column : 1 / - 1 ; }
. prototype - section { padding : 2 px 2 px 0 ; }
@@ -925,14 +893,22 @@ async function searchOrders(model: QueryModel) {
. order - section - heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px ; }
. order - section - heading h3 { margin - bottom : 5 px ; }
. order - section - heading p { margin : 0 ; color : var ( -- muted ) ; font - size : 11 px ; }
. party - select - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 14 px ; }
. party - panel { min - width : 0 ; padding : 16 px ; border : 1 px solid var ( -- border ) ; background : # fbfcfc ; }
. party - panel h4 { margin : 0 0 14 px ; color : var ( -- navy ) ; font - size : 14 px ; }
. party - panel : deep ( . el - alert ) { margin - bottom : 14 px ; }
. party - contact - grid , . destination - fields { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; column - gap : 12 px ; }
. destination - fields { margin - top : 16 px ; }
. private - address - toolbar { display : grid ; grid - template - columns : minmax ( 220 px , 1 fr ) auto auto ; gap : 10 px ; align - items : center ; margin - bottom : 16 px ; }
. prototype - form - flow { display : flex ; max - width : 900 px ; flex - direction : column ; padding - top : 4 px ; }
. prototype - form - row { display : grid ; width : 100 % ; grid - template - columns : 140 px minmax ( 0 , 560 px ) ; align - items : start ; margin - bottom : 18 px ; }
. prototype - form - row -- wide { grid - template - columns : 140 px minmax ( 0 , 720 px ) ; }
. prototype - form - row -- compact { grid - template - columns : 140 px minmax ( 0 , 420 px ) ; }
. prototype - form - row -- short { grid - template - columns : 140 px minmax ( 0 , 280 px ) ; }
. prototype - form - row -- tabs { grid - template - columns : 140 px minmax ( 0 , 520 px ) ; margin - bottom : 22 px ; }
. prototype - form - row : deep ( . el - form - item _ _label ) { width : auto ! important ; min - height : 40 px ; justify - content : flex - end ; padding : 0 20 px 0 0 ; line - height : 40 px ; }
. prototype - form - row : deep ( . el - form - item _ _content ) { display : block ; min - width : 0 ; line - height : normal ; }
. prototype - form - row : deep ( . el - input _ _wrapper ) , . prototype - form - row : deep ( . el - select _ _wrapper ) { min - height : 40 px ; }
. address - book - control { display : grid ; grid - template - columns : minmax ( 260 px , 1 fr ) auto auto ; gap : 10 px ; align - items : center ; }
. address - book - control : deep ( . el - button ) { min - height : 40 px ; margin - left : 0 ; }
. field - guidance { display : block ; margin - top : 5 px ; color : var ( -- muted ) ; line - height : 1.45 ; }
. selected - destination { display : flex ; width : min ( 100 % , 720 px ) ; align - items : center ; gap : 9 px ; margin : - 4 px 0 18 px 140 px ; padding : 10 px 12 px ; background : # f2f7f6 ; color : var ( -- primary ) ; }
. selected - destination span { color : var ( -- navy ) ; font - size : 12 px ; font - weight : 600 ; }
. flow - divider { width : min ( 100 % , 860 px ) ; margin : 4 px 0 22 px ; border - top : 1 px solid var ( -- border ) ; }
. compact - transport - grid { margin - top : 18 px ; }
. cargo - entry { padding : 16 px ; border : 1 px solid var ( -- border ) ; background : # fbfcfc ; }
. cargo - entry + . cargo - entry { margin - top : 12 px ; }
. cargo - entry > header { display : flex ; align - items : center ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px ; }
@@ -1000,7 +976,12 @@ async function searchOrders(model: QueryModel) {
. order - table - toolbar . el - button { width : 100 % ; min - height : 40 px ; }
. order - eligibility { flex - wrap : wrap ; }
. order - eligibility . el - button { width : 100 % ; min - height : 44 px ; }
. order - form - grid , . order - detail - grid , . party - select - grid , . party - contact - grid , . destination - fields , . private - address - toolbar , . cargo - primary - grid , . cargo - secondary - grid { grid - template - columns : 1 fr ; }
. order - form - grid , . order - detail - grid , . cargo - primary - grid , . cargo - secondary - grid { grid - template - columns : 1 fr ; }
. 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 ; }
. selected - destination { width : 100 % ; margin - left : 0 ; }
. address - book - control { grid - template - columns : 1 fr auto ; }
. address - book - control : deep ( . el - select ) { grid - column : 1 / - 1 ; }
. segmented - options { width : 100 % ; align - items : stretch ; flex - direction : column ; }
. segmented - options : deep ( . el - radio - button _ _inner ) { border : 1 px solid var ( -- border ) ; border - radius : 6 px ! important ; box - shadow : none ! important ; }
. warehouse - options { display : grid ; width : 100 % ; grid - template - columns : 1 fr ; }