船舶到港预约送仓预约提柜用车计划
@@ -149,6 +152,8 @@ defineExpose({ selectedDate, mode })
.calendar-main, .calendar-detail { background: #fff; border: 1px solid #e4e9f2; border-radius: 10px; overflow: hidden; }
.calendar-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--admin-space-3); padding: var(--admin-space-5); border-bottom: 1px solid #edf0f6; }
.calendar-label, .calendar-controls { display: flex; align-items: center; gap: 10px; }
+.calendar-record-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3); }
+.calendar-record-actions :deep(.el-button + .el-button) { margin-left: 0; }
.calendar-label > svg { color: #617ce9; }
.calendar-label > strong { font-size: var(--admin-font-section); }
.calendar-options { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--admin-space-3); padding: var(--admin-space-3) var(--admin-space-5); border-bottom: 1px solid var(--admin-table-border); }
diff --git a/src/modules/appointment-calendar/AppointmentCalendarView.test.ts b/src/modules/appointment-calendar/AppointmentCalendarView.test.ts
index 8c19a985..201a7c6d 100644
--- a/src/modules/appointment-calendar/AppointmentCalendarView.test.ts
+++ b/src/modules/appointment-calendar/AppointmentCalendarView.test.ts
@@ -40,7 +40,7 @@ function render(openCalendar = true) {
const unregister = register(() => !routing.containerPending)
onBeforeUnmount(unregister)
}, template: '
整柜跟进工作区
' },
- AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', mounted() { this.$emit('layers', { arrivals: true, trips: true }) }, props: ['events', 'incomplete', 'notices'], template: '
' },
+ AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', mounted() { this.$emit('layers', { arrivals: true, trips: true }) }, props: ['events', 'incomplete', 'notices'], template: '
' },
AppDialog: { name: 'AppDialog', props: ['modelValue', 'showConfirm', 'beforeClose'], template: '
' },
ElForm: { template: '
' },
ElFormItem: { template: '
' },
@@ -70,6 +70,19 @@ describe('appointment calendar data integration', () => {
vi.mocked(createFbaAppointment).mockResolvedValue({ version: 0, id: '1', appointmentNo: 'APT-AB', plannedDate: '2026-10-09', appointmentAt: null, originWarehouseId: '100', appointmentStatus: 'PENDING', selectedCount: 2, fbaWarehouseCode: 'ONT8', items: [] })
})
+ it('displays the existing D cabinet number in both arrival and pickup events', async () => {
+ vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', cabinetNo: 'D0007', arrivalAt: '2026-10-09T08:00:00', arrivalType: 'ESTIMATED' }])
+ vi.mocked(listPickupCalendar).mockResolvedValueOnce({ records: [{ containerBookingId: '1', cabinetNo: 'D0007', containerNo: null, destinationPortName: null, destinationWarehouseCode: null, fleetSupplierName: null, pickupAppointmentAt: '2026-10-09T09:00:00', actualPickupAt: null }], truncated: false, unclassifiedCount: 0, scopeBasis: 'US_DESTINATION_WAREHOUSE', timeBasis: 'SOURCE_LOCAL_UNSPECIFIED' })
+ const wrapper = render(); await flushPromises()
+ const events = wrapper.getComponent({ name: 'AppointmentCalendarPanel' }).props('events')
+ expect(events).toHaveLength(2)
+ for (const event of events) {
+ expect(event.fields).toContainEqual({ label: '柜次号', value: 'D0007' })
+ expect(event.title).toContain('D0007')
+ }
+ wrapper.unmount()
+ })
+
it('opens a dated calendar appointment read-only for list-only users', async () => {
routing.canWrite=false
const record={version:0,id:'1',appointmentNo:'AP-DATED',plannedDate:'2026-10-15',originWarehouseId:'100',appointmentStatus:'PENDING',selectedCount:1,fbaWarehouseCode:'ABE3',items:[]}
@@ -90,6 +103,7 @@ describe('appointment calendar data integration', () => {
vi.mocked(listUnscheduledAppointments).mockResolvedValue({records: [record], total: 1})
vi.mocked(getFbaAppointment).mockResolvedValue(record)
const wrapper=render(); await flushPromises()
+ await wrapper.get('[data-unscheduled]').trigger('click')
await wrapper.findAll('button').find(button=>button.text()==='查看 / 继续')!.trigger('click'); await flushPromises()
expect(wrapper.find('option[value="CANCELLED"]').exists()).toBe(false)
await wrapper.get('[data-cancel-editing]').trigger('click'); await flushPromises()
@@ -97,13 +111,13 @@ describe('appointment calendar data integration', () => {
expect(wrapper.find('[data-save]').exists()).toBe(false)
expect(updateFbaAppointment).not.toHaveBeenCalled(); wrapper.unmount()
})
- it('loads cancellation records only after expansion and opens cancelled appointments read-only', async () => {
+ it('loads cancellation records only after opening the dialog and opens cancelled appointments read-only', async () => {
const record = { version: 1, id: '1', appointmentNo: 'AP-CANCEL', originWarehouseId: '100', appointmentStatus: 'CANCELLED', selectedCount: 1, fbaWarehouseCode: 'ABE3', items: [] }
vi.mocked(getFbaAppointment).mockResolvedValue(record)
const wrapper=render();await flushPromises()
expect(wrapper.findComponent({name:'AppointmentCancellationRecords'}).exists()).toBe(false)
- const records=wrapper.findAll('details').find(d=>d.find('summary').text()==='取消记录')!
- ;(records.element as HTMLDetailsElement).open=true;await records.trigger('toggle')
+ const records=wrapper.get('[data-cancellations]')
+ await records.trigger('click')
await wrapper.get('[data-history-view]').trigger('click');await flushPromises()
expect(getFbaAppointment).toHaveBeenCalledWith('AP-CANCEL')
expect(wrapper.find('[data-save]').exists()).toBe(false)
@@ -119,8 +133,8 @@ describe('appointment calendar data integration', () => {
it('refreshes an expanded cancellation list after a successful cancellation', async () => {
vi.mocked(listFbaAppointmentCalendar).mockResolvedValue([{version:0,id:'1',appointmentNo:'AP-CANCEL',originWarehouseId:'100',appointmentStatus:'PENDING',selectedCount:1,fbaWarehouseCode:'ABE3',items:[],plannedDate:'2026-10-15'}])
const wrapper=render();await flushPromises()
- const records=wrapper.findAll('details').find(d=>d.find('summary').text()==='取消记录')!
- ;(records.element as HTMLDetailsElement).open=true;await records.trigger('toggle')
+ const records=wrapper.get('[data-cancellations]')
+ await records.trigger('click')
const previous=wrapper.getComponent({name:'AppointmentCancellationRecords'}).vm
wrapper.getComponent({name:'AppointmentCalendarPanel'}).vm.$emit('delete',{key:'AP-CANCEL'});await flushPromises()
wrapper.getComponent({name:'AppointmentCancellationDialog'}).vm.$emit('cancelled');await flushPromises()
@@ -150,6 +164,7 @@ describe('appointment calendar data integration', () => {
vi.mocked(listUnscheduledAppointments).mockResolvedValue({ records: [record], total: 1 })
vi.mocked(getFbaAppointment).mockResolvedValue(record)
const wrapper = render(); await flushPromises()
+ await wrapper.get('[data-unscheduled]').trigger('click')
await wrapper.findAll('button').find(b => b.text() === '查看 / 继续')!.trigger('click'); await flushPromises()
expect(wrapper.find('[data-save]').exists()).toBe(false)
const dialog = wrapper.findAllComponents({ name: 'AppDialog' }).at(-1)!
@@ -179,7 +194,7 @@ describe('appointment calendar data integration', () => {
expect(wrapper.text()).not.toContain('缩小日期范围')
})
it('shows the original pickup time once without claiming a port time zone and retains partial-source warnings', async () => {
- vi.mocked(listPickupCalendar).mockResolvedValueOnce({records: [{containerBookingId: '123',bookingNo: 'CB-TEST',containerNo: 'TEST-CONT',destinationPortName: 'Port',destinationWarehouseCode: 'US-W',fleetSupplierName: 'Fleet',pickupAppointmentAt: '2026-10-09T11:00:00',actualPickupAt: null}],truncated: false,unclassifiedCount: 1,scopeBasis: 'US_DESTINATION_WAREHOUSE',timeBasis: 'SOURCE_LOCAL_UNSPECIFIED'})
+ vi.mocked(listPickupCalendar).mockResolvedValueOnce({records: [{containerBookingId: '123',cabinetNo: 'D0001',containerNo: 'TEST-CONT',destinationPortName: 'Port',destinationWarehouseCode: 'US-W',fleetSupplierName: 'Fleet',pickupAppointmentAt: '2026-10-09T11:00:00',actualPickupAt: null}],truncated: false,unclassifiedCount: 1,scopeBasis: 'US_DESTINATION_WAREHOUSE',timeBasis: 'SOURCE_LOCAL_UNSPECIFIED'})
const wrapper=render(); await flushPromises()
const panel=wrapper.findComponent({name:'AppointmentCalendarPanel'})
expect(panel.props('events')).toEqual([expect.objectContaining({type:'pickup',date:'2026-10-09',time:'11:00',sourcePath:'/container-loading/overseas/123'})])
@@ -196,7 +211,7 @@ describe('appointment calendar data integration', () => {
})
it('keeps successful arrival data visible when the pickup source fails', async () => {
vi.mocked(listPickupCalendar).mockRejectedValueOnce(new Error('pickup unavailable'))
- vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{containerBookingId:'1',bookingNo:'CB-1',arrivalAt:'2026-10-09T09:00:00',arrivalType:'ESTIMATED'}])
+ vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{containerBookingId:'1',cabinetNo:'D0001',arrivalAt:'2026-10-09T09:00:00',arrivalType:'ESTIMATED'}])
const wrapper=render(); await flushPromises()
const panel=wrapper.findComponent({name:'AppointmentCalendarPanel'})
expect(panel.props('events')).toEqual([expect.objectContaining({type:'arrival',key:'arrival-1'})])
@@ -248,7 +263,7 @@ describe('appointment calendar data integration', () => {
expect(listPlatformWarehouses).toHaveBeenCalledWith(expect.objectContaining({ warehouseType: 'FBA', status: 'ACTIVE', size: 50 }))
expect(wrapper.find('input[placeholder="例如 ONT8"]').exists()).toBe(false)
expect(wrapper.text()).not.toContain('查询可预约货物')
- expect(wrapper.find('details').attributes('open')).toBeUndefined()
+ expect(wrapper.findComponent({name:'AppointmentCancellationRecords'}).exists()).toBe(false)
await wrapper.get('select[aria-label="预约仓库"]').setValue('ONT8')
await flushPromises()
expect(listInboundOrders).toHaveBeenLastCalledWith({ page: 1, size: 10, fbaWarehouseCode: 'ONT8' })
@@ -283,6 +298,8 @@ describe('appointment calendar data integration', () => {
const wrapper=render(); await flushPromises()
expect(wrapper.text()).toContain('未定时安排(1)')
expect(wrapper.findComponent({name:'AppointmentCalendarPanel'}).props('events')).toEqual([])
+ expect(wrapper.findComponent({name:'DataTable'}).exists()).toBe(false)
+ await wrapper.get('[data-unscheduled]').trigger('click')
expect(wrapper.findComponent({name:'DataTable'}).props('data')[0]).not.toHaveProperty('appointmentAt')
})
@@ -335,7 +352,7 @@ describe('appointment calendar data integration', () => {
it('keeps available arrivals and marks the result incomplete when appointments fail', async () => {
vi.mocked(listFbaAppointmentCalendar).mockRejectedValueOnce(new Error('预约暂不可用'))
- vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', bookingNo: 'C-1', arrivalAt: '2026-10-09T09:00:00', arrivalType: 'ESTIMATED' }])
+ vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', cabinetNo: 'D0001', arrivalAt: '2026-10-09T09:00:00', arrivalType: 'ESTIMATED' }])
const wrapper = render()
await flushPromises()
const calendar = wrapper.findComponent({ name: 'AppointmentCalendarPanel' })
diff --git a/src/modules/appointment-calendar/AppointmentCalendarView.vue b/src/modules/appointment-calendar/AppointmentCalendarView.vue
index 843de4e2..ed87fa2e 100644
--- a/src/modules/appointment-calendar/AppointmentCalendarView.vue
+++ b/src/modules/appointment-calendar/AppointmentCalendarView.vue
@@ -31,6 +31,7 @@ import { listArrivalCalendar, listPickupCalendar, type ArrivalCalendarEntry, typ
import { listTripCalendar, projectTripCalendar, tripIssues, type TripCalendarEntry } from './tripCalendar'
const cancellationTarget = ref
(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(() => [...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
-
-
-
+
+ 未定时安排({{ unscheduledError ? '加载失败' : unscheduledTotal }})
+ 取消记录
+
查看待核实车次({{ unresolvedTrips.length }})
+
+
+
+ 查看 / 继续
+
+
+
+
+
这些车次缺少地点或时区,暂未计入日历。原计划时间保留 UTC。
@@ -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; }
diff --git a/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts b/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts
index f4be5639..8fed4418 100644
--- a/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts
+++ b/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts
@@ -7,15 +7,38 @@ vi.mock('./planningApi', () => ({ listPlanningCargo: vi.fn(), resolvePlanningSou
vi.mock('vue-router', () => ({ onBeforeRouteLeave: vi.fn(), onBeforeRouteUpdate: vi.fn(), useRouter: () => ({ push: vi.fn() }) }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
const empty = { records: [], page: 1, size: 20, total: 0, pages: 0 }
-function render() {
- return mount(AppointmentCargoPanel, { global: { stubs: {
+function render(attach = false) {
+ return mount(AppointmentCargoPanel, { attachTo: attach ? document.body : undefined, global: { stubs: {
DataTable: { name: 'DataTable', props: ['data', 'total', 'resultsVisible'], emits: ['update:page'], template: '
' },
QueryToolbar: { name: 'QueryToolbar', emits: ['search'], template: '' },
- LoadErrorState: true, ApiErrorMeta: true, RowActions: true,
+ LoadErrorState: true, ApiErrorMeta: true, RowActions: true, AppointmentExternalCandidates: { name: 'AppointmentExternalCandidates', template: '' },
} } })
}
describe('appointment planning cargo query', () => {
beforeEach(() => { vi.resetAllMocks(); vi.mocked(listPlanningCargo).mockResolvedValue(empty) })
+ it('switches cargo tabs by click and keyboard without reloading first-leg cargo', async () => {
+ const wrapper = render(true); await flushPromises()
+ const tabs = () => wrapper.findAll('[role="tab"]')
+ expect(tabs().map(tab => tab.text())).toEqual(['头程货物', '外接货物'])
+ expect(wrapper.find('.integrated-query-group [role="tablist"]').exists()).toBe(true)
+ expect(tabs()[0]!.attributes('aria-selected')).toBe('true')
+ await tabs()[1]!.trigger('click')
+ expect(tabs()[1]!.attributes('aria-selected')).toBe('true')
+ expect(wrapper.findComponent({ name: 'AppointmentExternalCandidates' }).exists()).toBe(true)
+ await tabs()[1]!.trigger('keydown', { key: 'ArrowRight' })
+ await flushPromises()
+ expect(tabs()[0]!.attributes('aria-selected')).toBe('true')
+ expect(document.activeElement).toBe(tabs()[0]!.element)
+ expect(tabs()[1]!.attributes('tabindex')).toBe('-1')
+ await tabs()[0]!.trigger('keydown', { key: 'End' })
+ await flushPromises()
+ expect(tabs()[1]!.attributes('aria-selected')).toBe('true')
+ expect(document.activeElement).toBe(tabs()[1]!.element)
+ await tabs()[1]!.trigger('keydown', { key: 'Home' })
+ expect(tabs()[0]!.attributes('aria-selected')).toBe('true')
+ expect(listPlanningCargo).toHaveBeenCalledTimes(1)
+ wrapper.unmount()
+ })
it('uses one server-page request per entry, page change and reset', async () => {
const wrapper = render(); await flushPromises()
expect(listPlanningCargo).toHaveBeenCalledTimes(1)
diff --git a/src/modules/appointment-calendar/AppointmentCargoPanel.vue b/src/modules/appointment-calendar/AppointmentCargoPanel.vue
index 42484968..824bf14b 100644
--- a/src/modules/appointment-calendar/AppointmentCargoPanel.vue
+++ b/src/modules/appointment-calendar/AppointmentCargoPanel.vue
@@ -13,6 +13,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { listPlanningCargo, resolvePlanningSource, previewPlanningBatch, createSourceAppointment, type SourceAppointmentPayload, type PlanningBatchPreview, type ResolvedPlanningSource, type PlanningCargo, type PlanningSource } from './planningApi'
import AppointmentFollowupDialog from './AppointmentFollowupDialog.vue'
import AppointmentExternalCandidates from './AppointmentExternalCandidates.vue'
+import AppointmentCargoTabs from './AppointmentCargoTabs.vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
const router = useRouter()
@@ -149,11 +150,8 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller
-
- 头程货物
- 外接货物
-
+
清空选择
合批核对({{ selectedRows.length }}/100)
@@ -173,7 +171,12 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller
清空选择
合批核对({{ selectedRows.length }}/100)
-
+
+
+
待加入来源:{{ externalSource.bookingNo || externalSource.inboundCode || '尚未关联业务编号' }} · {{ externalSource.originWarehouseName }} → {{ externalSource.destinationWarehouseCode }}
diff --git a/src/modules/appointment-calendar/AppointmentCargoTabs.vue b/src/modules/appointment-calendar/AppointmentCargoTabs.vue
new file mode 100644
index 00000000..0debdda3
--- /dev/null
+++ b/src/modules/appointment-calendar/AppointmentCargoTabs.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/src/modules/appointment-calendar/AppointmentExternalCandidates.vue b/src/modules/appointment-calendar/AppointmentExternalCandidates.vue
index c5bcfc62..820ab666 100644
--- a/src/modules/appointment-calendar/AppointmentExternalCandidates.vue
+++ b/src/modules/appointment-calendar/AppointmentExternalCandidates.vue
@@ -44,7 +44,12 @@ onBeforeUnmount(() => { ++request; controller?.abort() })
-
+
+
+
diff --git a/src/modules/appointment-calendar/AppointmentTableRendering.test.ts b/src/modules/appointment-calendar/AppointmentTableRendering.test.ts
index 516c7c4c..ed4cec4e 100644
--- a/src/modules/appointment-calendar/AppointmentTableRendering.test.ts
+++ b/src/modules/appointment-calendar/AppointmentTableRendering.test.ts
@@ -32,9 +32,13 @@ describe('appointment tables use actual shared table slots', () => {
const wrapper = mount(AppointmentCargoPanel, { global: { plugins: [ElementPlus], stubs: { AppDialog: { template: '' } } } })
await flushPromises()
expect(listExternalAppointmentCandidates).not.toHaveBeenCalled()
+ expect(wrapper.get('.data-table__filters [role="tablist"]').text()).toContain('头程货物')
await wrapper.findAll('button').find(b => b.text() === '选择本页可合批货物')!.trigger('click')
await wrapper.findAll('button').find(b => b.text() === '外接货物')!.trigger('click'); await flushPromises()
expect(listExternalAppointmentCandidates).toHaveBeenCalledTimes(1)
+ expect(wrapper.findAll('.data-table__filters [role="tablist"]')).toHaveLength(1)
+ expect(wrapper.get('.data-table__filters [role="tablist"]').isVisible()).toBe(true)
+ expect(wrapper.get('.data-table__filters [role="tab"][aria-selected="true"]').text()).toBe('外接货物')
expect(wrapper.text()).toContain('B-1')
await wrapper.findAll('button').find(b => b.text() === '加入合批')!.trigger('click')
await wrapper.findAll('button').find(b => b.text().startsWith('合批核对') && b.isVisible())!.trigger('click'); await flushPromises()
diff --git a/src/modules/appointment-calendar/PROTOTYPE_MATRIX.md b/src/modules/appointment-calendar/PROTOTYPE_MATRIX.md
index 67ba78d0..4a3f7787 100644
--- a/src/modules/appointment-calendar/PROTOTYPE_MATRIX.md
+++ b/src/modules/appointment-calendar/PROTOTYPE_MATRIX.md
@@ -6,6 +6,9 @@
| 页面/操作 | 原型承载与布局 | 实现约束 |
|---|---|---|
+| 补充计划时段 | 用户2026-10-12新增预约截图:开始、结束时段输入框各占整行过长 | 共用 FbaAppointmentFields 的补充区域沿用三列网格,计划开始、结束时段同排各占一列,与主字段对齐;夏令时说明整行、两个时差输入同排。日历新增/编辑、合批安排、入仓单预约同步生效;字段语义及保存不变,待人工截图确认 |
+| 预约补充记录入口 | 用户2026-10-12截图1:原顶部未定时安排、取消记录两行默认折叠文字要求优化 | 用户2026-10-12后续确认覆盖页内展开方案:入口并入日历工具栏,与数据提示同组;未定时安排1000px、取消记录1160px居中弹框,保留独立分页及错误重试,取消记录仅打开时加载。新增预约紧邻日历标题,月/周及日期操作连续排列 |
+| 送仓与排车货源页签 | 用户2026-10-12截图2、3:参考尾程订单下划线页签 | 头程货物、外接货物通过同一局部页签组件复用 density-status-tabs,两种模式均放入表格 filters 的 integrated-query-group 内并紧邻搜索;点击及方向键/Home/End切换,保留跨货源选择;新增、编辑、取消预约沿用原承载方式。待人工1920×1080、100%截图确认 |
| 预约列表 | 左侧月/周日历,右侧选中日期详情 | 保留双栏日历;到港蓝色、送仓绿色,区分预计/实际到港 |
| 新增预约 | 居中表单,按用户简化要求使用1080px三列 | 仓库远程搜索并自动加载货物;主区保留计划日期、登记阶段及可选备注。系统单号自动生成,不要求外部约号和预计装车时间;计划时段收至补充信息。选择“已取得对方正式确认”才展开外部号和确认时段,并显示地点时区 |
| 编辑预约 | 复用居中表单 | 与入仓单共用记录;版本校验;真实货物归属不允许手填更改 |
diff --git a/src/modules/appointment-calendar/api.ts b/src/modules/appointment-calendar/api.ts
index d8aa7fdb..874e796e 100644
--- a/src/modules/appointment-calendar/api.ts
+++ b/src/modules/appointment-calendar/api.ts
@@ -2,7 +2,7 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export interface ArrivalCalendarEntry {
containerBookingId: string
- bookingNo: string
+ cabinetNo: string
containerNo?: string | null
vesselName?: string | null
voyageNo?: string | null
@@ -14,7 +14,7 @@ export interface ArrivalCalendarEntry {
export interface PickupCalendarEntry {
containerBookingId: string
- bookingNo: string
+ cabinetNo: string
containerNo: string | null
destinationPortName: string | null
destinationWarehouseCode: string | null
diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts
index fcba101b..e47ef8a4 100644
--- a/src/modules/container-loading/api/platformContainerLoading.test.ts
+++ b/src/modules/container-loading/api/platformContainerLoading.test.ts
@@ -182,7 +182,7 @@ describe('platform container loading API', () => {
it('queries workbench containers with the same global filters and excludes the retired booking parameter', async () => {
vi.mocked(http.get).mockResolvedValueOnce({ code: '200', data: {
- records: [{ bookingId: 9007199254740993n, bookingNo: 'BK-1', cabinetNo: 'CAB-1', containerNo: null, warehouseId: 8, warehouseName: '义乌仓', channelCode: 'MATSON_CLX_TRUCK', channelName: '美森正班海卡', containerType: '40HQ', planId: 9007199254740994n, planNo: 'LP-1', planStatus: 'EDITING', version: '2' }],
+ records: [{ bookingId: 9007199254740993n, cabinetNo: 'CAB-1', containerNo: null, warehouseId: 8, warehouseName: '义乌仓', channelCode: 'MATSON_CLX_TRUCK', channelName: '美森正班海卡', containerType: '40HQ', planId: 9007199254740994n, planNo: 'LP-1', planStatus: 'EDITING', version: '2' }],
page: '1', size: '200', total: '1', pages: '1',
} })
diff --git a/src/modules/container-loading/components/ContainerOverviewPanel.test.ts b/src/modules/container-loading/components/ContainerOverviewPanel.test.ts
index 4f06b632..1d795b0d 100644
--- a/src/modules/container-loading/components/ContainerOverviewPanel.test.ts
+++ b/src/modules/container-loading/components/ContainerOverviewPanel.test.ts
@@ -8,7 +8,7 @@ import { downloadContainerPdaPhoto } from '../api/platformContainerLoading'
vi.mock('../api/platformContainerLoading', () => ({ downloadContainerPdaPhoto: vi.fn() }))
const overview: ContainerOverview = {
- container: { id: '7', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', status: 'LOADING', warehouseId: '8', warehouseName: '义乌仓', voyageNo: 'V1', vesselName: '船一', pickupCode: '00123456', pickupCodeUpdatedBy: '13', pickupCodeUpdatedByName: '操作员', pickupCodeUpdatedAt: '2026-08-13T10:30:00', version: 2 },
+ container: { id: '7', cabinetNo: 'CB-1', containerNo: 'MSCU-1', status: 'LOADING', warehouseId: '8', warehouseName: '义乌仓', voyageNo: 'V1', vesselName: '船一', pickupCode: '00123456', pickupCodeUpdatedBy: '13', pickupCodeUpdatedByName: '操作员', pickupCodeUpdatedAt: '2026-08-13T10:30:00', version: 2 },
loadingPlanId: '9',
pda: { taskId: '10', loadingPlanId: '9', containerBookingId: '7', taskStatus: 'IN_PROGRESS', workflowStatus: 'AWAITING_REVIEW', taskVersion: 2, itemCount: 4, completedItemCount: 4, unresolvedItemCount: 0, photoCounts: [{ evidenceType: 'PHOTO_STEP_EMPTY_CONTAINER', count: 2 }], photos: [{ evidenceId: '20', tenantId: '21', inboundId: '22', evidenceType: 'PHOTO_STEP_EMPTY_CONTAINER', fileId: '23', fileName: 'empty.jpg', status: 'ACTIVE', downloadPath: '/api/platform/v1/containers/7/pda/photos/23/download', uploadedAt: '2026-08-13T12:30:00' }], exceptionReasons: ['一项待复核'] },
documents: [{ id: '11', documentType: 'DELIVERY_ORDER', fileId: '9007199254740999', fileName: 'do.pdf', fileHash: null, uploadedBy: '13', uploadedByName: '操作员', uploadedAt: '2026-08-13T10:00:00', version: 1, completed: false }],
diff --git a/src/modules/container-loading/components/OceanBookingLinkDialog.test.ts b/src/modules/container-loading/components/OceanBookingLinkDialog.test.ts
index fa8e958f..b10d86fe 100644
--- a/src/modules/container-loading/components/OceanBookingLinkDialog.test.ts
+++ b/src/modules/container-loading/components/OceanBookingLinkDialog.test.ts
@@ -10,7 +10,7 @@ vi.mock('@/modules/ocean-bookings', () => ({ listOceanBookingLinkCandidates: vi.
vi.mock('../api/platformContainerLoading', () => ({ linkContainerOceanBooking: vi.fn() }))
const container = {
- id: '9001', cabinetNo: 'CAB-1', bookingNo: 'CB-1', containerNo: null, planId: '7001', planStatus: 'EDITING',
+ id: '9001', cabinetNo: 'CAB-1', containerNo: null, planId: '7001', planStatus: 'EDITING',
sourceType: 'SIMULATION', warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null,
oceanBookingId: null, billOfLadingNo: null, vesselName: null, voyageNo: null, containerType: '40HQ',
shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: null,
diff --git a/src/modules/container-loading/pages/ContainersView.test.ts b/src/modules/container-loading/pages/ContainersView.test.ts
index de4dd8f2..7fd42145 100644
--- a/src/modules/container-loading/pages/ContainersView.test.ts
+++ b/src/modules/container-loading/pages/ContainersView.test.ts
@@ -75,9 +75,9 @@ describe('ContainersView', () => {
vi.mocked(recordTerminalDrayageEmptyReturn).mockReset().mockResolvedValue({ ...drayage, status: 'EMPTY_RETURNED', actualEmptyReturnAt: '2026-09-11T13:00:00', version: 4, emptyReturnWarning: { status: 'RESOLVED', deadlineAt: '2026-09-10T10:00:00', overdueHours: 0, overdueDays: 0 }, allowedActions: ['SELECT_FLEET'] })
vi.mocked(listContainerArrivalTimes).mockReset().mockResolvedValue([{ sourceContainerBookingId: '9001', estimatedWarehouseArrivalAt: '2026-09-25T15:31:00', actualWarehouseArrivalAt: '2026-09-26T09:00:00', version: 0 }])
vi.mocked(updateContainerExpectedArrival).mockReset().mockResolvedValue({ sourceContainerBookingId: '9001', estimatedWarehouseArrivalAt: '2026-09-25T16:00:00', actualWarehouseArrivalAt: '2026-09-26T09:00:00', version: 1 })
- vi.mocked(listContainers).mockResolvedValue({ records: [{ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER', 'SELECT_FLEET', 'SCHEDULE_PICKUP', 'RECORD_EMPTY_RETURN'], terminalDrayage: drayage, oceanBookingId: null, warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500, terminalTruckingCompanyId: null, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }], page: 1, size: 20, total: 1, pages: 1 })
- vi.mocked(getContainer).mockResolvedValue({ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER'], oceanBookingId: null, warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500, terminalTruckingCompanyId: null, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null })
- vi.mocked(getContainerOverview).mockResolvedValue({ container: { id: '9001', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', status: 'WAIT_LOADING', warehouseId: '8', warehouseName: '义乌仓', voyageNo: null, vesselName: null, version: 0 }, loadingPlanId: null, pda: null, documents: [], exception: { active: false, reason: null, markedBy: null, markedByName: null, markedAt: null }, exceptionHistory: [], timeline: [] })
+ vi.mocked(listContainers).mockResolvedValue({ records: [{ id: '9001', planId: null, cabinetNo: 'CB-1', containerNo: 'MSCU-1', sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER', 'SELECT_FLEET', 'SCHEDULE_PICKUP', 'RECORD_EMPTY_RETURN'], terminalDrayage: drayage, oceanBookingId: null, warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500, terminalTruckingCompanyId: null, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }], page: 1, size: 20, total: 1, pages: 1 })
+ vi.mocked(getContainer).mockResolvedValue({ id: '9001', planId: null, cabinetNo: 'CB-1', containerNo: 'MSCU-1', sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER'], oceanBookingId: null, warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500, terminalTruckingCompanyId: null, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null })
+ vi.mocked(getContainerOverview).mockResolvedValue({ container: { id: '9001', cabinetNo: 'CB-1', containerNo: 'MSCU-1', status: 'WAIT_LOADING', warehouseId: '8', warehouseName: '义乌仓', voyageNo: null, vesselName: null, version: 0 }, loadingPlanId: null, pda: null, documents: [], exception: { active: false, reason: null, markedBy: null, markedByName: null, markedAt: null }, exceptionHistory: [], timeline: [] })
})
it('loads the real paged contract and renders core container fields', async () => {
@@ -296,7 +296,7 @@ describe('ContainersView', () => {
it('uses stable view labels when cabinetNo is missing', async () => {
const base = {
- id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', containerType: '40HQ', planStatus: 'WAIT_LOADING' as const, version: 0,
+ id: '9001', planId: null, cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', containerType: '40HQ', planStatus: 'WAIT_LOADING' as const, version: 0,
billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null,
departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩',
destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500,
@@ -305,9 +305,9 @@ describe('ContainersView', () => {
}
vi.mocked(listContainers).mockResolvedValueOnce({
records: [
- { ...base, id: '9002', cabinetNo: undefined, containerNo: 'MSCU-2', bookingNo: 'BK-2' },
- { ...base, id: '9003', cabinetNo: '', containerNo: null, bookingNo: 'BK-3' },
- { ...base, id: '9004', cabinetNo: null, containerNo: null, bookingNo: '' },
+ { ...base, id: '9002', cabinetNo: undefined, containerNo: 'MSCU-2' },
+ { ...base, id: '9003', cabinetNo: '', containerNo: null },
+ { ...base, id: '9004', cabinetNo: null, containerNo: null },
],
page: 1, size: 20, total: 3, pages: 1,
} as never)
@@ -319,13 +319,13 @@ describe('ContainersView', () => {
const labels = wrapper.findAll('.el-table__body button')
.map((button) => button.attributes('aria-label'))
.filter((label): label is string => Boolean(label))
- expect(labels).toEqual(expect.arrayContaining(['查看 MSCU-2', '查看 BK-3', '查看货柜详情']))
+ expect(labels).toEqual(['查看 MSCU-2', '查看货柜详情', '查看货柜详情'])
expect(labels.every((label) => !/undefined|null/.test(label))).toBe(true)
})
it('routes each plan status to its owning workflow without reopening pushed drafts', async () => {
const base = {
- id: '9001', planId: '9101', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', version: 0,
+ id: '9001', planId: '9101', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', version: 0,
billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null,
departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩',
destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 19500,
@@ -632,7 +632,7 @@ describe('ContainersView', () => {
it('filters OB-260825-0183 for CAB-260825-0215 because the real warehouses differ', async () => {
vi.mocked(listContainers).mockResolvedValue({ records: [{
- id: '215', planId: null, bookingNo: 'CB-260825-0215', cabinetNo: 'CAB-260825-0215', containerNo: null,
+ id: '215', planId: null, cabinetNo: 'CAB-260825-0215', containerNo: null,
sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER'], oceanBookingId: null, warehouseId: '193000000000200002', warehouseName: '东莞仓',
channelGroupCode: 'EXX', channelCode: null, channelName: null, containerType: '45HQ', planStatus: 'EDITING',
capacityCbm: 86, maxWeightKg: 29000, version: 7,
@@ -679,7 +679,7 @@ describe('ContainersView', () => {
it('keeps a three-field match selectable when only capacity snapshots differ and blocks duplicate waves', async () => {
let resolveCandidates!: (value: Awaited>) => void
vi.mocked(listContainers).mockResolvedValue({ records: [{
- id: '315', planId: null, bookingNo: 'CB-CAPACITY-SNAPSHOT', cabinetNo: 'CAB-CAPACITY-SNAPSHOT', containerNo: null,
+ id: '315', planId: null, cabinetNo: 'CAB-CAPACITY-SNAPSHOT', containerNo: null,
sourceType: 'SIMULATION', allowedActions: ['EDIT_CONTAINER'], oceanBookingId: null, warehouseId: '193000000000200002', warehouseName: '东莞仓',
channelGroupCode: 'EXX', channelCode: null, channelName: null, containerType: '45HQ', planStatus: 'WAIT_LOADING',
capacityCbm: 86, maxWeightKg: 29000, version: 7,
diff --git a/src/modules/container-loading/pages/ContainersView.vue b/src/modules/container-loading/pages/ContainersView.vue
index 4a1bd40c..f550d029 100644
--- a/src/modules/container-loading/pages/ContainersView.vue
+++ b/src/modules/container-loading/pages/ContainersView.vue
@@ -313,7 +313,7 @@ function localDateTimeValue() {
return now.toISOString().slice(0, 19)
}
function containerViewLabel(row: ContainerBookingResource) {
- const reference = [row.cabinetNo, row.containerNo, row.bookingNo]
+ const reference = [row.cabinetNo, row.containerNo]
.find((value) => typeof value === 'string' && value.trim())
return reference ? `查看 ${reference}` : '查看货柜详情'
}
diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts
index 6985904f..b49bc05e 100644
--- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts
+++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts
@@ -64,7 +64,7 @@ vi.mock('@/modules/warehouses', () => ({
vi.mock('../smartLoadingPlanGroups', () => ({ loadSmartLoadingPlanCoLoadGroups: vi.fn().mockReturnValue(new Map()) }))
const container = {
- id: '9007199254740993', planId: '9007199254740994', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1',
+ id: '9007199254740993', planId: '9007199254740994', cabinetNo: 'CB-1', containerNo: 'MSCU-1',
warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HQ', planStatus: 'EDITING' as const, version: 1,
billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: '31', shippingCompanyName: '美森轮船',
departurePortCode: null, departurePortName: null, destinationPortCode: null, destinationPortName: null,
@@ -101,7 +101,7 @@ function candidatePage(records: unknown[], page = 1, size = 20, total = records.
function containerPage(records: ContainerBookingResource[] = [container]) {
return {
records: records.map((item) => ({
- bookingId: item.id, bookingNo: item.bookingNo, cabinetNo: item.cabinetNo, containerNo: item.containerNo,
+ bookingId: item.id, cabinetNo: item.cabinetNo, containerNo: item.containerNo,
warehouseId: item.warehouseId, warehouseName: item.warehouseName || '', channelGroupCode: item.channelGroupCode,
channelCode: item.channelCode, channelName: item.channelName,
containerType: item.containerType, capacityCbm: item.capacityCbm, maxWeightKg: item.maxWeightKg,
diff --git a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts
index 0f9d9e48..1e25c883 100644
--- a/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts
+++ b/src/modules/container-loading/pages/SmartLoadingWorkbenchView.test.ts
@@ -18,7 +18,7 @@ vi.mock('../smartLoadingPlanGroups', () => ({ loadSmartLoadingPlanCoLoadGroups:
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
const container = {
- id: '9007199254740993', planId: '9007199254740994', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1',
+ id: '9007199254740993', planId: '9007199254740994', cabinetNo: 'CB-1', containerNo: 'MSCU-1',
warehouseId: '8', warehouseName: '义乌仓', channelGroupCode: 'MATSON_CLX', channelCode: null, channelName: null, containerType: '40HQ', planStatus: 'EDITING' as const, version: 1,
billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null,
departurePortCode: null, departurePortName: null, destinationPortCode: null, destinationPortName: null,
diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts
index 2bc833ea..1fb043ea 100644
--- a/src/modules/container-loading/types.ts
+++ b/src/modules/container-loading/types.ts
@@ -4,7 +4,6 @@ export interface ContainerBookingResource {
id: string
planId: string | null
planNo?: string | null
- bookingNo: string
containerNo: string | null
cabinetNo: string
sourceType?: 'SIMULATION' | 'OCEAN_BOOKING'
@@ -175,7 +174,7 @@ export interface ContainerTimelineEntry {
}
export interface ContainerOverview {
- container: Pick & { status: string }
+ container: Pick & { status: string }
loadingPlanId: string | null
pda: ContainerPdaSummary | null
documents: ContainerOverviewDocument[]
@@ -373,7 +372,6 @@ export interface SmartLoadingCandidatePage {
export interface SmartLoadingWorkbenchContainer {
bookingId: string
- bookingNo: string
cabinetNo: string
containerNo: string | null
warehouseId: string
diff --git a/src/modules/outbound-dispatch/pages/OutboundDispatchesView.test.ts b/src/modules/outbound-dispatch/pages/OutboundDispatchesView.test.ts
index f6604778..526146d3 100644
--- a/src/modules/outbound-dispatch/pages/OutboundDispatchesView.test.ts
+++ b/src/modules/outbound-dispatch/pages/OutboundDispatchesView.test.ts
@@ -21,7 +21,7 @@ vi.mock('../api/platformOutboundDispatch', () => ({ listOutboundDispatches: vi.f
const customer = { id: '10001', tenantCode: 'CUS-1', tenantName: '示例客户', legalName: '示例客户有限公司', status: 'ACTIVE' } as PlatformTenantResource
const order = { orderId: '20001', orderNo: 'ORD-001', inboundId: '30001', inboundCode: 'IN-001', customerId: '10001', customerName: '示例客户' } as UnifiedOrderResource
const secondOrder = { ...order, orderId: '20002', orderNo: 'ORD-002', inboundId: '30002', inboundCode: 'IN-002' } as UnifiedOrderResource
-const container = { id: '40001', bookingNo: 'BK-001', cabinetNo: 'CAB-001', containerNo: 'MSCU-001' } as ContainerBookingResource
+const container = { id: '40001', cabinetNo: 'CAB-001', containerNo: 'MSCU-001' } as ContainerBookingResource
const row = { orderId: '20001', orderNo: 'ORD-001', inboundId: '30001', inboundCode: 'IN-001', customerId: '10001', customerName: '示例客户', containerBookingId: '40001', cabinetNo: 'CAB-001', containerNo: 'MSCU-001', oceanBookingStatus: 'RELEASED', truckAppointmentStatus: 'COMPLETED', pickTaskStatus: 'AWAITING_REVIEW', dispatchStatus: 'READY_FOR_DISPATCH', version: 1, gate: { passed: true, items: [], checkedAt: '2026-08-11T10:00:00' } } as OutboundDispatchResource
enableAutoUnmount(afterEach)
diff --git a/src/modules/outbound-dispatch/pages/OutboundDispatchesView.vue b/src/modules/outbound-dispatch/pages/OutboundDispatchesView.vue
index 70d3d756..731781f2 100644
--- a/src/modules/outbound-dispatch/pages/OutboundDispatchesView.vue
+++ b/src/modules/outbound-dispatch/pages/OutboundDispatchesView.vue
@@ -41,7 +41,7 @@ let disposed = false
const fields = computed(() => [
{ key: 'customerId', type: 'select', label: '客户', width: 240, placeholder: '全部客户', options: customers.value.map((item) => ({ label: item.tenantName, value: String(item.id) })), props: { filterable: true, loading: optionsLoading.value } },
{ key: 'orderId', type: 'select', label: '入仓码', width: 280, placeholder: filters.value.customerId ? '请选择该客户的入仓码' : '请选择入仓码', options: orders.value.map((item) => ({ label: item.inboundCode || '入仓码暂缺', value: String(item.orderId), disabled: item.inboundId == null })), props: { filterable: true, loading: optionsLoading.value } },
- { key: 'containerBookingId', type: 'select', label: '柜次', width: 240, placeholder: '请选择柜号或集装箱号', options: containers.value.map((item) => ({ label: `${item.cabinetNo || item.bookingNo}${item.containerNo ? ` / ${item.containerNo}` : ''}`, value: item.id })), props: { filterable: true, loading: optionsLoading.value } },
+ { key: 'containerBookingId', type: 'select', label: '柜次', width: 240, placeholder: '请选择柜号或集装箱号', options: containers.value.map((item) => ({ label: `${item.cabinetNo || '—'}${item.containerNo ? ` / ${item.containerNo}` : ''}`, value: item.id })), props: { filterable: true, loading: optionsLoading.value } },
])
const columns = [
diff --git a/src/modules/overseas-warehouse/components/FbaAppointmentFields.vue b/src/modules/overseas-warehouse/components/FbaAppointmentFields.vue
index 40e49dc9..c1079be1 100644
--- a/src/modules/overseas-warehouse/components/FbaAppointmentFields.vue
+++ b/src/modules/overseas-warehouse/components/FbaAppointmentFields.vue
@@ -14,17 +14,21 @@ defineProps<{ model: AppointmentForm; appointmentNo?: string; timezone?: string
diff --git a/src/modules/port-schedules/components/ContainerPortSchedulePanel.test.ts b/src/modules/port-schedules/components/ContainerPortSchedulePanel.test.ts
index dc0ef1f5..02029741 100644
--- a/src/modules/port-schedules/components/ContainerPortSchedulePanel.test.ts
+++ b/src/modules/port-schedules/components/ContainerPortSchedulePanel.test.ts
@@ -7,7 +7,7 @@ import { ApiResponseError } from '@/shared/api/http'
const can = vi.hoisted(() => vi.fn(() => true))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) }))
vi.mock('../api/platformPortSchedules', () => ({ getPortSchedule: vi.fn(), createPortSchedule: vi.fn(), updatePortSchedule: vi.fn() }))
-const container = { id: 'C1', customerId: '0', customerName: '平台订舱', oceanBookingId: 'B1', currentPortScheduleId: 'S1', bookingNo: 'BOOK1', cabinetNo: 'D1', containerNo: 'TEST', vesselName: 'OLD', voyageNo: 'V1', departurePortCode: 'CNSHA', destinationPortCode: 'USLAX', estimatedSailingAt: '2026-10-04T01:00:00', estimatedPortArrivalAt: '2026-10-20T01:00:00' }
+const container = { id: 'C1', customerId: '0', customerName: '平台订舱', oceanBookingId: 'B1', currentPortScheduleId: 'S1', billOfLadingNo: 'BL1', cabinetNo: 'D1', containerNo: 'TEST', vesselName: 'OLD', voyageNo: 'V1', departurePortCode: 'CNSHA', destinationPortCode: 'USLAX', estimatedSailingAt: '2026-10-04T01:00:00', estimatedPortArrivalAt: '2026-10-20T01:00:00' }
const original = { id: 'S1', scheduleNo: 'PS1', version: 5, customerId: '0', oceanBookingId: 'B1', containerBookingId: 'C1', sourceType: 'CARRIER', sourceName: '官网', sourceRef: 'REF', vesselName: 'OLD', voyageNo: 'V1', departurePortCode: 'CNSHA', destinationPortCode: 'USLAX', etdAt: container.estimatedSailingAt, etaAt: container.estimatedPortArrivalAt, observedAt: '2026-10-01T00:00:00', expiresAt: '2026-10-10T00:00:00', portCutoffAt: '2026-10-03T00:00:00', remark: '原备注' }
const result = () => ({ portSchedule: { schedule: { ...original }, expired: false }, histories: [] })
interface VM { begin(): Promise
; save(): Promise; form: Record; error: string; open: boolean; schedule: ReturnType | null }
diff --git a/src/modules/port-schedules/components/ContainerPortSchedulePanel.vue b/src/modules/port-schedules/components/ContainerPortSchedulePanel.vue
index d6d53ec4..6a32d70a 100644
--- a/src/modules/port-schedules/components/ContainerPortSchedulePanel.vue
+++ b/src/modules/port-schedules/components/ContainerPortSchedulePanel.vue
@@ -8,7 +8,7 @@ import type { PortScheduleData, PortScheduleDetail, PortScheduleSaveRequest } fr
const props = defineProps<{ container: {
id: string; currentPortScheduleId?: string | null; customerId?: string | null; customerName?: string | null
- oceanBookingId?: string | null; bookingNo: string; cabinetNo: string; containerNo: string | null
+ oceanBookingId?: string | null; billOfLadingNo?: string | null; cabinetNo: string; containerNo: string | null
vesselName: string | null; voyageNo: string | null; departurePortCode: string | null; departurePortName?: string | null
destinationPortCode: string | null; destinationPortName?: string | null
estimatedSailingAt: string | null; estimatedPortArrivalAt: string | null
@@ -103,7 +103,7 @@ onBeforeUnmount(() => { generation++ })
-
{{ container.cabinetNo || '—' }} · {{ container.containerNo || '—' }} · 订舱 {{ container.bookingNo || '—' }} · {{ container.customerName || '—' }}
+
{{ container.cabinetNo || '—' }} · {{ container.containerNo || '—' }} · 提单 {{ container.billOfLadingNo || '—' }} · {{ container.customerName || '—' }}
请登记本柜的船名、航次和预计时间,并按实际信息填写来源及有效期。
diff --git a/src/styles/adminDensity.test.ts b/src/styles/adminDensity.test.ts
index 5ffaa896..7fcf4521 100644
--- a/src/styles/adminDensity.test.ts
+++ b/src/styles/adminDensity.test.ts
@@ -172,6 +172,7 @@ describe('approved Admin density ownership', () => {
'unified-orders/pages/UnifiedOrdersView.vue', 'overseas-warehouse/pages/LastMileOrdersView.vue',
'overseas-master-data/pages/VehiclesView.vue', 'suppliers/pages/SuppliersView.vue',
'domestic-containers/pages/DomesticContainersView.vue', 'container-loading/pages/ContainersView.vue',
+ 'appointment-calendar/AppointmentCargoTabs.vue',
]) {
const page = parse(read(`modules/${file}`)).descriptor
expect(page.template!.content).toMatch(/class="[^"]*density-status-tabs[^"]*" role="tablist"/)