@@ -41,8 +41,6 @@ const partyTypes = [
{ value : 'CONSIGNEE' , label : '收货人' } ,
{ value : 'NOTIFY_PARTY' , label : '通知人' } ,
] as const
const serviceTermOptions = [ 'CY-CY' , 'CY-DOOR' , 'DOOR-CY' , 'DOOR-DOOR' ]
const freightTermOptions = [ 'PREPAID' , 'COLLECT' ]
const containerTypeOptions = [ '20GP' , '40GP' , '40HC' , '40NR' , '45HC' ]
const containerKindOptions = [
{ value : 'FCL' , label : '整箱 FCL' } ,
@@ -100,15 +98,16 @@ function blankParties(): OceanBookingParty[] {
}
function blankData ( ) : OceanBookingDraftData {
return {
customerId : '' , customerName : '' , channelCode : '' , warehouseId : '' , warehouseName : '' , parties : blankParties ( ) ,
customerId : null , customerName : null , channelCode : '' , warehouseId : '' , warehouseName : '' , serviceTerm : null , freightTerm : null , parties : blankParties ( ) ,
containerRequirements : [ createBlankContainerRequirement ( ) ] ,
}
}
const form = reactive < OceanBookingSaveRequest > ( { expectedVersion : 0 , data : blankData ( ) } )
const rules : FormRules = {
customerId : [ { required : true , message : '请选择客户' , trigger : 'change' } ] ,
warehouseId : [ { required : true , message : '请选择装柜仓库' , trigger : 'change' } ] ,
channelCode : [ { required : true , message : '请选择物流渠道' , trigger : 'change' } ] ,
serviceTerm : [ { required : true , message : '请输入主要运输条款 Service Term' , trigger : 'blur' } ] ,
freightTerm : [ { required : true , message : '请输入运费条款 Freight Term' , trigger : 'blur' } ] ,
}
const editable = computed ( ( ) => isNew . value || detail . value ? . status === 'DRAFT' )
const canSave = computed ( ( ) => isNew . value
@@ -143,10 +142,17 @@ async function loadOptions() {
optionsLoading . value = false
}
}
function selectCustomer ( customerId : string ) {
function selectCustomer ( value : unknown ) {
const customerId = value == null || value === '' ? null : String ( value )
form . data . customerId = customerId
const customer = customers . value . find ( ( item ) => String ( item . id ) === customerId )
form . data . customerName = customer ? . tenantName || ''
const customer = customerId == null ? null : customers . value . find ( ( item ) => String ( item . id ) === customerId )
form . data . customerName = customer ? . tenantName || null
}
function normalizeDraftData ( ) {
form . data . customerId = form . data . customerId || null
form . data . customerName = form . data . customerId ? form . data . customerName : null
form . data . serviceTerm = form . data . serviceTerm ? . trim ( ) || null
form . data . freightTerm = form . data . freightTerm ? . trim ( ) || null
}
function selectWarehouse ( value : unknown ) {
const warehouseId = String ( value || '' )
@@ -190,14 +196,12 @@ function applyDetail(value: OceanBookingDetail) {
detail . value = value
form . expectedVersion = value . version
form . data = {
customerId : value . customerId || '' , customerName : value . customerName || '' , channelCode : value . channelCode || '' , warehouseId : value . warehouseId || '' , warehouseName : value . warehouseName || '' , billOfLadingNo : value . billOfLadingNo ,
customerId : value . customerId ? ? null , customerName : value . customerName ? ? null , channelCode : value . channelCode || '' , warehouseId : value . warehouseId || '' , warehouseName : value . warehouseName || '' ,
shippingCompanyId : value . shippingCompanyId , shippingCompanyName : value . shippingCompanyName ,
bookingAgentId : value . bookingAgentId , bookingAgentName : value . bookingAgentName ,
vesselName : value . vesselName , voyageNo : value . voyageNo ,
departurePortCode : value . departurePortCode , departurePortName : value . departurePortName ,
destinationPortCode : value . destinationPortCode , destinationPortName : value . destinationPortName ,
placeOfDelivery : value . placeOfDelivery , serviceTerm : value . serviceTerm , freightTerm : value . freightTerm ,
estimatedSailingAt : value . estimatedSailingAt , estimatedArrivalAt : value . estimatedArrivalAt ,
remark : value . remark ,
parties : value . parties . length ? value . parties . map ( ( { partyType , partyName , contactName , contactPhone , address } ) => ( { partyType , partyName , contactName , contactPhone , address } ) ) : blankParties ( ) ,
containerRequirements : value . containerRequirements . length ? value . containerRequirements . map ( ( { containerType , containerKind , capacityCbm , maxWeightKg , quantity } ) => ( { containerType , containerKind , capacityCbm : capacityCbm ? ? null , maxWeightKg : maxWeightKg ? ? null , quantity : quantity ? ? null } ) ) : [ createBlankContainerRequirement ( ) ] ,
@@ -217,8 +221,9 @@ async function load() {
async function save ( ) {
if ( ! canSave . value ) return
actionError . value = null
normalizeDraftData ( )
if ( ! await formRef . value ? . validate ( ) . catch ( ( ) => false ) ) {
actionError . value = { message : '请完善客户、 装柜仓库和 物流渠道等必填信息' }
actionError . value = { message : '请完善装柜仓库、 物流渠道和运输条款 等必填信息' }
return
}
if ( form . data . parties . some ( ( party ) => ! party . partyName . trim ( ) ) ) {
@@ -339,7 +344,7 @@ onMounted(() => { void load(); void loadOptions() })
< template # header >
< div class = "detail-heading" >
< el-button @click ="router.push('/ocean-bookings')" > < ArrowLeft :size = "16" / > 返回列表 < / el-button >
< PageToolbar class = "booking-page-toolbar" as = "section" : title = "detail?.bookingNo || '订舱资料'" : description = "detail ? `${detail.customerName || '— '} · ${detail.pol || '—'} → ${detail.pod || '—'}` : '填写并保存订舱资料'" >
< PageToolbar class = "booking-page-toolbar" as = "section" : title = "detail?.bookingNo || '订舱资料'" : description = "detail ? `${detail.customerName || '未关联客户 '} · ${detail.pol || '—'} → ${detail.pod || '—'}` : '填写并保存订舱资料'" >
< template v-if = "detail" #actions><div class="heading-status"><StatusBadge :status="statusLabel(detail.status)" / > < / div > < / template >
< / PageToolbar >
< / div >
@@ -361,33 +366,33 @@ onMounted(() => { void load(); void loadOptions() })
< section class = "booking-group" aria -label = " 基本信息 " >
< h3 > 基本信息 < / h3 > < div class = "booking-group__body booking-group__body--fields" >
< el-form-item label = "订舱号" > < el-input : model -value = " detail ? .bookingNo | | ' 保存后生成 ' " disabled / > < / el-form-item >
< el-form-item label = "客户" prop = "customerId "> < el-select :model-value = "form.data.customerId" filterable placeholder = "请选择客户" :loading = "optionsLoading" :disabled = "!isNew" style = "width:100%" @ update : model -value = " selectCustomer ( String ( $ event | | ' ' ) ) " > < el-option v-if = "form.data.customerId && !selectableCustomers.some((customer) => String(customer.id) === String(form.data.customerId))" :label="form.data.customerName || '客户名称缺失'" :value="String(form.data.customerId)" /><el-option v-for="customer in selectableCustomers" :key="String(customer.id)" :label="customer.tenantName" :value="String(customer.id)" / > < / el-select > < / el-form-item >
< el-form-item label = "客户(可选) " > < el-select :model-value = "form.data.customerId" filterable clearable placeholder = "请选择客户(可选)" no -data -text = " 暂无可选客户 " :loading = "optionsLoading" style = "width:100%" @ update :model-value = "selectCustomer" > < el-option v-if = "form.data.customerId && !selectableCustomers.some((customer) => String(customer.id) === String(form.data.customerId))" :label="form.data.customerName || '客户名称缺失'" :value="String(form.data.customerId)" /><el-option v-for="customer in selectableCustomers" :key="String(customer.id)" :label="customer.tenantName" :value="String(customer.id)" / > < / el-select > < / el-form-item >
< el-form-item label = "状态" > < el-input : model -value = " detail ? statusLabel ( detail.status ) : ' 待创建 ' " disabled / > < / el-form-item >
< el-form-item label = "装柜仓库" prop = "warehouseId" > < el-select :model-value = "form.data.warehouseId" filterable placeholder = "请选择装柜仓库" :loading = "optionsLoading" style = "width:100%" @ update :model-value = "selectWarehouse" > < el-option v-for = "warehouse in warehouses" :key="String(warehouse.id)" :label="warehouse.warehouseName" :value="String(warehouse.id)" / > < / el-select > < / el-form-item >
< el-form-item label = "物流渠道" prop = "channelCode" > < el-select :model-value = "form.data.channelCode" filterable : disabled = "!form.data.warehouseId || channelsLoading || !!channelsError || channelUnavailable" :loading = "channelsLoading" : placeholder = "form.data.warehouseId ? '请选择物流渠道' : '请先选择装柜仓库'" no -data -text = " 该仓库暂无有效物流渠道 " style = "width:100%" @ update : model -value = " selectChannel ( String ( $ event | | ' ' ) ) " > < el-option v-if = "form.data.channelCode && !channels.some((channel) => channel.channelCode === form.data.channelCode)" :label="selectedChannelName || form.data.channelCode" :value="form.data.channelCode" /><el-option v-for="channel in channels" :key="channel.channelCode" :label="channel.channelName" :value="channel.channelCode"><span>{{ channel.channelName }}</span><small class="channel-code">{{ channel.channelCode }}</small></el-option></el-select><div v-if="channelsError" class="field-error"><span>{{ channelsError }}</span><el-button link type="primary" :loading="channelsLoading" @click="loadChannels(form.data.warehouseId)">重新加载</el-button></div><div v-else-if="channelUnavailable" class="field-hint" > 该仓库暂无有效物流渠道 , 请更换仓库或联系管理员 。 < / div > < / el -form -item >
< el-form-item label = "提单号" > < el-input v-model = "form.data.billOfLadingNo" placeholder="请输入提单号(可选)" / > < / el-form-item >
< el-form-item label = "提单号" > < el-input : model -value = " detail ? .billOfLadingNo | | ' ' " disabled placeholder = "确认放舱时录入" / > < / el-form-item >
< el-form-item label = "船公司" > < el-select :model-value = "form.data.shippingCompanyId" filterable clearable placeholder = "请选择船公司" :loading = "optionsLoading" style = "width:100%" @ update : model -value = " selectPartner ( ' shippingCompany ' , String ( $ event | | ' ' ) ) " > < el-option v-if = "form.data.shippingCompanyId && !shippingCompanies.some((partner) => String(partner.id) === String(form.data.shippingCompanyId))" :label="form.data.shippingCompanyName || '船公司名称缺失'" :value="String(form.data.shippingCompanyId)" /><el-option v-for="partner in shippingCompanies" :key="String(partner.id)" :label="partner.partnerName" :value="String(partner.id)" / > < / el-select > < / el-form-item >
< el-form-item label = "订舱代理" > < el-select :model-value = "form.data.bookingAgentId" filterable clearable placeholder = "请选择订舱代理" :loading = "optionsLoading" style = "width:100%" @ update : model -value = " selectPartner ( ' bookingAgent ' , String ( $ event | | ' ' ) ) " > < el-option v-if = "form.data.bookingAgentId && !bookingAgents.some((partner) => String(partner.id) === String(form.data.bookingAgentId))" :label="form.data.bookingAgentName || '订舱代理名称缺失'" :value="String(form.data.bookingAgentId)" /><el-option v-for="partner in bookingAgents" :key="String(partner.id)" :label="partner.partnerName" :value="String(partner.id)" / > < / el-select > < / el-form-item >
< / div >
< / section >
< section class = "booking-group" aria -label = " 航线船期 " >
< h3 > 航线船期 < / h3 > < div class = "booking-group__body booking-group__body--fields" >
< el-form-item label = "船名" > < el-input v-model = "form.data.vesselName" placeholder="请输入船名" / > < / el-form-item >
< el-form-item label = "航次" > < el-input v-model = "form.data.voyageNo" placeholder="请输入航次" / > < / el-form-item >
< el-form-item label = "船名" > < el-input : model -value = " detail ? .vesselName | | ' ' " disabled placeholder = "确认放舱时录入" / > < / el-form-item >
< el-form-item label = "航次" > < el-input : model -value = " detail ? .voyageNo | | ' ' " disabled placeholder = "确认放舱时录入" / > < / el-form-item >
< el-form-item label = "起运港代码" > < el-input v-model = "form.data.departurePortCode" placeholder="例如:CNNGB" / > < / el-form-item >
< el-form-item label = "起运港" > < el-input v-model = "form.data.departurePortName" placeholder="例如:宁波港" / > < / el-form-item >
< el-form-item label = "目的港代码" > < el-input v-model = "form.data.destinationPortCode" placeholder="例如:USLAX" / > < / el-form-item >
< el-form-item label = "目的港" > < el-input v-model = "form.data.destinationPortName" placeholder="例如:洛杉矶港" / > < / el-form-item >
< el-form-item class = "booking-field--wide" label = "交货地点" > < el-input v-model = "form.data.placeOfDelivery" placeholder="请输入最终交货地点" / > < / el-form-item >
< el-form-item label = "预计开船" > < el-date-picker v-model = "form.data.estimatedSailingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计开船时间" style= "width:100%" / > < / el-form-item >
< el-form-item label = "预计到港" > < el-date-picker v-model = "form.data.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到港时间" style= "width:100%" / > < / el-form-item >
< el-form-item label = "预计开船" > < el-date-picker : model -value = " detail ? .estimatedSailingAt | | null " type = "datetime" value -format = " YYYY -MM -DDTHH : mm : ss " disabled placeholder = "确认放舱时录入" style = "width:100%" / > < / el-form-item >
< el-form-item label = "预计到港" > < el-date-picker : model -value = " detail ? .estimatedArrivalAt | | null " type = "datetime" value -format = " YYYY -MM -DDTHH : mm : ss " disabled placeholder = "确认放舱时录入" style = "width:100%" / > < / el-form-item >
< / div >
< / section >
< section class = "booking-group" aria -label = " 条款与柜量 " >
< h3 > 条款与柜量 < / h3 > < div class = "booking-group__body" >
< div class = "booking-terms" >
< el-form-item label = "服务条款 " > < el-select v-model = "form.data.serviceTerm" clearable placeholder="请选择服务条款"><el-option v-for="value in s erviceTermOptions" :key="value" :label="value" :value="value" / > < / el-select > < / el-form-item >
< el-form-item label = "运费条款" > < el-select v-model = "form.data.freightTerm" clearable placeholder="请选择运费条款"><el-option v-for="value in f reightTermOptions" :key="value" :label="value" :value="value" / > < / el-select > < / el-form-item >
< el-form-item label = "主要运输条款 Service Term" prop = "serviceTerm "> < el-input v-model = "form.data.serviceTerm" clearable maxlength="32" placeholder="请输入 S ervice Term" / > < / el-form-item >
< el-form-item label = "运费条款 Freight Term" prop = "freightTerm "> < el-input v-model = "form.data.freightTerm" clearable maxlength="32" placeholder="请输入 F reight Term" / > < / el-form-item >
< / div >
< div class = "container-table-wrap" aria -label = " 箱型箱量 " >
< table class = "container-table" >