This commit is contained in:
2026-09-15 16:11:20 +08:00
parent 7d5bee80b8
commit c78ecbef31
5 changed files with 61 additions and 6 deletions
@@ -79,6 +79,7 @@ const selectedTruckingSupplierId = ref('')
const unresolvedTruckingSupplierName = ref<string | null>(null)
const drayages = ref<Record<string, TerminalDrayageView>>({})
const arrivalTimes = ref<Record<string, ContainerArrivalTime>>({})
const arrivalInputs = ref<Record<string, string>>({})
const arrivalSavingIds = ref<string[]>([])
const drayageSavingIds = ref<string[]>([])
const drayageActionOpen = ref(false)
@@ -189,6 +190,7 @@ const canCreate = computed(() => access.can('platform:firstleg:container:create'
const canOverseasDocumentEdit = computed(() => access.can('platform:firstleg:terminal-drayage:document:edit'))
const canFleetEdit = computed(() => access.can('platform:firstleg:terminal-drayage:fleet:edit'))
const canPickupAppointmentEdit = computed(() => access.can('platform:firstleg:terminal-drayage:pickup-appointment:edit'))
const canExpectedArrivalEdit = computed(() => access.can('platform:overseas-warehouse:last-mile-order:edit'))
const canEmptyReturnConfirm = computed(() => access.can('platform:firstleg:terminal-drayage:empty-return:confirm'))
const canSmartLoading = computed(() => access.can('platform:firstleg:smart-loading:list'))
const canViewPreload = computed(() => access.can('platform:firstleg:preload:list'))
@@ -579,11 +581,15 @@ async function saveExpectedArrival(row: ContainerBookingResource, value: string
expectedVersion: current.version,
})
arrivalTimes.value = { ...arrivalTimes.value, [row.id]: updated }
ElMessage.success('预计到仓时间已同步到整柜入库计划')
arrivalInputs.value = { ...arrivalInputs.value, [row.id]: updated.estimatedWarehouseArrivalAt || '' }
ElMessage.success('预计到仓时间已同步整柜,后续入库计划将自动带出')
} catch (error) {
ElMessage.error(getApiErrorDetails(error, '预计到仓时间保存失败').message)
const latest = await listContainerArrivalTimes([row.id]).catch(() => [])
if (latest[0]) arrivalTimes.value = { ...arrivalTimes.value, [row.id]: latest[0] }
if (latest[0]) {
arrivalTimes.value = { ...arrivalTimes.value, [row.id]: latest[0] }
arrivalInputs.value = { ...arrivalInputs.value, [row.id]: latest[0].estimatedWarehouseArrivalAt || '' }
}
} finally { arrivalSavingIds.value = arrivalSavingIds.value.filter((id) => id !== row.id) }
}
async function openDrayageHistory(row: ContainerBookingResource) {
@@ -612,6 +618,7 @@ async function loadRows() {
const arrivalValues = isOverseas.value ? await listContainerArrivalTimes(rows.value.map((row) => row.id)) : []
if (sequence !== listSequence) return
arrivalTimes.value = Object.fromEntries(arrivalValues.map((item) => [item.sourceContainerBookingId, item]))
arrivalInputs.value = Object.fromEntries(arrivalValues.map((item) => [item.sourceContainerBookingId, item.estimatedWarehouseArrivalAt || '']))
} catch (error) {
if (sequence !== listSequence) return
rows.value = []; total.value = 0; loadError.value = getApiErrorDetails(error, '货柜列表加载失败,请稍后重试')
@@ -720,7 +727,7 @@ onMounted(async () => { await Promise.all([loadCustomers(), loadRows()]) })
<template #oceanBooking="{ row }"><span v-if="row.oceanBookingId"><strong>{{ row.billOfLadingNo || '已关联订舱' }}</strong><small>{{ row.containerNo ? `集装箱号:${row.containerNo}` : '集装箱号:待补录' }}</small></span><span v-else>未关联</span></template>
<template #containerNo="{ row }">{{ row.containerNo || '待补录' }}</template>
<template #terminalTruckingCompany="{ row }"><el-select class="fleet-select" :model-value="drayageFor(row.id)?.fleetSupplierId || ''" filterable remote reserve-keyword remote-show-suffix placeholder="输入车队名称或联系人搜索" :remote-method="searchTerminalFleets" :loading="terminalFleetLoading || drayageSavingIds.includes(row.id)" :disabled="!canDrayage(row as ContainerBookingResource, 'SELECT_FLEET') || drayageSavingIds.includes(row.id)" no-data-text="未找到合作中的码头车队" @visible-change="handleTerminalFleetVisible" @update:model-value="selectRowFleet(row, $event)"><el-option v-if="drayageFor(row.id)?.fleetSupplierName && !terminalFleetOptions.some((item) => item.id === drayageFor(row.id)?.fleetSupplierId)" :label="drayageFor(row.id)?.fleetSupplierName || '原码头车队'" :value="drayageFor(row.id)?.fleetSupplierId || ''" disabled /><el-option v-for="supplier in terminalFleetOptions" :key="supplier.id" :label="supplier.name" :value="supplier.id" /></el-select></template>
<template #pickupAppointmentAt="{ row }"><el-date-picker class="arrival-time-picker" :model-value="arrivalTimes[row.id]?.estimatedWarehouseArrivalAt || ''" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" format="YYYY-MM-DD HH:mm" placeholder="选择预计到仓" :disabled="!canPickupAppointmentEdit || !arrivalTimes[row.id] || arrivalTimes[row.id].version < 0 || arrivalSavingIds.includes(row.id)" @change="saveExpectedArrival(row as ContainerBookingResource, $event as string | null)" /></template>
<template #pickupAppointmentAt="{ row }"><el-date-picker v-model="arrivalInputs[row.id]" class="arrival-time-picker" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" format="YYYY-MM-DD HH:mm" placeholder="选择预计到仓" :disabled="!canExpectedArrivalEdit || !arrivalTimes[row.id] || arrivalTimes[row.id].version < 0 || arrivalSavingIds.includes(row.id)" @change="saveExpectedArrival(row as ContainerBookingResource, $event as string | null)" /></template>
<template #actualPickupAt="{ row }">{{ formatTime(arrivalTimes[row.id]?.actualWarehouseArrivalAt) }}</template>
<template #estimatedEmptyReturnAt="{ row }">{{ formatTime(drayageFor(row.id)?.estimatedEmptyReturnAt) }}</template>
<template #actualEmptyReturnAt="{ row }">{{ formatTime(drayageFor(row.id)?.actualEmptyReturnAt) }}</template>
@@ -788,7 +795,7 @@ onMounted(async () => { await Promise.all([loadCustomers(), loadRows()]) })
</template>
<style scoped>
.container-page { display: grid; gap: var(--admin-space-5); min-width: 0; }.container-page small { display: block; margin-top: var(--admin-space-1); color: var(--cc-muted); }.container-query-panel { overflow: hidden; border: 1px solid var(--cc-border); border-radius: var(--admin-radius); background: var(--admin-surface); }.container-scope-tabs { display: flex; max-width: 100%; overflow-x: auto; margin-bottom: 0; border-bottom: 1px solid #e5e7eb; scrollbar-width: thin; }.container-query-toolbar { border: 0; }.row-actions { flex-wrap: nowrap; white-space: nowrap; }.row-actions :deep(.el-button + .el-button) { margin-left: 0; }.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-5); }.form-grid :deep(.el-select),.form-grid :deep(.el-date-editor),.form-grid :deep(.el-input-number),.simulation-form :deep(.el-select),.simulation-form :deep(.el-input-number),.fleet-select { width: 100%; }.details :deep(.el-descriptions__label) { width: 120px; }.drayage-load-alert { margin-bottom: var(--admin-space-5); }.drayage-summary { margin-bottom: var(--admin-space-5); }
.container-page { display: grid; gap: var(--admin-space-5); min-width: 0; }.container-page small { display: block; margin-top: var(--admin-space-1); color: var(--cc-muted); }.container-query-panel { overflow: hidden; border: 1px solid var(--cc-border); border-radius: var(--admin-radius); background: var(--admin-surface); }.container-scope-tabs { display: flex; max-width: 100%; overflow-x: auto; margin-bottom: 0; border-bottom: 1px solid #e5e7eb; scrollbar-width: thin; }.container-query-toolbar { border: 0; }.row-actions { flex-wrap: nowrap; white-space: nowrap; }.row-actions :deep(.el-button + .el-button) { margin-left: 0; }.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-5); }.form-grid :deep(.el-select),.form-grid :deep(.el-date-editor),.form-grid :deep(.el-input-number),.simulation-form :deep(.el-select),.simulation-form :deep(.el-input-number),.fleet-select,.arrival-time-picker { width: 100%; }.details :deep(.el-descriptions__label) { width: 120px; }.drayage-load-alert { margin-bottom: var(--admin-space-5); }.drayage-summary { margin-bottom: var(--admin-space-5); }
@media(max-width:720px){.details :deep(.el-descriptions__body table),.details :deep(tbody),.details :deep(tr),.details :deep(td){display:block;width:100%!important}.row-actions { min-width: 180px; }}
@media(max-width:720px){.form-grid{grid-template-columns:1fr}}
</style>