fix(appointments): unify calendar layout and cabinet numbers

This commit is contained in:
2026-10-12 11:45:00 +08:00
parent 20b4d1425b
commit 92bdd32f86
25 changed files with 178 additions and 65 deletions
@@ -31,6 +31,7 @@ import { listArrivalCalendar, listPickupCalendar, type ArrivalCalendarEntry, typ
import { listTripCalendar, projectTripCalendar, tripIssues, type TripCalendarEntry } from './tripCalendar'
const cancellationTarget = ref<FbaAppointmentDto | null>(null)
const unscheduledOpen = ref(false)
const cancellationsOpen = ref(false), cancellationsRevision = ref(0), cancellationHistoryOpen = ref(false), forceReadOnly = ref(false)
const appointmentReadOnly = computed(() => forceReadOnly.value || editing.value?.appointmentStatus === 'CANCELLED' || !access.can(permission.inboundOrderAppointment))
const access = useAccessPreviewStore()
@@ -130,22 +131,22 @@ const columns: DataTableColumn[] = [
const events = computed<CalendarEvent[]>(() => [...arrivals.value.map(arrival => ({
key: `arrival-${arrival.containerBookingId}`, type: 'arrival' as const,
date: arrival.arrivalAt.slice(0, 10), time: arrival.arrivalAt.slice(11, 16),
title: `${arrival.voyageNo || arrival.bookingNo} ${arrival.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港'}`,
title: `${arrival.voyageNo || arrival.cabinetNo} ${arrival.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港'}`,
fields: [
{ label: '船名 / 航次', value: [arrival.vesselName, arrival.voyageNo].filter(Boolean).join(' / ') },
{ label: '目的港', value: arrival.destinationPortName },
{ label: '已装货柜号', value: arrival.containerNo },
{ label: '柜次号', value: arrival.bookingNo },
{ label: '柜次号', value: arrival.cabinetNo },
{ label: '货物去向', value: arrival.destinationWarehouseCode },
],
})), ...pickups.value.map(pickup => ({
key: `pickup-${pickup.containerBookingId}`, type: 'pickup' as const,
date: pickup.pickupAppointmentAt.slice(0, 10), time: pickup.pickupAppointmentAt.slice(11, 16),
title: `${pickup.containerNo || pickup.bookingNo || '未关联柜号'} 预约提柜`,
title: `${pickup.containerNo || pickup.cabinetNo || '未关联柜号'} 预约提柜`,
sourcePath: `/container-loading/overseas/${encodeURIComponent(pickup.containerBookingId)}`,
subtitle: '美国目的仓相关提柜安排',
fields: [
{ label: '柜次号', value: pickup.bookingNo }, { label: '目的港', value: pickup.destinationPortName },
{ label: '柜次号', value: pickup.cabinetNo }, { label: '目的港', value: pickup.destinationPortName },
{ label: '目的仓', value: pickup.destinationWarehouseCode }, { label: '车队', value: pickup.fleetSupplierName },
{ label: '实际提柜', value: pickup.actualPickupAt || '尚未记录' },
{ label: '时间口径', value: '原提柜业务时间,港口时区待核实' },
@@ -416,13 +417,23 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
<div v-if="calendarVisited" v-show="workView === 'calendar'">
<LoadErrorState v-if="loadError" :title="errorTitle" :message="loadError.message" :loading="loading" @retry="load()"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
<LoadErrorState v-for="error in sourceErrors" :key="error.title" :title="error.title" :message="error.details.message" :loading="loading" @retry="load()"><template #details><ApiErrorMeta :details="error.details" /></template></LoadErrorState>
<LoadErrorState v-if="unscheduledError" title="未定时安排加载失败" :message="unscheduledError.message" @retry="loadUnscheduled" />
<details v-if="unscheduledTotal" class="appointment-extra"><summary>未定时安排({{ unscheduledTotal }})</summary><DataTable v-model:page="unscheduledPage" v-model:page-size="unscheduledSize" :data="unscheduled" :columns="unscheduledColumns" :total="unscheduledTotal" row-key="appointmentNo" @update:page="loadUnscheduled" @update:page-size="unscheduledPage = 1; loadUnscheduled()"><template #actions="{ row }"><el-button @click="openEdit({key: row.appointmentNo})">查看 / 继续</el-button></template></DataTable></details>
<details class="appointment-extra" @toggle="cancellationsOpen = ($event.target as HTMLDetailsElement).open"><summary>取消记录</summary><AppointmentCancellationRecords v-if="cancellationsOpen" :key="cancellationsRevision" @view="openEdit({key: $event}, true)" /></details>
<AppointmentCalendarPanel :notices="[pickupNotice, showTripLayer ? tripNotice : ''].filter(Boolean)" :events="events" :loading="loading" :incomplete="sourceErrors.length > 0 || !!pickupNotice || (showTripLayer && !!tripNotice)" :can-create="access.can(permission.inboundOrderAppointment)" @layers="showTripLayer = $event.trips" @range="load" @create="openCreate" @view="openEdit($event, true)" @edit="openEdit" @delete="remove" @open="openSource">
<template #toolbar-actions>
<el-button aria-haspopup="dialog" data-unscheduled @click="unscheduledOpen = true">未定时安排({{ unscheduledError ? '加载失败' : unscheduledTotal }})</el-button>
<el-button aria-haspopup="dialog" data-cancellations @click="cancellationsOpen = true">取消记录</el-button>
</template>
<template #notice-actions><el-button v-if="showTripLayer && unresolvedTrips.length" link type="primary" @click="unresolvedDialogOpen = true">查看待核实车次({{ unresolvedTrips.length }})</el-button></template>
</AppointmentCalendarPanel>
</div>
<AppDialog v-if="unscheduledOpen" v-model="unscheduledOpen" title="未定时安排" width="min(1000px, calc(100vw - 48px))" :show-confirm="false" cancel-text="关闭" class="admin-density-dialog">
<LoadErrorState v-if="unscheduledError" title="未定时安排加载失败" :message="unscheduledError.message" @retry="loadUnscheduled" />
<DataTable v-model:page="unscheduledPage" v-model:page-size="unscheduledSize" :data="unscheduled" :columns="unscheduledColumns" :total="unscheduledTotal" :results-visible="!unscheduledError" row-key="appointmentNo" empty-text="暂无未定时安排" @update:page="loadUnscheduled" @update:page-size="unscheduledPage = 1; loadUnscheduled()">
<template #actions="{ row }"><el-button @click="openEdit({key: row.appointmentNo})">查看 / 继续</el-button></template>
</DataTable>
</AppDialog>
<AppDialog v-if="cancellationsOpen" v-model="cancellationsOpen" title="取消记录" width="min(1160px, calc(100vw - 48px))" :show-confirm="false" cancel-text="关闭" class="admin-density-dialog">
<AppointmentCancellationRecords :key="cancellationsRevision" @view="openEdit({key: $event}, true)" />
</AppDialog>
<AppDialog v-if="unresolvedDialogOpen" v-model="unresolvedDialogOpen" :title="`待核实车次(${unresolvedTrips.length})`" width="min(1000px, calc(100vw - 48px))" :show-confirm="false" class="admin-density-dialog">
<p>这些车次缺少地点或时区,暂未计入日历。原计划时间保留 UTC。</p>
<DataTable v-model:page="unresolvedPage" v-model:page-size="unresolvedSize" :data="unresolvedRows" :columns="unresolvedColumns" :total="unresolvedTrips.length" row-key="tripNo" @update:page-size="unresolvedPage = 1">
@@ -470,7 +481,5 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
.calendar-form { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--admin-space-6); }
.calendar-form :deep(.el-select), .calendar-form :deep(.el-date-editor) { width: 100%; }
.appointment-cargo { padding: 12px 0; line-height: 1.7; overflow-wrap: anywhere; font-size: 13px; }
.appointment-extra { margin-bottom: var(--admin-space-5); }
.appointment-extra summary { cursor: pointer; margin-bottom: var(--admin-space-3); font-size: var(--admin-font-body); }
.appointment-hint { color: var(--el-text-color-secondary); font-size: var(--admin-font-body); margin: var(--admin-space-5) 0; }
</style>