@@ -45,6 +45,7 @@ const columns = computed<DataTableColumn[]>(() => activeTab.value === 'inventory
] )
function displayTime ( value : string ) { return value ? value . replace ( 'T' , ' ' ) . slice ( 0 , 16 ) : '—' }
function statusLabel ( row : ContainerOrder | InventoryRecord ) { if ( 'inventoryStatus' in row ) return ( { IN _STOCK : '在库' , PARTIALLY _OUTBOUND : '部分出库' , OUTBOUND : '已出库' } as const ) [ row . inventoryStatus ] ; return row . status === 'PENDING' ? ( activeTab . value === 'inbound' ? '待入库' : '待出库' ) : ( activeTab . value === 'inbound' ? '已入库' : '已出库' ) }
function canViewInventory ( row : ContainerOrder | InventoryRecord ) { return 'inventoryStatus' in row || activeTab . value === 'outbound' || row . status === 'COMPLETED' }
function queryParams ( ) { const model = appliedFilters . value ; return { page : page . value , size : pageSize . value , warehouseId : String ( model . warehouseId || '' ) || undefined , orderNo : activeTab . value === 'inventory' ? undefined : String ( model . orderNo || '' ) || undefined , containerNo : String ( model . containerNo || '' ) || undefined , cargoName : String ( model . cargoName || '' ) || undefined , status : String ( model . status || '' ) || undefined } }
async function load ( ) { loading . value = true ; loadError . value = null ; try { const result = activeTab . value === 'inbound' ? await listDomesticContainerInbounds ( queryParams ( ) ) : activeTab . value === 'outbound' ? await listDomesticContainerOutbounds ( queryParams ( ) ) : await listDomesticContainerInventories ( queryParams ( ) ) ; rows . value = result . records ; total . value = result . total ; if ( 'summary' in result ) summary . value = result . summary } catch ( cause ) { loadError . value = getApiErrorDetails ( cause , '整柜业务加载失败' ) } finally { loading . value = false } }
async function loadWarehouses ( ) { warehouseLoading . value = true ; warehouseError . value = null ; try { warehouses . value = ( await listAllPlatformWarehouses ( { warehouseType : 'DOMESTIC' , status : 'ACTIVE' } ) ) . filter ( ( item ) => item . warehouseType === 'DOMESTIC' && item . status === 'ACTIVE' ) } catch ( cause ) { warehouseError . value = getApiErrorDetails ( cause , '国内仓候选加载失败' ) } finally { warehouseLoading . value = false } }
@@ -59,20 +60,20 @@ const inboundForm = reactive({ warehouseId: '', cargoName: '', containerNo: '',
const outboundForm = reactive ( { warehouseId : '' , inventoryId : '' , containerNo : '' , cargoName : '' , availableBoxes : 0 , inventoryVersion : undefined as number | undefined , boxCount : 1 , operationTime : '' , status : 'COMPLETED' as ContainerOrderStatus , remark : '' } )
const inventoryCandidates = ref < InventoryRecord [ ] > ( [ ] ) ; const candidateLoading = ref ( false ) ; const candidateError = ref < ApiErrorDetails | null > ( null )
const currentForm = computed ( ( ) => dialogType . value === 'inbound' ? inboundForm : outboundForm )
const rules : FormRules = { warehouseId : [ { required : true , message : '请选择国内仓' , trigger : 'change' } ] , cargoName : [ { required : true , message : '请输入货物名称' , trigger : 'blur' } ] , containerNo : [ { required : true , message : '请选择或输入集装箱号' , trigger : 'change' } ] , boxCount : [ { required : true , type : 'integer' , min : 1 , message : '箱数必 须为正 整数' , trigger : 'change' } ] , volumeCbm : [ { required : true , type : 'number' , min : 0.0001 , message : '体积必须大于等于 0.0001' , trigger : 'change' } ] , operationTime : [ { required : true , message : '请选择操作时间' , trigger : 'change' } ] , status : [ { required : true , message : '请选择状态' , trigger : 'change' } ] }
const rules : FormRules = { warehouseId : [ { required : true , message : '请选择国内仓' , trigger : 'change' } ] , cargoName : [ { required : true , message : '请输入货物名称' , trigger : 'blur' } ] , containerNo : [ { required : true , message : '请选择或输入集装箱号' , trigger : 'change' } , { pattern : /^[A-Za-z0-9-]{4,32}$/ , message : '集装箱号须为 4–32 位字母、数字或连字符' , trigger : 'blur' } ] , boxCount : [ { required : true , type : 'integer' , min : 1 , max : 999999999 , message : '箱数须为 1– 999999999 的 整数' , trigger : 'change' } ] , volumeCbm : [ { required : true , type : 'number' , min : 0.0001 , max : 999999999999.9999 , message : '体积须在 0.0001– 999999999999.9999 m³ 之间 ' , trigger : 'change' } ] , operationTime : [ { required : true , message : '请选择操作时间' , trigger : 'change' } ] , status : [ { required : true , message : '请选择状态' , trigger : 'change' } ] }
function localDateTime ( ) { const offset = new Date ( ) . getTimezoneOffset ( ) * 60 _000 ; return ` ${ new Date ( Date . now ( ) - offset ) . toISOString ( ) . slice ( 0 , 16 ) } :00 ` }
function newIdempotencyKey ( type : string ) { return ` ${ type } - ${ globalThis . crypto ? . randomUUID ? . ( ) || ` ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 16 ) . slice ( 2 ) } ` } ` }
function openInbound ( ) { dialogType . value = 'inbound' ; Object . assign ( inboundForm , { warehouseId : '' , cargoName : '' , containerNo : '' , boxCount : 1 , volumeCbm : 0.01 , operationTime : localDateTime ( ) , status : 'COMPLETED' , remark : '' } ) ; idempotencyKey . value = newIdempotencyKey ( 'inbound' ) ; submitError . value = null ; dialogOpen . value = true }
function openOutbound ( ) { dialogType . value = 'outbound' ; Object . assign ( outboundForm , { warehouseId : '' , inventoryId : '' , containerNo : '' , cargoName : '' , availableBoxes : 0 , inventoryVersion : undefined , boxCount : 1 , operationTime : localDateTime ( ) , status : 'COMPLETED' , remark : '' } ) ; inventoryCandidates . value = [ ] ; candidateError . value = null ; idempotencyKey . value = newIdempotencyKey ( 'outbound' ) ; submitError . value = null ; dialogOpen . value = true }
async function loadCandidates ( ) { inventoryCandidates . value = [ ] ; Object . assign ( outboundForm , { inventoryId : '' , containerNo : '' , cargoName : '' , availableBoxes : 0 , inventoryVersion : undefined } ) ; if ( ! outboundForm . warehouseId ) return ; candidateLoading . value = true ; candidateError . value = null ; try { const result = await listDomesticContainerInventories ( { page : 1 , size : 200 , warehouseId : outboundForm . warehouseId } ) ; inventoryCandidates . value = result . records . filter ( ( item ) => item . availableBoxes > 0 ) } catch ( cause ) { candidateError . value = getApiErrorDetails ( cause , '可出库整柜库存加载失败' ) } finally { candidateLoading . value = false } }
async function selectInventory ( id : string ) { const selected = inventoryCandidates . value . find ( ( item ) => String ( item . id ) === id ) ; if ( ! selected ) return ; Object . assign ( outboundForm , { inventoryId : id , containerNo : selected . containerNo , cargoName : selected . cargoName , availableBoxes : selected . availableBoxes , inventoryVersion : selected . version } ) ; try { const detail = await getDomesticContainerInventory ( id ) ; Object . assign ( outboundForm , { availableBoxes : detail . inventory . availableBoxes , inventoryVersion : detail . inventory . version } ) } catch ( cause ) { candidateError . value = getApiErrorDetails ( cause , '库存最新版本加载失败' ) } }
async function submit ( ) { if ( ! await formRef . value ? . validate ( ) . catch ( ( ) => false ) ) return ; if ( dialogType . value === 'outbound' && outboundForm . status === 'COMPLETED' && outboundForm . boxCount > outboundForm . availableBoxes ) { submitError . value = { message : ` 库存不足,当前可用 ${ outboundForm . availableBoxes } 箱 ` , code : 'A0409' } ; return } saving . value = true ; submitError . value = null ; try { const result = dialogType . value === 'inbound' ? await createDomesticContainerInbound ( { warehouseId : inboundForm . warehouseId , cargoName : inboundForm . cargoName . trim ( ) , containerNo : inboundForm . containerNo . trim ( ) . toUpperCase ( ) , boxCount : inboundForm . boxCount , volumeCbm : inboundForm . volumeCbm , inboundTime : inboundForm . operationTime , status : inboundForm . status , remark : inboundForm . remark . trim ( ) || undefined , idempotencyKey : idempotencyKey . value } ) : await createDomesticContainerOutbound ( { warehouseId : outboundForm . warehouseId , containerNo : outboundForm . containerNo , boxCount : outboundForm . boxCount , outboundTime : outboundForm . operationTime , status : outboundForm . status , remark : outboundForm . remark . trim ( ) || undefined , idempotencyKey : idempotencyKey . value , inventoryVersion : outboundForm . status === 'COMPLETED' ? outboundForm . inventoryVersion : undefined } ) ; ElMessage . success ( result . replayed ? ` 请求已处理,单号 ${ result . orderNo } ` : ` 创建成功,单号 ${ result . orderNo } ` ) ; dialogOpen . value = false ; await load ( ) } catch ( cause ) { submitError . value = getApiErrorDetails ( cause , '整柜单据创建失败' ) ; if ( dialogType . value === 'outbound' && submitError . value . code === 'A0409' ) await loadCandidates ( ) } finally { saving . value = false } }
async function submit ( ) { if ( ! await formRef . value ? . validate ( ) . catch ( ( ) => false ) ) return ; if ( dialogType . value === 'outbound' && outboundForm . status === 'COMPLETED' && outboundForm . inventoryVersion == null ) { submitError . value = { message : '库存版本未就绪,请重新选择集装箱' , code : 'A0422' } ; return } if ( dialogType . value === 'outbound' && outboundForm . status === 'COMPLETED' && outboundForm . boxCount > outboundForm . availableBoxes ) { submitError . value = { message : ` 库存不足,当前可用 ${ outboundForm . availableBoxes } 箱 ` , code : 'A0409' } ; return } saving . value = true ; submitError . value = null ; try { const result = dialogType . value === 'inbound' ? await createDomesticContainerInbound ( { warehouseId : inboundForm . warehouseId , cargoName : inboundForm . cargoName . trim ( ) , containerNo : inboundForm . containerNo . trim ( ) . toUpperCase ( ) , boxCount : inboundForm . boxCount , volumeCbm : inboundForm . volumeCbm , inboundTime : inboundForm . operationTime , status : inboundForm . status , remark : inboundForm . remark . trim ( ) || undefined , idempotencyKey : idempotencyKey . value } ) : await createDomesticContainerOutbound ( { warehouseId : outboundForm . warehouseId , containerNo : outboundForm . containerNo , boxCount : outboundForm . boxCount , outboundTime : outboundForm . operationTime , status : outboundForm . status , remark : outboundForm . remark . trim ( ) || undefined , idempotencyKey : idempotencyKey . value , inventoryVersion : outboundForm . status === 'COMPLETED' ? outboundForm . inventoryVersion : undefined } ) ; ElMessage . success ( result . replayed ? ` 请求已处理,单号 ${ result . orderNo } ` : ` 创建成功,单号 ${ result . orderNo } ` ) ; dialogOpen . value = false ; await load ( ) } catch ( cause ) { submitError . value = getApiErrorDetails ( cause , '整柜单据创建失败' ) ; if ( dialogType . value === 'outbound' && submitError . value . code === 'A0409' ) await loadCandidates ( ) } finally { saving . value = false } }
watch ( [ page , pageSize ] , load ) ; onMounted ( async ( ) => { await Promise . all ( [ loadWarehouses ( ) , load ( ) ] ) } )
< / script >
< template > < AdminPage as = "div" class = "domestic-containers-page" >
< LoadErrorState v-if = "warehouseError" compact title="国内仓候选加载失败" :message="warehouseError.message" :loading="warehouseLoading" @retry="loadWarehouses"><template #details><ApiErrorMeta :details="warehouseError" / > < / template > < / LoadErrorState >
< ListPageLayout : aria -label = " ` $ { activeTab = = = ' inbound ' ? ' 整柜入库单 ' : activeTab = = = ' outbound ' ? ' 整柜出库单 ' : ' 整柜库存 ' } 列表 ` " > < template # toolbar > < div class = "business-query-panel" > < div class = "status-tabs" role = "tablist" aria -label = " 整柜业务类型 " > < button v-for = "(tab, index) in businessTabs" :key="tab.value" type="button" role="tab" :aria-selected="activeTab === tab.value" aria-controls="domestic-container-results" :tabindex="activeTab === tab.value ? 0 : -1" :class="['status-tab', { 'is-active': activeTab === tab.value }]" @click="switchTab(tab.value)" @keydown="handleTabKeydown($event, index)">{{ tab.label }}</button></div><QueryToolbar v-model="filters" class="business-query-toolbar" ariaLabel="整柜业务筛选" :fields="queryFields" :preference-key="`domestic-containers-${activeTab}-query`" @search="search" /></div></template><template v-if="loadError" #state><LoadErrorState title="整柜业务加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template><section v-if="!loadError" id="domestic-container-results" class="surface-panel table-panel"><div v-if="activeTab === 'inventory'" class="inventory-summary"><div><span>入库箱数</span><strong>{{ summary.inboundBoxes }}</strong></div><div><span>已出库箱数</span><strong>{{ summary.outboundBoxes }}</strong></div><div><span>当前库存</span><strong>{{ summary.availableBoxes }}</strong></div><div><span>累计入库体积</span><strong>{{ summary.inboundVolumeCbm.toFixed(2) }} m³</strong></div></div><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" stripe row-key="id" :preference-key="`domestic-containers-${activeTab}-table`" :empty-text="activeTab === 'inbound' ? '暂无整柜入库单' : activeTab === 'outbound' ? '暂无整柜出库单' : '暂无整柜库存'"><template #toolbar-actions><el-button v-if="activeTab === 'inbound' && access.can('platform:domestic-container:inbound:create')" type="primary" @click="openInbound"><Plus :size="16" />新建入库单</el-button><el-button v-if="activeTab === 'outbound' && access.can('platform:domestic-container:outbound:create')" type="primary" @click="openOutbound"><Plus :size="16" />新建出库单</el-button></template><template #actions="{ row }"><el-button size="small" type="primary" plain @click="viewInventory(row as ContainerOrder | InventoryRecord)"><Eye :size="15" />查看</el-button></template><template #status="{ row }"><StatusBadge :status="statusLabel(row as ContainerOrder | InventoryRecord)" / > < / template > < / DataTable > < / section > < / ListPageLayout >
< AppDialog v-model = "dialogOpen" class="domestic-container-create-dialog" :title="dialogType === 'inbound' ? '新建整柜入库单' : '新建整柜出库单'" width="min(720px, calc(100vw - 28px))" :confirm-loading="saving" :close-on-click-modal="false" :confirm-text="dialogType === 'inbound' ? '创建入库单' : '创建出库单'" @confirm="submit"><LoadErrorState v-if="submitError" compact title="创建失败" :message="submitError.message" :show-retry="false"><template #details><ApiErrorMeta :details="submitError" /></template></LoadErrorState><el-form ref="formRef" :model="currentForm" :rules="rules" label-position="top"><el-row :gutter="16"><template v-if="dialogType === 'inbound'"><el-col :xs="24" :sm="12"><el-form-item label="国内仓" prop="warehouseId"><el-select v-model="inboundForm.warehouseId" :loading="warehouseLoading" :disabled="Boolean(warehouseError)" placeholder="请选择启用的国内仓"><el-option v-for="item in warehouseOptions" :key="String(item.value)" :label="item.label" :value="String(item.value)" /></el-select></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="货物名称" prop="cargoName"><el-input v-model="inboundForm.cargoName" maxlength="128" placeholder="请输入货物名称" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="集装箱号" prop="containerNo"><el-input v-model="inboundForm.containerNo" maxlength="32" placeholder="如 MSCU1234567" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="箱子数量" prop="boxCount"><el-input-number v-model="inboundForm.boxCount" :min="1" :precision="0" controls-position="right" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="体积(m³)" prop="volumeCbm"><el-input-number v-model="inboundForm.volumeCbm" :min="0.0001" :precision="4" controls-position="right" /></el-form-item></el-col></template><template v-else><el-col :xs="24" :sm="12"><el-form-item label="国内仓" prop="warehouseId"><el-select v-model="outboundForm.warehouseId" :loading="warehouseLoading" :disabled="Boolean(warehouseError)" placeholder="请选择启用的国内仓" @change="loadCandidates"><el-option v-for="item in warehouseOptions" :key="String(item.value)" :label="item.label" :value="String(item.value)" /></el-select></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="绑定集装箱号" prop="containerNo"><el-select v-model="outboundForm.inventoryId" :loading="candidateLoading" :disabled="!outboundForm.warehouseId || Boolean(candidateError)" placeholder="请选择已有集装箱号" @change="selectInventory"><el-option v-for="item in inventoryCandidates" :key="String(item.id)" :label="`${item.containerNo} · 可用 ${item.availableBoxes} 箱`" :value="String(item.id)" /></el-select><input v-model="outboundForm.containerNo" class="sr-only" tabindex="-1"></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="货物名称"><el-input v-model="outboundForm.cargoName" readonly /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="出库箱数" prop="boxCount"><el-input-number v-model="outboundForm.boxCount" :min="1" :precision="0" controls-position="right" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="可用库存"><el-input :model-value="`${outboundForm.availableBoxes} 箱`" readonly /></el-form-item></el-col><el-col v-if="candidateError" :xs="24"><LoadErrorState compact title="可出库库存加载失败" :message="candidateError.message" @retry="loadCandidates"><template #details><ApiErrorMeta :details="candidateError" /></template></LoadErrorState></el-col></template><el-col :xs="24" :sm="12"><el-form-item :label="dialogType === 'inbound' ? '入库时间' : '出库时间'" prop="operationTime"><el-date-picker v-model="currentForm.operationTime" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" format="YYYY-MM-DD HH:mm" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="状态" prop="status"><el-select v-model="currentForm.status"><el-option :label="dialogType === 'inbound' ? '已入库' : '已出库'" value="COMPLETED" /><el-option :label="dialogType === 'inbound' ? '待入库' : '待出库'" value="PENDING" /></el-select></el-form-item></el-col><el-col :xs="24"><el-form-item label="备注"><el-input v-model="currentForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入备注" / > < / el-form-item > < / el-col > < / el-row > < / el-form > < / AppDialog > < / AdminPage > < / template >
< ListPageLayout : aria -label = " ` $ { activeTab = = = ' inbound ' ? ' 整柜入库单 ' : activeTab = = = ' outbound ' ? ' 整柜出库单 ' : ' 整柜库存 ' } 列表 ` " > < template # toolbar > < div class = "business-query-panel" > < div class = "status-tabs" role = "tablist" aria -label = " 整柜业务类型 " > < button v-for = "(tab, index) in businessTabs" :key="tab.value" type="button" role="tab" :aria-selected="activeTab === tab.value" aria-controls="domestic-container-results" :tabindex="activeTab === tab.value ? 0 : -1" :class="['status-tab', { 'is-active': activeTab === tab.value }]" @click="switchTab(tab.value)" @keydown="handleTabKeydown($event, index)">{{ tab.label }}</button></div><QueryToolbar v-model="filters" class="business-query-toolbar" ariaLabel="整柜业务筛选" :fields="queryFields" :preference-key="`domestic-containers-${activeTab}-query`" @search="search" /></div></template><template v-if="loadError" #state><LoadErrorState title="整柜业务加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template><section v-if="!loadError" id="domestic-container-results" class="surface-panel table-panel"><div v-if="activeTab === 'inventory'" class="inventory-summary"><div><span>入库箱数</span><strong>{{ summary.inboundBoxes }}</strong></div><div><span>已出库箱数</span><strong>{{ summary.outboundBoxes }}</strong></div><div><span>当前库存</span><strong>{{ summary.availableBoxes }}</strong></div><div><span>累计入库体积</span><strong>{{ summary.inboundVolumeCbm.toFixed(2) }} m³</strong></div></div><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" stripe row-key="id" :preference-key="`domestic-containers-${activeTab}-table`" :empty-text="activeTab === 'inbound' ? '暂无整柜入库单' : activeTab === 'outbound' ? '暂无整柜出库单' : '暂无整柜库存'"><template #toolbar-actions><el-button v-if="activeTab === 'inbound' && access.can('platform:domestic-container:inbound:create')" type="primary" @click="openInbound"><Plus :size="16" />新建入库单</el-button><el-button v-if="activeTab === 'outbound' && access.can('platform:domestic-container:outbound:create')" type="primary" @click="openOutbound"><Plus :size="16" />新建出库单</el-button></template><template #actions="{ row }"><el-button v-if="canViewInventory(row as ContainerOrder | InventoryRecord)" size="small" type="primary" plain @click="viewInventory(row as ContainerOrder | InventoryRecord)"><Eye :size="15" />查看</el-button><span v-else>—</span>< /template><template #status="{ row }"><StatusBadge :status="statusLabel(row as ContainerOrder | InventoryRecord)" / > < / template > < / DataTable > < / section > < / ListPageLayout >
< AppDialog v-model = "dialogOpen" class="domestic-container-create-dialog" :title="dialogType === 'inbound' ? '新建整柜入库单' : '新建整柜出库单'" width="min(720px, calc(100vw - 28px))" :confirm-loading="saving" :close-on-click-modal="false" :confirm-text="dialogType === 'inbound' ? '创建入库单' : '创建出库单'" @confirm="submit"><LoadErrorState v-if="submitError" compact title="创建失败" :message="submitError.message" :show-retry="false"><template #details><ApiErrorMeta :details="submitError" /></template></LoadErrorState><el-form ref="formRef" :model="currentForm" :rules="rules" label-position="top"><el-row :gutter="16"><template v-if="dialogType === 'inbound'"><el-col :xs="24" :sm="12"><el-form-item label="国内仓" prop="warehouseId"><el-select v-model="inboundForm.warehouseId" :loading="warehouseLoading" :disabled="Boolean(warehouseError)" placeholder="请选择启用的国内仓"><el-option v-for="item in warehouseOptions" :key="String(item.value)" :label="item.label" :value="String(item.value)" /></el-select></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="货物名称" prop="cargoName"><el-input v-model="inboundForm.cargoName" maxlength="128" placeholder="请输入货物名称" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="集装箱号" prop="containerNo"><el-input v-model="inboundForm.containerNo" maxlength="32" placeholder="如 MSCU1234567" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="箱子数量" prop="boxCount"><el-input-number v-model="inboundForm.boxCount" :min="1" :max="999999999" :precision="0" controls-position="right" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="体积(m³)" prop="volumeCbm"><el-input-number v-model="inboundForm.volumeCbm" :min="0.0001" :max="999999999999.9999" :precision="4" controls-position="right" /></el-form-item></el-col></template><template v-else><el-col :xs="24" :sm="12"><el-form-item label="国内仓" prop="warehouseId"><el-select v-model="outboundForm.warehouseId" :loading="warehouseLoading" :disabled="Boolean(warehouseError)" placeholder="请选择启用的国内仓" @change="loadCandidates"><el-option v-for="item in warehouseOptions" :key="String(item.value)" :label="item.label" :value="String(item.value)" /></el-select></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="绑定集装箱号" prop="containerNo"><el-select v-model="outboundForm.inventoryId" :loading="candidateLoading" :disabled="!outboundForm.warehouseId || Boolean(candidateError)" placeholder="请选择已有集装箱号" @change="selectInventory"><el-option v-for="item in inventoryCandidates" :key="String(item.id)" :label="`${item.containerNo} · 可用 ${item.availableBoxes} 箱`" :value="String(item.id)" /></el-select><input v-model="outboundForm.containerNo" class="sr-only" tabindex="-1"></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="货物名称"><el-input v-model="outboundForm.cargoName" readonly /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="出库箱数" prop="boxCount"><el-input-number v-model="outboundForm.boxCount" :min="1" :max="999999999" :precision="0" controls-position="right" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="可用库存"><el-input :model-value="`${outboundForm.availableBoxes} 箱`" readonly /></el-form-item></el-col><el-col v-if="candidateError" :xs="24"><LoadErrorState compact title="可出库库存加载失败" :message="candidateError.message" @retry="loadCandidates"><template #details><ApiErrorMeta :details="candidateError" /></template></LoadErrorState></el-col></template><el-col :xs="24" :sm="12"><el-form-item :label="dialogType === 'inbound' ? '入库时间' : '出库时间'" prop="operationTime"><el-date-picker v-model="currentForm.operationTime" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" format="YYYY-MM-DD HH:mm" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="状态" prop="status"><el-select v-model="currentForm.status"><el-option :label="dialogType === 'inbound' ? '已入库' : '已出库'" value="COMPLETED" /><el-option :label="dialogType === 'inbound' ? '待入库' : '待出库'" value="PENDING" /></el-select></el-form-item></el-col><el-col :xs="24"><el-form-item label="备注"><el-input v-model="currentForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入备注" / > < / el-form-item > < / el-col > < / el-row > < / el-form > < / AppDialog > < / AdminPage > < / template >
< style scoped > . domestic - containers - page { min - width : 0 } . business - query - panel { overflow : hidden ; border : 1 px solid var ( -- cc - border ) ; border - radius : 8 px ; background : # fff } . status - tabs { display : flex ; max - width : 100 % ; overflow - x : auto ; margin - bottom : 0 ; border - bottom : 1 px solid # e5e7eb ; background : # f9fafb ; scrollbar - width : thin } . status - tab { position : relative ; flex : 0 0 auto ; min - height : 44 px ; padding : 0 18 px ; border : 0 ; background : transparent ; color : var ( -- cc - muted ) ; font : inherit ; font - size : 14 px ; cursor : pointer } . status - tab : : after { position : absolute ; right : 12 px ; bottom : 0 ; left : 12 px ; height : 3 px ; background : transparent ; content : '' } . status - tab : hover { color : var ( -- cc - primary ) ; background : # f6f9fd } . status - tab : focus - visible { outline : 2 px solid var ( -- cc - primary ) ; outline - offset : - 2 px } . status - tab . is - active { color : var ( -- cc - primary ) ; font - weight : 600 } . status - tab . is - active : : after { background : var ( -- cc - primary ) } . business - query - toolbar { border : 0 } . inventory - summary { display : flex ; align - items : center ; gap : 0 ; margin : 0 16 px 12 px ; border : 1 px solid var ( -- cc - border ) ; background : # f8fafc } . inventory - summary > div { display : flex ; align - items : baseline ; gap : 8 px ; min - width : 0 ; padding : 9 px 16 px ; border - right : 1 px solid var ( -- cc - border ) } . inventory - summary > div : last - child { border - right : 0 } . inventory - summary span { color : var ( -- cc - muted ) ; font - size : 12 px } . inventory - summary strong { font - size : 15 px } . domestic - container - create - dialog : deep ( . el - select ) , . domestic - container - create - dialog : deep ( . el - date - editor ) , . domestic - container - create - dialog : deep ( . el - input - number ) { width : 100 % } . sr - only { position : absolute ; width : 1 px ; height : 1 px ; overflow : hidden ; clip : rect ( 0 , 0 , 0 , 0 ) } @ media ( max - width : 720 px ) { . status - tab { min - height : 46 px ; padding : 0 14 px } . inventory - summary { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) } . inventory - summary > div : nth - child ( 2 ) { border - right : 0 } . inventory - summary > div : nth - child ( - n + 2 ) { border - bottom : 1 px solid var ( -- cc - border ) } } @ media ( max - width : 360 px ) { . status - tab { padding : 0 10 px ; font - size : 13 px } . inventory - summary { grid - template - columns : 1 fr } . inventory - summary > div { border - right : 0 ; border - bottom : 1 px solid var ( -- cc - border ) } . inventory - summary > div : last - child { border - bottom : 0 } } < / style >