diff --git a/src/modules/container-loading/components/SmartLoadingDialog.test.ts b/src/modules/container-loading/components/SmartLoadingDialog.test.ts index 5283eedd..64f4c3e0 100644 --- a/src/modules/container-loading/components/SmartLoadingDialog.test.ts +++ b/src/modules/container-loading/components/SmartLoadingDialog.test.ts @@ -1,6 +1,8 @@ import ElementPlus from 'element-plus' import { flushPromises, mount } from '@vue/test-utils' import { afterEach, describe, expect, it } from 'vitest' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' import SmartLoadingDialog from './SmartLoadingDialog.vue' describe('SmartLoadingDialog', () => { @@ -18,6 +20,7 @@ describe('SmartLoadingDialog', () => { await flushPromises() expect(wrapper.find('.smart-loading-dialog__warehouses').exists()).toBe(false) + expect(document.querySelector('.smart-loading-dialog .el-dialog__body')?.classList.contains('el-dialog__body')).toBe(true) const expand = wrapper.get('button[aria-label="展开全屏"]') await expand.trigger('click') expect(document.querySelector('.smart-loading-dialog.is-fullscreen')).not.toBeNull() @@ -27,4 +30,11 @@ describe('SmartLoadingDialog', () => { expect(document.querySelector('.smart-loading-dialog.is-fullscreen')).toBeNull() expect(wrapper.find('button[aria-label="展开全屏"]').exists()).toBe(true) }) + + it('keeps the outer dialog fixed so only workbench data panes scroll', () => { + const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/components/SmartLoadingDialog.vue'), 'utf8') + expect(source).toContain('height: 94vh; max-height: 94vh;') + expect(source).toContain('flex: 1; padding: 0; overflow: hidden;') + expect(source).toContain('min-height: 0; flex: 1; overflow: hidden;') + }) }) diff --git a/src/modules/container-loading/components/SmartLoadingDialog.vue b/src/modules/container-loading/components/SmartLoadingDialog.vue index 62657aca..045685cc 100644 --- a/src/modules/container-loading/components/SmartLoadingDialog.vue +++ b/src/modules/container-loading/components/SmartLoadingDialog.vue @@ -43,16 +43,15 @@ watch(open, (value) => { diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 6db8959d..5ab09bd2 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -101,6 +101,8 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.text()).toContain('IN-2') expect(wrapper.text()).not.toContain('ORD-1') expect(wrapper.text()).not.toContain('ORD-2') + expect(wrapper.get('.candidate-pane').text()).toContain('120 kg') + expect(wrapper.get('.candidate-pane').text()).toContain('2.4 m³') }) it('uses the top-level container channel snapshot without expecting a nested snapshot', async () => { @@ -318,7 +320,7 @@ describe('GlobalSmartLoadingView', () => { const pane = wrapper.get('.candidate-pane') expect(pane.text()).toContain('IN-020') expect(pane.text()).not.toContain('IN-021') - expect(pane.text()).toContain('共 25 条 · 第 1 / 2 页') + expect(pane.text()).toContain('共 25 条 · 1/2 页') await pane.findAll('button').find((button) => button.attributes('aria-label') === '下一页')!.trigger('click') await flushPromises() @@ -671,12 +673,14 @@ describe('GlobalSmartLoadingView', () => { expect(source).toContain('height: clamp(520px, 68vh, 720px); min-height: 0;') expect(source).toContain('.dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; }') expect(source).toContain('.candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { min-height: 0; flex: 1; }') + expect(source).toContain(".candidate-pane :deep(.data-table__page-button[aria-label^='第']),.candidate-pane :deep(.data-table__page-ellipsis) { display: none; }") + expect(source).toContain('.candidate-pane :deep(.data-table__page-size .el-select) { width: 68px; }') expect(source).toContain('.container-list { display: grid; min-height: 0; flex: 1;') expect(source).toContain('grid-auto-rows: max-content;') 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('grid-template-columns: minmax(430px, 1fr) minmax(260px, 280px) minmax(520px, 1.25fr)') expect(source).toContain('min-height: 112px') expect(source).toContain("label: '预计到货时间'") expect(source).toContain("label: '订单状态'") diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index bf1de4a7..ac2a5b26 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -88,7 +88,7 @@ const editable = computed(() => plan.value?.canEdit === true) const assignable = computed(() => plan.value?.canAssign === true) const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null) const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) -type SmartLoadingCandidateRow = SmartLoadingCandidate & { sourceCandidates: SmartLoadingCandidate[]; memberInboundCodes: string[]; memberOrderSummaries: string[]; memberOrderCount: number; inboundFragmentCount: number } +type SmartLoadingCandidateRow = SmartLoadingCandidate & { sourceCandidates: SmartLoadingCandidate[]; memberInboundCodes: string[]; memberOrderSummaries: string[]; memberOrderCount: number; inboundFragmentCount: number; totalGrossWeightKg: number; totalVolumeCbm: number } function memberOrderSummaries(members: T[]) { const orders = new Map() for (const member of members) { @@ -104,12 +104,20 @@ function groupMemberLabel(group: SmartLoadingPlanCoLoadGroup) { ? `${group.memberOrderCount} 票` : `${group.memberOrderCount} 票 · ${group.inboundFragmentCount} 个入仓片段` } +function candidateTotalGrossWeight(candidate: SmartLoadingCandidate) { + return Number(candidate.unitGrossWeightKg || 0) * Number(candidate.availablePackageCount || 0) +} +function candidateTotalVolume(candidate: SmartLoadingCandidate) { + return candidate.availableVolumeCbm == null + ? Number(candidate.averageUnitVolumeCbm || 0) * Number(candidate.availablePackageCount || 0) + : Number(candidate.availableVolumeCbm) +} const displayedCandidates = computed(() => { const rows: SmartLoadingCandidateRow[] = [] const groups = new Map() for (const candidate of candidates.value) { if (!candidate.coLoadGroupId) { - rows.push({ ...candidate, sourceCandidates: [candidate], memberInboundCodes: [candidate.inboundCode || '入仓码暂缺'], memberOrderSummaries: memberOrderSummaries([candidate]), memberOrderCount: 1, inboundFragmentCount: 1 }) + rows.push({ ...candidate, sourceCandidates: [candidate], memberInboundCodes: [candidate.inboundCode || '入仓码暂缺'], memberOrderSummaries: memberOrderSummaries([candidate]), memberOrderCount: 1, inboundFragmentCount: 1, totalGrossWeightKg: candidateTotalGrossWeight(candidate), totalVolumeCbm: candidateTotalVolume(candidate) }) continue } groups.set(candidate.coLoadGroupId, [...(groups.get(candidate.coLoadGroupId) || []), candidate]) @@ -128,6 +136,8 @@ const displayedCandidates = computed(() => { memberOrderSummaries: memberOrderSummaries(members), memberOrderCount: new Set(members.map((member) => member.orderId)).size, inboundFragmentCount: Number(first.memberCount || members.length), + totalGrossWeightKg: members.reduce((sum, member) => sum + candidateTotalGrossWeight(member), 0), + totalVolumeCbm: members.reduce((sum, member) => sum + candidateTotalVolume(member), 0), }) } return rows @@ -241,13 +251,15 @@ function preferredContainerId() { const candidateColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 92, fixed: 'left', slotName: 'actions' }, - { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, - { key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' }, - { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 140 }, - { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 100 }, - { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, - { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 135, slotName: 'unitGrossWeight' }, - { key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 145, slotName: 'averageUnitVolume' }, + { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true }, + { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' }, + { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 }, + { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 }, + // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, + { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 100, slotName: 'unitGrossWeight' }, + { key: 'totalGrossWeightKg', label: '总重量', width: 90, slotName: 'candidateTotalWeight' }, + { key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 100, slotName: 'averageUnitVolume' }, + { key: 'totalVolumeCbm', label: '总体积', width: 90, slotName: 'candidateTotalVolume' }, ] const itemColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' }, @@ -614,6 +626,7 @@ onMounted(() => { void load(); void loadCustomers() }) column-settings-mode="hide" :empty-text="selectedContainer ? '当前仓库暂无可装明细' : '暂无可用柜次'" > + 共 {{ total }} 条 · {{ page }}/{{ pageCount }} 页共 0 条 { void load(); void loadCustomers() }) {{ row.groupNo }} · {{ row.memberOrderCount }} 票 · {{ row.inboundFragmentCount }} 个入仓片段{{ summary }}整组 {{ row.groupTotalBoxes }} 箱,必须全量进入同一柜{{ row.blockReason }}该组已锁定,不能重复分配普通单票 {{ formatNumber(Number(row.unitGrossWeightKg || 0), 1) }} kg + {{ formatNumber(Number(row.totalGrossWeightKg || 0), 1) }} kg {{ formatNumber(Number(row.averageUnitVolumeCbm || 0)) }} m³ + {{ formatNumber(Number(row.totalVolumeCbm || 0)) }} m³ 未核验 @@ -729,8 +744,9 @@ onMounted(() => { void load(); void loadCustomers() })