@@ -249,23 +249,44 @@ onMounted(() => { void load(); void loadOptions() })
< / div >
< / div >
< el-alert v-if = "optionsError" type="warning" :closable="false" show-icon :title="optionsError"><el-button link type="warning" :loading="optionsLoading" @click="loadOptions" > 重新加载选项 < / el -button > < / el-alert >
< el-form ref = "formRef" :model = "form.data" :rules = "rules" label -position = " top " :disabled = "!editable" class = "form-grid " >
< 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-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 v-model = "form.data.billOfLadingNo" 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.bookingAgent Id" 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 bookingAgent s" :key="String(partn er.id)" :label="partner.partner Name" :value="String(partn er.id)" / > < / el-select > < / el-form-item >
< 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.voyage No" placeholder="请输入航次 " / > < / el-form-item >
< el-form-item label = "起运港代码 " > < el-input v-model = "form.data.departurePortCode " p laceholder="例如: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 label = "交货地点" > < el-input v-model = "form.data.placeOfDelivery" placeholder="请输入最终交货地点" / > < / el-form-item >
< el-form-item label = "服务条款" > < el-input v-model = "form.data.serviceTerm" placeholder="例如:CY-CY" / > < / el-form-item >
< el-form-item label = "运费条款 " > < el-input v-model = "form.data.freightTerm " placeholder="例如:PREPAID " / > < / 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 class = "wide" label = "备注 " > < el-input v-model = "form.data.remark" type="textarea " placeholder="请输入订舱备注(可选) " / > < / el-form-item >
< el-form ref = "formRef" :model = "form.data" :rules = "rules" label -position = " top " :disabled = "!editable" class = "booking-groups " >
< section class = "booking-group" aria -label = " 基本信息 " >
< h3 > 基本信息 < / h3 > < div class = "booking-group__body" >
< 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.customer Id" filterable placeholder = "请选择客户 " :loading = "optionsLoading" :disabled = "!isNew" style = "width:100%" @ update : model -value = " selectCustomer ( String ( $ event | | ' ' ) ) " > < el-option v-for = "customer in selectableCustomer s" :key="String(custom er.id)" :label="customer.tenant Name" :value="String(custom er.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 = "提单号 " > < el-input v-model = "form.data.billOfLading No" 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" >
< 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 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 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 >
< / div >
< / section >
< section class = "booking-group" aria -label = " 条款与柜量 " >
< h3 > 条款与柜量 < / h3 > < div class = "booking-group__body" >
< el-form-item label = "服务条款" > < el-input v-model = "form.data.serviceTerm" placeholder="例如:CY-CY" / > < / el-form-item >
< el-form-item label = "运费条款" > < el-input v-model = "form.data.freightTerm" placeholder="例如:PREPAID" / > < / el-form-item >
< div v-for = "(container, index) in form.data.containerRequirements" :key="index" class="container-row" >
< el -input v-model = "container.containerType" :disabled="!editable" placeholder="箱型,如 40HC" / >
< el-input v-model = "container.containerKind" :disabled="!editable" placeholder="类型,如 FCL" / >
< el-input-number v-model = "container.quantity" :disabled="!editable" :min="1" :max="999" / >
< el-button v-if = "editable" type="danger" plain :disabled="form.data.containerRequirements.length === 1" @click="removeContainer(index)"><Trash2 :size="15" / > 删除 < / el-button >
< / div >
< el-button v-if = "editable" class="add-container" @click="addContainer"><Plus :size="16" / > 新增箱型 < / el-button >
< el-form-item label = "备注" > < el-input v-model = "form.data.remark" type="textarea" :rows="3" placeholder="请输入订舱备注(可选)" / > < / el-form-item >
< / div >
< / section >
< / el-form >
< / section >
@@ -282,16 +303,6 @@ onMounted(() => { void load(); void loadOptions() })
< / div >
< / section >
< section class = "surface-panel" >
< div class = "section-title" > < h2 > 箱型需求 < / h2 > < el-button v-if = "editable" @click="addContainer"><Plus :size="16" / > 新增箱型 < / el-button > < / div >
< div v-for = "(container, index) in form.data.containerRequirements" :key="index" class="container-row" >
< el -input v-model = "container.containerType" :disabled="!editable" placeholder="箱型,如 40HC" / >
< el-input v-model = "container.containerKind" :disabled="!editable" placeholder="类型,如 FCL" / >
< el-input-number v-model = "container.quantity" :disabled="!editable" :min="1" :max="999" / >
< el-button v-if = "editable" type="danger" plain :disabled="form.data.containerRequirements.length === 1" @click="removeContainer(index)"><Trash2 :size="15" / > 删除 < / el-button >
< / div >
< / section >
< section v-if = "detail" class="surface-panel history-panel" >
< div class = "section-title" > < div > < h2 > 状态历史 < / h2 > < p > 当前进度 : { { statusLabel ( detail . status ) } } < / p > < / div > < / div >
< el-timeline > < el-timeline-item v-for = "event in detail.histories" :key="event.id" :timestamp="event.occurredAt"><strong>{{ statusLabel(event.fromStatus) }} → {{ statusLabel(event.toStatus) }}</strong><p>{{ event.operatorName || '系统' }}<span v-if="event.reason" > · {{ event.reason }} < / span > < / p > < / el -timeline -item > < / el-timeline >
@@ -312,5 +323,5 @@ onMounted(() => { void load(); void loadOptions() })
< / template >
< style scoped >
. booking - detail { min - width : 0 } . detail - heading { display : flex ; min - width : 0 ; align - items : flex - start ; gap : 16 px } . booking - page - toolbar { min - width : 0 ; flex : 1 } . section - title p , . history - panel p { margin : 5 px 0 0 ; color : var ( -- cc - muted ) ; font - size : 13 px } . heading - status { display : flex ; align - items : center ; gap : 8 px } . surface - panel { padding : 20 px } . surface - panel h2 { margin : 0 ; font - size : 16 px } . section - title { display : flex ; justify - content : space - between ; gap : 12 px ; align - items : flex - start ; margin - bottom : 18 px } . actions { display : flex ; flex - wrap : wrap ; gap : 8 px } . form - grid { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ; gap : 0 16 px } . form - grid . wide { grid - column : 1 / - 1 } . party - grid { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ; gap : 12 px ; margin - top : 16 px } . subsection { display : grid ; gap : 10 px ; padding : 14 px ; border : 1 px solid var ( -- cc - border ) ; border - radius : 6 px } . subsection h3 { margin : 0 ; font - size : 14 px } . container - row { display : grid ; grid - template - columns : 1 fr 1 fr 160 px auto ; gap : 10 px ; margin - top : 10 px } . history - panel strong { font - size : 14 px } @ media ( max - width : 900 px ) { . form - grid , . party - grid { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) } . container - row { grid - template - columns : 1 fr 1 fr } } @ media ( max - width : 600 px ) { . detail - heading { flex - direction : column } . booking - page - toolbar { width : 100 % } . form - grid , . party - grid , . container - row { grid - template - columns : 1 fr } . form - grid . wide { grid - column : auto } . section - title { flex - direction : column } . actions { width : 100 % } }
. booking - detail { min - width : 0 } . detail - heading { display : flex ; min - width : 0 ; align - items : flex - start ; gap : 16 px } . booking - page - toolbar { min - width : 0 ; flex : 1 } . section - title p , . history - panel p { margin : 5 px 0 0 ; color : var ( -- cc - muted ) ; font - size : 13 px } . heading - status { display : flex ; align - items : center ; gap : 8 px } . surface - panel { padding : 20 px } . surface - panel h2 { margin : 0 ; font - size : 16 px } . section - title { display : flex ; justify - content : space - between ; gap : 12 px ; align - items : flex - start ; margin - bottom : 18 px } . actions { display : flex ; flex - wrap : wrap ; gap : 8 px } . booking - groups { display : grid ; grid - template - columns : minmax ( 0 , 1.4 fr ) repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 16 px } . booking - group { min - width : 0 ; overflow : hidden ; border : 1 px solid # e8edf3 ; border - radius : 8 px ; background : # fff } . booking - group > h3 { margin : 0 ; padding : 14 px 16 px ; border - bottom : 1 px solid # eef2f6 ; color : # 1 f2937 ; font - size : 14 px } . booking - group _ _body { padding : 16 px } . party - grid { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ; gap : 12 px ; margin - top : 16 px } . subsection { display : grid ; gap : 10 px ; padding : 14 px ; border : 1 px solid var ( -- cc - border ) ; border - radius : 6 px } . subsection h3 { margin : 0 ; font - size : 14 px } . container - row { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1 fr ) 110 px auto ; gap : 8 px ; margin - bottom : 10 px } . add - container { width : 100 % ; margin - bottom : 18 px } . history - panel strong { font - size : 14 px } @ media ( max - width : 1199 px ) { . booking - groups { grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) } . booking - group : last - child { grid - column : 1 / - 1 } } @ media ( max - width : 720 px ) { . booking - groups , . party - grid { grid - template - columns : 1 fr } . booking - group : last - child { grid - column : auto } . container - row { grid - template - columns : 1 fr 1 fr } . detail - heading { flex - direction : column } . booking - page - toolbar { width : 100 % } . section - title { flex - direction : column } . actions { width : 100 % } } @ media ( max - width : 420 px ) { . surface - panel { padding : 12 px } . booking - group _ _body { padding : 14 px } . container - row { grid - template - columns : 1 fr } . actions : deep ( . el - button ) { min - height : 44 px } }
< / style >