diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 815f0b19..96a00296 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -95,6 +95,34 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.text()).not.toContain('LP-99') }) + it('explains simulated empty cabinets instead of presenting an internal id as a container number', async () => { + vi.mocked(listContainers).mockResolvedValue({ + records: [{ + ...container, + cabinetNo: '2088475410134773762', + containerNo: null, + channelName: '美森正班海卡包税', + warehouseName: '义乌国内仓', + containerType: '40HQ', + }], + page: 1, + size: 100, + total: 1, + pages: 1, + }) + const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + + const option = wrapper.find('.container-option') + expect(option.text()).toContain('模拟柜次') + expect(option.text()).toContain('模拟柜次:真实柜号待录入') + expect(option.text()).toContain('真实柜号:待录入') + expect(option.text()).toContain('渠道:美森正班海卡包税') + expect(option.text()).toContain('仓库:义乌国内仓') + expect(option.text()).toContain('柜型:40HQ') + expect(option.text()).not.toContain('2088475410134773762') + }) + it('groups containers by channel and reloads candidates when the channel changes', async () => { const other = { ...container, @@ -318,6 +346,8 @@ describe('GlobalSmartLoadingView', () => { expect(source).toContain('overflow-wrap: anywhere;') expect(source).toContain('.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }') expect(source).toContain('.container-pane { max-height: min(58vh, 460px); }') + expect(source).toContain('grid-template-columns: minmax(390px, 1.1fr) minmax(320px, .9fr) minmax(460px, 1.35fr)') + expect(source).toContain('min-height: 112px') }) it('uses overflow-aware channel tabs with a compact right-aligned summary', () => { diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index ba224215..d1cd20dc 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -142,7 +142,17 @@ function displayContainerNo(container: ContainerBookingResource | null | undefin if (!container) return '柜号待生成' const actual = String(container.containerNo || '').trim() const internalReference = /^(?:MAN|SPLIT)-/i.test(actual) - return actual && !internalReference ? actual : String(container.cabinetNo || '').trim() || '柜号待生成' + if (actual && !internalReference) return actual + const cabinetReference = String(container.cabinetNo || '').trim() + return /^\d{15,}$/.test(cabinetReference) ? '模拟柜次' : cabinetReference || '柜号待生成' +} +function hasRealContainerNo(container: ContainerBookingResource | null | undefined) { + if (!container) return false + const value = String(container.containerNo || '').trim() + return Boolean(value) && !/^(?:MAN|SPLIT)-/i.test(value) +} +function hasOpaqueCabinetReference(container: ContainerBookingResource | null | undefined) { + return Boolean(container && /^\d{15,}$/.test(String(container.cabinetNo || '').trim())) } function syncDrafts(next: SmartLoadingPlan, preserved: Record = {}) { for (const key of Object.keys(quantityDrafts)) delete quantityDrafts[key] @@ -445,7 +455,18 @@ onMounted(() => { void load(); void loadCustomers() }) :class="{ selected: item.id === selectedContainerId }" @click="selectContainer(item.id)" > - {{ displayContainerNo(item) }} + + {{ hasRealContainerNo(item) ? displayContainerNo(item) : '模拟柜次' }} + + {{ hasRealContainerNo(item) ? '已录入柜号' : '空柜' }} + + + + {{ hasRealContainerNo(item) ? `柜号:${displayContainerNo(item)}` : (hasOpaqueCabinetReference(item) ? '模拟柜次:真实柜号待录入' : `柜次参考:${displayContainerNo(item)}`) }} + + 渠道:{{ item.channelName || item.channelCode || '渠道待补充' }} + 仓库:{{ item.warehouseName || item.warehouseId || '仓库待补充' }} · 柜型:{{ item.containerType || '柜型待补充' }} + 真实柜号:待录入 @@ -518,7 +539,7 @@ onMounted(() => { void load(); void loadCustomers() }) .compact-summary dd { margin: 0; color: var(--cc-text); font-size: 12px; } .compact-summary strong { color: var(--el-color-primary); font-size: 14px; } .pane-header small,.container-option span,.global-loading-page small { color: var(--cc-muted); font-size: 12px; } -.dispatch-grid { display: grid; grid-template-columns: minmax(390px, 1.1fr) 270px minmax(460px, 1.35fr); height: clamp(520px, 68vh, 720px); min-height: 0; overflow-x: auto; border: 1px solid var(--cc-border); background: #fff; } +.dispatch-grid { display: grid; grid-template-columns: minmax(390px, 1.1fr) minmax(320px, .9fr) minmax(460px, 1.35fr); height: clamp(520px, 68vh, 720px); min-height: 0; overflow-x: auto; border: 1px solid var(--cc-border); background: #fff; } .embedded-smart-loading .dispatch-grid { min-height: 520px; height: min(62vh, 700px); } .dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; } .dispatch-pane + .dispatch-pane { border-left: 1px solid var(--cc-border); } @@ -528,10 +549,12 @@ onMounted(() => { void load(); void loadCustomers() }) .candidate-pane :deep(.el-table),.plan-pane :deep(.el-table) { height: 100%; } .container-pane { background: #fafafa; } .container-list { display: grid; min-height: 0; flex: 1; grid-auto-rows: max-content; align-content: start; gap: 8px; padding: 10px; overflow-y: auto; } -.container-option { display: grid; width: 100%; min-height: 0; height: auto; align-content: start; gap: 5px; padding: 10px; overflow: hidden; border: 1px solid var(--cc-border); border-radius: 6px; background: #fff; color: var(--cc-text); line-height: 1.35; text-align: left; cursor: pointer; } +.container-option { display: grid; width: 100%; min-height: 112px; height: auto; align-content: start; gap: 6px; padding: 14px; overflow: hidden; border: 1px solid var(--cc-border); border-radius: 6px; background: #fff; color: var(--cc-text); line-height: 1.4; text-align: left; cursor: pointer; } .container-option:hover,.container-option:focus-visible { border-color: var(--el-color-primary); outline: none; } .container-option.selected { border-color: var(--el-color-primary); box-shadow: inset 3px 0 0 var(--el-color-primary); background: var(--el-color-primary-light-9); } .container-option__top { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: var(--cc-text) !important; } +.container-option__identity { color: var(--cc-text); font-size: 13px; font-weight: 600; } +.container-option__hint { color: var(--el-color-warning); font-weight: 600; } .container-option span,.container-option strong { min-width: 0; overflow-wrap: anywhere; } .plan-header { flex-wrap: wrap; } .plan-actions,.row-actions,.meter-title { display: flex; align-items: center; gap: 8px; } diff --git a/src/modules/customers/pages/CustomersView.test.ts b/src/modules/customers/pages/CustomersView.test.ts index cb741d38..b6a3656e 100644 --- a/src/modules/customers/pages/CustomersView.test.ts +++ b/src/modules/customers/pages/CustomersView.test.ts @@ -559,7 +559,7 @@ describe('CustomersView', () => { expect(document.querySelector('.el-table__fixed')).toBeNull() }) - it('keeps every permitted customer action visible without a more menu', async () => { + it('keeps three high-frequency actions visible and moves overflow actions into a menu', async () => { const auth = useAuthStore() auth.permissions = [...auth.permissions, 'platform:customer:reset-password', 'platform:customer:impersonate', 'platform:customer:status', 'platform:customer:delete'] useAccessPreviewStore().initializeAfterLogin() @@ -570,11 +570,10 @@ describe('CustomersView', () => { expect(tableButtonByText('详情')).toBeDefined() expect(tableButtonByText('编辑')).toBeDefined() - expect(document.querySelector('.el-table__body button[aria-label^="更多客户操作:"]')).toBeNull() + expect(document.querySelector('.el-table__body button[aria-label^="更多客户操作:"]')).not.toBeNull() expect(tableButtonByText('重置密码')).toBeDefined() - expect(tableButtonByText('跳转登录')).toBeDefined() - tableButtonByText('跳转登录')?.click() - await flushPromises() + expect(tableButtonByText('跳转登录')).toBeUndefined() + await clickMoreAction('跳转登录') expect(document.body.textContent).toContain('代登录原因') }) @@ -596,7 +595,7 @@ describe('CustomersView', () => { expect(tableButtonByText('删除')).toBeUndefined() }) - it('renders all permitted actions directly for multiple customers', async () => { + it('renders one overflow menu per customer when permitted actions exceed three', async () => { const auth = useAuthStore() auth.permissions = [...auth.permissions, 'platform:customer:reset-password', 'platform:customer:impersonate', 'platform:customer:status', 'platform:customer:delete'] useAccessPreviewStore().initializeAfterLogin() @@ -612,8 +611,8 @@ describe('CustomersView', () => { mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } }) await flushPromises() - expect(document.querySelectorAll('.el-table__body button[aria-label^="更多客户操作:"]').length).toBe(0) - expect(document.querySelectorAll('.el-table__body button').length).toBeGreaterThan(9) + expect(document.querySelectorAll('.el-table__body button[aria-label^="更多客户操作:"]').length).toBe(3) + expect(document.querySelectorAll('.el-table__body .table-actions > .el-button').length).toBe(9) }) it('keeps the action row usable when all customer identifiers are missing', async () => { @@ -628,7 +627,7 @@ describe('CustomersView', () => { mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } }) await flushPromises() - expect(document.querySelector('.el-table__body button[aria-label^="更多客户操作:"]')).toBeNull() + expect(document.querySelector('.el-table__body button[aria-label="更多客户操作:当前客户"]')).not.toBeNull() expect(tableButtonByText('详情')).toBeDefined() }) }) diff --git a/src/modules/customers/pages/CustomersView.vue b/src/modules/customers/pages/CustomersView.vue index 97a2f719..d2caa0de 100644 --- a/src/modules/customers/pages/CustomersView.vue +++ b/src/modules/customers/pages/CustomersView.vue @@ -1,7 +1,7 @@