@@ -12,6 +12,8 @@ import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from
const props = defineProps < { modelValue : SaveTruckAppointmentRequest ; resource ? : TruckAppointmentResource | null ; readonly ? : boolean ; submitting ? : boolean } > ( )
const emit = defineEmits < { 'update:modelValue' : [ SaveTruckAppointmentRequest ] ; submit : [ ] ; attachments : [ ] } > ( )
const formRef = ref < FormInstance > ( )
const relationWarning = ref ( '' )
const derivedCustomerSource = ref < 'booking' | 'container' | null > ( null )
const defaults : Partial < SaveTruckAppointmentRequest > = { expectedVersion : 0 , currency : 'RMB' , payableAmount : '0.00' , receivableAmount : '0.00' , packageCount : 0 , palletCount : 0 , transitMinutes : 0 }
function normalizeDraft ( value : SaveTruckAppointmentRequest ) : SaveTruckAppointmentRequest { return { ... defaults , ... value , currency : value . currency ? . trim ( ) . toUpperCase ( ) || 'RMB' } }
const draft = reactive < SaveTruckAppointmentRequest > ( normalizeDraft ( props . modelValue ) )
@@ -27,6 +29,7 @@ const phoneRule = (label: string) => ({ validator: (_rule: unknown, value: unkno
else callback ( new Error ( ` ${ label } 格式不正确 ` ) )
} , trigger : 'blur' } )
const rules : FormRules = {
customerId : [ { required : true , message : '请选择客户' , trigger : 'change' } , { validator : ( _rule , value , callback ) => callback ( selectableCustomers . value . some ( ( item ) => String ( item . id ) === String ( value ) ) ? undefined : new Error ( '请选择有效的启用客户' ) ) , trigger : 'change' } ] ,
carrierId : [ { required : true , message : '请选择承运商' , trigger : 'change' } , { validator : ( _rule , value , callback ) => callback ( selectableCarriers . value . some ( ( item ) => String ( item . id ) === String ( value ) ) ? undefined : new Error ( '请选择有效的合作中拖车供应商' ) ) , trigger : 'change' } ] ,
plannedLoadingAt : [ { required : true , message : '请选择预计装箱时间' , trigger : 'change' } ] ,
fleetPhone : [ phoneRule ( '车队电话' ) ] , driverPhone : [ phoneRule ( '司机电话' ) ] , storePhone : [ phoneRule ( '门店电话' ) ] ,
@@ -34,7 +37,7 @@ const rules: FormRules = {
grossWeightKg : [ nonNegative ( '毛重' ) ] , volumeCbm : [ nonNegative ( '体积' ) ] , payableAmount : [ nonNegative ( '应付拖车费' ) ] , receivableAmount : [ nonNegative ( '应收拖车费' ) ] ,
currency : [ { pattern : /^[A-Z]{3,8}$/ , message : '币种请输入 3-8 位大写字母(如 RMB)' , trigger : 'blur' } ] ,
}
const relationText = computed ( ( ) => [ props . resource ? . customerName , props . resource ? . outboundOrderId , props . resource ? . containerBookingId ] . filter ( Boolean ) . join ( ' / ' ) || '由服务端关联资源返回 ' )
const relationText = computed ( ( ) => [ props . resource ? . customerName , props . resource ? . oceanBookingNo , props . resource ? . cabinetNo , props . resource ? . containerNo , props . resource ? . warehouseName ] . filter ( Boolean ) . join ( ' / ' ) || '无历史关联 ' )
const customers = ref < PlatformTenantResource [ ] > ( [ ] )
const carriers = ref < Supplier [ ] > ( [ ] )
const containers = ref < ContainerBookingResource [ ] > ( [ ] )
@@ -42,9 +45,7 @@ const bookings = ref<OceanBookingResource[]>([])
const warehouses = ref < PlatformWarehouseSummaryResource [ ] > ( [ ] )
const optionsLoading = ref ( false )
const optionsError = ref ( '' )
const selectableCustomers = computed ( ( ) => customers . value . filter ( ( item ) => (
item . status === 'ACTIVE' || String ( item . id ) === String ( draft . customerId )
) ) )
const selectableCustomers = computed ( ( ) => customers . value . filter ( ( item ) => item . status === 'ACTIVE' ) )
const selectableWarehouses = computed ( ( ) => warehouses . value . filter ( ( item ) => (
item . status === 'ACTIVE' || String ( item . id ) === String ( draft . warehouseId )
) ) )
@@ -52,10 +53,35 @@ const selectableCarriers = computed(() => carriers.value.filter((item) => (
item . status === 'ACTIVE'
) ) )
function customerLabel ( customer : PlatformTenantResource ) { return customer . tenantName }
function selectCustomer ( customerId : string ) { const customer = customers . value . find ( ( item ) => String ( item . id ) === customerId ) ; draft . customerId = customerId || null ; draft . customerName = customer ? customerLabel ( customer ) : null }
function selectCustomer ( customerId : string ) { const customer = selectableCustomers . value . find ( ( item ) => String ( item . id ) === customerId ) ; draft . customerId = customer ? . id ? String ( customer . id ) : null ; draft . customerName = customer ? customerLabel ( customer ) : null ; derivedCustomerSource . value = null ; relationWarning . value = '' }
function selectCarrier ( carrierId : string ) { const carrier = selectableCarriers . value . find ( ( item ) => String ( item . id ) === carrierId ) ; draft . carrierId = carrier ? . id || null ; draft . carrierName = carrier ? . name || null }
function selectContainer ( containerId : string ) { const item = containers . value . find ( ( entry ) => entry . id === containerId ) ; draft . containerBookingId = containerId || null ; if ( item ) { draft . loadingPlanId = item . planId ; draft . warehouseId = item . warehouseId } }
function selectBooking ( bookingId : string ) { const item = bookings . value . find ( ( entry ) => entry . id === bookingId ) ; draft . oceanBookingId = bookingId || null ; if ( item ? . customerId ) selectCustomer ( item . customerId ) }
function applyRelatedCustomer ( customerId : string | null | undefined , customerName : string | null | undefined , source : 'booking' | 'container' ) {
if ( ! customerId ) return
if ( ! draft . customerId ) { draft . customerId = String ( customerId ) ; draft . customerName = customerName || null ; derivedCustomerSource . value = source ; return }
if ( String ( draft . customerId ) !== String ( customerId ) ) relationWarning . value = '所选关联资源与当前客户不一致,请重新选择客户或清空该关联'
}
function selectContainer ( containerId : string ) {
const item = containers . value . find ( ( entry ) => entry . id === containerId )
draft . containerBookingId = item ? . id || null
relationWarning . value = ''
if ( ! item ) {
draft . loadingPlanId = null ; draft . warehouseId = null
if ( derivedCustomerSource . value === 'container' ) { draft . customerId = null ; draft . customerName = null ; derivedCustomerSource . value = null }
return
}
draft . loadingPlanId = item . planId || null ; draft . warehouseId = item . warehouseId || null
applyRelatedCustomer ( item . customerId , item . customerName , 'container' )
}
function selectBooking ( bookingId : string ) {
const item = bookings . value . find ( ( entry ) => entry . id === bookingId )
draft . oceanBookingId = item ? . id || null
relationWarning . value = ''
if ( ! item ) {
if ( derivedCustomerSource . value === 'booking' ) { draft . customerId = null ; draft . customerName = null ; derivedCustomerSource . value = null }
return
}
applyRelatedCustomer ( item . customerId , item . customerName , 'booking' )
}
async function loadOptions ( ) {
optionsLoading . value = true ; optionsError . value = ''
const results = await Promise . allSettled ( [
@@ -83,24 +109,27 @@ watch(() => props.readonly, (readonly) => { if (!readonly && !customers.value.le
< template >
< el-form ref = "formRef" :model = "draft" :rules = "rules" label -position = " top " : disabled = "readonly || submitting" class = "truck-form" >
< el-alert v-if = "resource" type=" info" :closable= "false" class= "relation-alert" > 关联资源 : {{ relationText }} ; 柜型由关联柜体返回 ( 只读 ) < / el -alert >
< el-alert type = "info" :closable = "false" class = "relation-alert" > 订舱 、 柜次和装柜计划均为可选关联 , 不影响独立开立拖车预约 ; 选择后仅用于补全快照和一致性检查 。 < / el-alert >
< el-alert v-if = "resource" type="info" :closable="false" class="relation-alert" > 历史关联快照 : {{ relationText }} < / el -alert >
< el-alert v-if = "relationWarning" type="warning" :closable="false" show-icon :title="relationWarning" class="relation-alert" / >
< el-alert v-if = "optionsError" type="warning" :closable="false" show-icon :title="optionsError" class="relation-alert"><el-button link type="warning" :loading="optionsLoading" @click="loadOptions" > 重新加载选项 < / el -button > < / el-alert >
< el-row :gutter = "16" >
< el-col :xs = "24" > < el-divider content -position = " left " > 关联资源 < / el-divider > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "关联柜次 " > < el-select :model-value = "draft.containerBooking Id" filterable clearable placeholder = "请选择柜次 " :loading = "optionsLoading" style = "width:100%" @ update : model -value = " selectContainer ( String ( $ event | | ' ' ) ) " > < el-option v-for = "item in containers" :key="item.id" :label="`柜次号:${item.cabinetNo || item.bookingNo || '待生成'} / 集装箱号:${item.containerNo || '待录入'}` " :value="item.id" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "关联订舱 " > < el-select :model-value = "draft.oceanBooking Id" filterable clearable placeholder = "请选择订舱单 " :loading = "optionsLoading" style = "width:100%" @ update : model -value = " selectBooking ( String ( $ event | | ' ' ) ) " > < el-option v-for = "item in bookings" :key="item.id" :label="item.bookingNo || `订舱 ${item.id}` " :value="item.id" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "客户" > < el-select :model-value = "draft.customerId" filterable clearable placeholder = "请选择客户" :loading = "optionsLoading" style = "width:100%" @ update : model -value = " selectCustomer ( String ( $ event | | ' ' ) ) " > < el-option v-if = "draft.customerId && !selectableCustomers.some((item) => String(item.id) === String(draft.customerId))" :label="draft.customerName || '当前客户' " : value="String(draft.customerId)" /><el-option v-for="item in selectableCustomers" :key="String(item.id)" :label="customerLabel(item)" :value="String(item.id)" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" > < el-divider content -position = " left " > 基本信息 < / el-divider > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "客户" prop = "customerId "> < el-select :model-value = "draft.customer Id" filterable clearable placeholder = "请选择客户 " :loading = "optionsLoading" no -data -text = " 暂无启用的客户 " style = "width:100%" @ update : model -value = " selectCustomer ( String ( $ event | | ' ' ) ) " > < el-option v-if = "draft.customerId && !selectableCustomers.some((item) => String(item.id) === String(draft.customerId))" :label="draft.customerName || '历史客户(需重新选择)'" :value="String(draft.customerId)" disabled /><el-option v-for="item in selectableCustomers" :key="String(item.id)" :label="customerLabel(item) " :value="String( item.id) " / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "承运商" prop = "carrierId "> < el-select :model-value = "draft.carrier Id" filterable clearable placeholder = "请选择承运商 " :loading = "optionsLoading" no -data -text = " 暂无可用拖车供应商 " style = "width:100%" @ update : model -value = " selectCarrier ( String ( $ event | | ' ' ) ) " > < el-option v-if = "draft.carrierId && !selectableCarriers.some((item) => String(item.id) === String(draft.carrierId))" :label="draft.carrierName || '当前承运商(需重新选择)'" :value="String(draft.carrierId)" disabled /><el-option v-for="item in selectableCarriers" :key="String(item.id)" :label="item.name " :value="String( item.id) " / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "预计装箱时间" prop = "plannedLoadingAt" > < el-date-picker v-model = "draft.plannedLoadingAt" type="datetime " value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计装箱时间" style="width:100%" / > < / el-form-item > < / el-col >
< el-col :xs = "24" > < el-divider content -position = " left " > 可选关联 < / el-divider > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "关联柜次(可选)" > < el-select :model-value = "draft.containerBookingId" filterable clearable placeholder = "可选择柜次辅助补全" :loading = "optionsLoading" no -data -text = " 暂无可关联柜次 " style = "width:100%" @ update : model -value = " selectContainer ( String ( $ event | | ' ' ) ) " > < el-option v-for = "item in containers" :key="item.id" :label="`柜次号:${item.cabinetNo || item.bookingNo || '待生成'} / 集装箱号:${item.containerNo || '待录入'}`" :value="item.id" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "关联订舱(可选)" > < el-select :model-value = "draft.oceanBookingId" filterable clearable placeholder = "可选择订舱辅助补全" :loading = "optionsLoading" no -data -text = " 暂无可关联订舱 " style = "width:100%" @ update : model -value = " selectBooking ( String ( $ event | | ' ' ) ) " > < el-option v-for = "item in bookings" :key="item.id" :label="item.bookingNo || '订舱号缺失'" :value="item.id" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "仓库" > < el-select v-model = "draft.warehouseId" filterable clearable placeholder="请选择仓库" :loading="optionsLoading" style="width:100%"><el-option v-for="item in selectableWarehouses" :key="String(item.id)" :label="`${item.warehouseName}( ${item.warehouseCode}) `" :value="String(item.id)" / > < / el-select > < / el-form-item > < / el-col >
< el-col v-if = "resource" :xs="24" :sm="12"><el-form-item label="派车单号"><el-input :model-value="resource.appointmentNo || ''" disabled / > < / el-form-item > < / el-col >
< el-col :xs = "24" > < el-divider content -position = " left " > 运输与时间计划 < / el-divider > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "承运商" prop = "carrierId" > < el-select :model-value = "draft.carrierId" filterable clearable placeholder = "请选择承运商" :loading = "optionsLoading" no -data -text = " 暂无可用拖车供应商 " style = "width:100%" @ update : model -value = " selectCarrier ( String ( $ event | | ' ' ) ) " > < el-option v-if = "draft.carrierId && !selectableCarriers.some((item) => String(item.id) === String(draft.carrierId))" :label="draft.carrierName || '当前承运商(需重新选择)'" :value="String(draft.carrierId)" disabled /><el-option v-for="item in selectableCarriers" :key="String(item.id)" :label="item.name" :value="String(item.id)" / > < / el-select > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "车队联系人" > < el-input v-model = "draft.fleetContact" placeholder="请输入车队联系人" / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "车队电话" prop = "fleetPhone" > < el-input v-model = "draft.fleetPhone" placeholder="请输入车队联系电话" maxlength="32" show -word -limit / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "司机" > < el-input v-model = "draft.driverName" placeholder="请输入司机姓名" / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "司机电话" prop = "driverPhone" > < el-input v-model = "draft.driverPhone" placeholder="请输入司机联系电话" maxlength="32" show -word -limit / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "司机身份证号" > < el-input v-model = "draft.driverIdCard" placeholder="请输入司机身份证号" / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "车头号" > < el-input v-model = "draft.vehicleNo" placeholder="请输入车牌或车头号" / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "12" > < el-form-item label = "预计装箱时间" prop = "plannedLoadingAt" > < el-date-picker v-model = "draft.plannedLoadingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计装箱时间" style="width:100%" / > < / el-form-item > < / el-col >
< el-col v-if = "resource" :xs="24" :sm="12"><el-form-item label="实际装箱时间"><el-input :model-value="resource.actualLoadingAt || ''" disabled / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "8" > < el-form-item label = "件数" prop = "packageCount" > < el-input v-model = "draft.packageCount" inputmode="numeric" placeholder="请输入件数" / > < / el-form-item > < / el-col >
< el-col :xs = "24" :sm = "8" > < el-form-item label = "毛重(kg) " prop = "grossWeightKg" > < el-input v-model = "draft.grossWeightKg" inputmode="decimal" placeholder="请输入毛重" / > < / el-form-item > < / el-col >