Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test

This commit is contained in:
2026-09-03 14:30:41 +08:00
6 changed files with 66 additions and 23 deletions
@@ -1,6 +1,8 @@
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import { flushPromises, mount } from '@vue/test-utils' import { flushPromises, mount } from '@vue/test-utils'
import { afterEach, describe, expect, it } from 'vitest' import { afterEach, describe, expect, it } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import SmartLoadingDialog from './SmartLoadingDialog.vue' import SmartLoadingDialog from './SmartLoadingDialog.vue'
describe('SmartLoadingDialog', () => { describe('SmartLoadingDialog', () => {
@@ -18,6 +20,7 @@ describe('SmartLoadingDialog', () => {
await flushPromises() await flushPromises()
expect(wrapper.find('.smart-loading-dialog__warehouses').exists()).toBe(false) 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="展开全屏"]') const expand = wrapper.get('button[aria-label="展开全屏"]')
await expand.trigger('click') await expand.trigger('click')
expect(document.querySelector('.smart-loading-dialog.is-fullscreen')).not.toBeNull() 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(document.querySelector('.smart-loading-dialog.is-fullscreen')).toBeNull()
expect(wrapper.find('button[aria-label="展开全屏"]').exists()).toBe(true) 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;')
})
}) })
@@ -43,16 +43,15 @@ watch(open, (value) => {
</template> </template>
<style scoped> <style scoped>
:global(.smart-loading-dialog.app-dialog.el-dialog) { width: min(1600px, 98vw); max-height: 94vh; margin-block: 3vh !important; } :global(.smart-loading-dialog.app-dialog.el-dialog) { width: min(1600px, 98vw); height: 94vh; max-height: 94vh; margin-block: 3vh !important; }
:global(.smart-loading-dialog.app-dialog.el-dialog.is-fullscreen) { width: 100%; height: 100vh; max-height: 100vh; margin: 0 !important; border-radius: 0; } :global(.smart-loading-dialog.app-dialog.el-dialog.is-fullscreen) { width: 100%; height: 100vh; max-height: 100vh; margin: 0 !important; border-radius: 0; }
:global(.smart-loading-dialog .el-dialog__body) { padding: 0 } :global(.smart-loading-dialog .el-dialog__body) { display: flex; min-height: 0; flex: 1; padding: 0; overflow: hidden; }
:global(.smart-loading-dialog .app-dialog__body) { min-width: 0; } :global(.smart-loading-dialog .app-dialog__body) { display: flex; min-width: 0; min-height: 0; flex: 1; overflow: hidden; }
.smart-loading-dialog__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; color: var(--cc-text); font-size: 18px; font-weight: 600; line-height: 24px; } .smart-loading-dialog__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; color: var(--cc-text); font-size: 18px; font-weight: 600; line-height: 24px; }
.smart-loading-dialog__heading { display: flex; min-width: 0; align-items: center; gap: 24px; } .smart-loading-dialog__heading { display: flex; min-width: 0; align-items: center; gap: 24px; }
.smart-loading-dialog__heading > span { flex: 0 0 auto; } .smart-loading-dialog__heading > span { flex: 0 0 auto; }
.smart-loading-dialog__header :deep(.el-button) { flex: 0 0 auto; margin-right: 24px; color: var(--cc-muted); } .smart-loading-dialog__header :deep(.el-button) { flex: 0 0 auto; margin-right: 24px; color: var(--cc-muted); }
@media (max-width: 720px) { @media (max-width: 720px) {
:global(.smart-loading-dialog.app-dialog.el-dialog) { width: calc(100vw - 12px); max-height: calc(100svh - 12px); margin-block: 6px !important; } :global(.smart-loading-dialog.app-dialog.el-dialog) { width: calc(100vw - 12px); height: calc(100svh - 12px); max-height: calc(100svh - 12px); margin-block: 6px !important; }
:global(.smart-loading-dialog .el-dialog__body) { padding: 0 }
} }
</style> </style>
@@ -101,6 +101,8 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).toContain('IN-2') expect(wrapper.text()).toContain('IN-2')
expect(wrapper.text()).not.toContain('ORD-1') expect(wrapper.text()).not.toContain('ORD-1')
expect(wrapper.text()).not.toContain('ORD-2') 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 () => { 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') const pane = wrapper.get('.candidate-pane')
expect(pane.text()).toContain('IN-020') expect(pane.text()).toContain('IN-020')
expect(pane.text()).not.toContain('IN-021') 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 pane.findAll('button').find((button) => button.attributes('aria-label') === '下一页')!.trigger('click')
await flushPromises() await flushPromises()
@@ -671,12 +673,14 @@ describe('GlobalSmartLoadingView', () => {
expect(source).toContain('height: clamp(520px, 68vh, 720px); min-height: 0;') 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('.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),.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('.container-list { display: grid; min-height: 0; flex: 1;')
expect(source).toContain('grid-auto-rows: max-content;') expect(source).toContain('grid-auto-rows: max-content;')
expect(source).toContain('overflow-wrap: anywhere;') expect(source).toContain('overflow-wrap: anywhere;')
expect(source).toContain('.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }') 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('.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('min-height: 112px')
expect(source).toContain("label: '预计到货时间'") expect(source).toContain("label: '预计到货时间'")
expect(source).toContain("label: '订单状态'") expect(source).toContain("label: '订单状态'")
@@ -88,7 +88,7 @@ const editable = computed(() => plan.value?.canEdit === true)
const assignable = computed(() => plan.value?.canAssign === true) const assignable = computed(() => plan.value?.canAssign === true)
const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null) const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null)
const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) 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<T extends { orderId: string; orderNo?: string | null; inboundCode?: string | null }>(members: T[]) { function memberOrderSummaries<T extends { orderId: string; orderNo?: string | null; inboundCode?: string | null }>(members: T[]) {
const orders = new Map<string, { label: string; inboundCodes: string[] }>() const orders = new Map<string, { label: string; inboundCodes: string[] }>()
for (const member of members) { for (const member of members) {
@@ -104,12 +104,20 @@ function groupMemberLabel(group: SmartLoadingPlanCoLoadGroup) {
? `${group.memberOrderCount} 票` ? `${group.memberOrderCount} 票`
: `${group.memberOrderCount} 票 · ${group.inboundFragmentCount} 个入仓片段` : `${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<SmartLoadingCandidateRow[]>(() => { const displayedCandidates = computed<SmartLoadingCandidateRow[]>(() => {
const rows: SmartLoadingCandidateRow[] = [] const rows: SmartLoadingCandidateRow[] = []
const groups = new Map<string, SmartLoadingCandidate[]>() const groups = new Map<string, SmartLoadingCandidate[]>()
for (const candidate of candidates.value) { for (const candidate of candidates.value) {
if (!candidate.coLoadGroupId) { 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 continue
} }
groups.set(candidate.coLoadGroupId, [...(groups.get(candidate.coLoadGroupId) || []), candidate]) groups.set(candidate.coLoadGroupId, [...(groups.get(candidate.coLoadGroupId) || []), candidate])
@@ -128,6 +136,8 @@ const displayedCandidates = computed<SmartLoadingCandidateRow[]>(() => {
memberOrderSummaries: memberOrderSummaries(members), memberOrderSummaries: memberOrderSummaries(members),
memberOrderCount: new Set(members.map((member) => member.orderId)).size, memberOrderCount: new Set(members.map((member) => member.orderId)).size,
inboundFragmentCount: Number(first.memberCount || members.length), 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 return rows
@@ -241,13 +251,15 @@ function preferredContainerId() {
const candidateColumns: DataTableColumn[] = [ const candidateColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 92, fixed: 'left', slotName: 'actions' }, { key: 'actions', label: '操作', width: 92, fixed: 'left', slotName: 'actions' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true },
{ key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' }, { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 140 }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
{ key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 100 }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 },
{ key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' },
{ key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 135, slotName: 'unitGrossWeight' }, { key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 100, slotName: 'unitGrossWeight' },
{ key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 145, slotName: 'averageUnitVolume' }, { key: 'totalGrossWeightKg', label: '总重量', width: 90, slotName: 'candidateTotalWeight' },
{ key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 100, slotName: 'averageUnitVolume' },
{ key: 'totalVolumeCbm', label: '总体积', width: 90, slotName: 'candidateTotalVolume' },
] ]
const itemColumns: DataTableColumn[] = [ const itemColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' }, { key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' },
@@ -614,6 +626,7 @@ onMounted(() => { void load(); void loadCustomers() })
column-settings-mode="hide" column-settings-mode="hide"
:empty-text="selectedContainer ? '当前仓库暂无可装明细' : '暂无可用柜次'" :empty-text="selectedContainer ? '当前仓库暂无可装明细' : '暂无可用柜次'"
> >
<template #pagination-left="{ total, page, pageCount }"><span v-if="pageCount > 0">共 {{ total }} 条 · {{ page }}/{{ pageCount }} 页</span><span v-else>共 0 条</span></template>
<template #actions="{ row }"> <template #actions="{ row }">
<el-button <el-button
v-if="canAssign && assignable" v-if="canAssign && assignable"
@@ -626,7 +639,9 @@ onMounted(() => { void load(); void loadCustomers() })
</template> </template>
<template #coLoadGroup="{ row }"><div v-if="row.coLoadGroupId" class="candidate-group"><el-tag size="small" type="warning">{{ row.groupNo }} · {{ row.memberOrderCount }} 票 · {{ row.inboundFragmentCount }} 个入仓片段</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>整组 {{ row.groupTotalBoxes }} 箱,必须全量进入同一柜</span><small v-if="row.blockReason" class="candidate-block">{{ row.blockReason }}</small><small v-else-if="row.locked">该组已锁定,不能重复分配</small></div><span v-else>普通单票</span></template> <template #coLoadGroup="{ row }"><div v-if="row.coLoadGroupId" class="candidate-group"><el-tag size="small" type="warning">{{ row.groupNo }} · {{ row.memberOrderCount }} 票 · {{ row.inboundFragmentCount }} 个入仓片段</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>整组 {{ row.groupTotalBoxes }} 箱,必须全量进入同一柜</span><small v-if="row.blockReason" class="candidate-block">{{ row.blockReason }}</small><small v-else-if="row.locked">该组已锁定,不能重复分配</small></div><span v-else>普通单票</span></template>
<template #unitGrossWeight="{ row }">{{ formatNumber(Number(row.unitGrossWeightKg || 0), 1) }} kg</template> <template #unitGrossWeight="{ row }">{{ formatNumber(Number(row.unitGrossWeightKg || 0), 1) }} kg</template>
<template #candidateTotalWeight="{ row }">{{ formatNumber(Number(row.totalGrossWeightKg || 0), 1) }} kg</template>
<template #averageUnitVolume="{ row }">{{ formatNumber(Number(row.averageUnitVolumeCbm || 0)) }} m³</template> <template #averageUnitVolume="{ row }">{{ formatNumber(Number(row.averageUnitVolumeCbm || 0)) }} m³</template>
<template #candidateTotalVolume="{ row }">{{ formatNumber(Number(row.totalVolumeCbm || 0)) }} m³</template>
<template #cartonIdentityStatus><el-tag size="small" type="info" effect="plain">未核验</el-tag></template> <template #cartonIdentityStatus><el-tag size="small" type="info" effect="plain">未核验</el-tag></template>
</DataTable> </DataTable>
</section> </section>
@@ -729,8 +744,9 @@ onMounted(() => { void load(); void loadCustomers() })
</template> </template>
<style scoped> <style scoped>
.global-loading-page { display: grid; gap: 12px; min-width: 0; } .global-loading-page { display: flex; min-width: 0; min-height: 0; flex-direction: column; gap: 12px; }
.embedded-smart-loading { min-width: 0; } .embedded-smart-loading { display: flex; width: 100%; min-width: 0; min-height: 0; flex: 1; overflow: hidden; }
.embedded-smart-loading .global-loading-page { width: 100%; min-height: 0; flex: 1; overflow: hidden; }
.channel-summary-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; min-width: 0; align-items: center; border-bottom: 2px solid var(--cc-border); background: #fff; } .channel-summary-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; min-width: 0; align-items: center; border-bottom: 2px solid var(--cc-border); background: #fff; }
.channel-tags { display: flex; min-width: 0; align-items: center; flex-wrap: nowrap; gap: 6px; padding: 6px 0; overflow-x: auto; } .channel-tags { display: flex; min-width: 0; align-items: center; flex-wrap: nowrap; gap: 6px; padding: 6px 0; overflow-x: auto; }
.channel-tags__label { flex: 0 0 auto; margin-right: 2px; color: var(--cc-text); font-size: 13px; font-weight: 600; } .channel-tags__label { flex: 0 0 auto; margin-right: 2px; color: var(--cc-text); font-size: 13px; font-weight: 600; }
@@ -746,14 +762,19 @@ onMounted(() => { void load(); void loadCustomers() })
:deep(.smart-loading-query .query-toolbar__tail) { flex-wrap: nowrap; gap: 8px; } :deep(.smart-loading-query .query-toolbar__tail) { flex-wrap: nowrap; gap: 8px; }
:deep(.smart-loading-query .query-toolbar__actions) { flex-wrap: nowrap; gap: 6px; } :deep(.smart-loading-query .query-toolbar__actions) { flex-wrap: nowrap; gap: 6px; }
.pane-header small,.container-option span,.global-loading-page small { color: var(--cc-muted); font-size: 12px; } .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) 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; } .dispatch-grid { display: grid; grid-template-columns: minmax(430px, 1fr) minmax(260px, 280px) minmax(520px, 1.25fr); 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); } .embedded-smart-loading .dispatch-grid { height: auto; min-height: 0; flex: 1; }
.dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; } .dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; }
.dispatch-pane + .dispatch-pane { border-left: 1px solid var(--cc-border); } .dispatch-pane + .dispatch-pane { border-left: 1px solid var(--cc-border); }
.pane-header { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-bottom: 1px solid var(--cc-border); background: #fff; } .pane-header { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-bottom: 1px solid var(--cc-border); background: #fff; }
.pane-header > div:first-child { display: grid; min-width: 0; gap: 3px; } .pane-header > div:first-child { display: grid; min-width: 0; gap: 3px; }
.candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { min-height: 0; flex: 1; } .candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { min-height: 0; flex: 1; }
.candidate-pane :deep(.el-table),.plan-pane :deep(.el-table) { height: 100%; } .candidate-pane :deep(.el-table),.plan-pane :deep(.el-table) { height: 100%; }
.candidate-pane :deep(.data-table__pagination) { min-height: 46px; flex-wrap: nowrap; gap: 8px; padding: 7px 8px; }
.candidate-pane :deep(.data-table__pagination-left) { flex: 0 0 auto; white-space: nowrap; }
.candidate-pane :deep(.data-table__pagination-controls),.candidate-pane :deep(.data-table__page-size),.candidate-pane :deep(.data-table__page-buttons) { flex-wrap: nowrap; gap: 4px; white-space: nowrap; }
.candidate-pane :deep(.data-table__page-button[aria-label^='第']),.candidate-pane :deep(.data-table__page-ellipsis) { display: none; }
.candidate-pane :deep(.data-table__page-size .el-select) { width: 68px; }
.container-pane { background: #fafafa; } .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-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: 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 { 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; }
@@ -785,6 +806,7 @@ onMounted(() => { void load(); void loadCustomers() })
.embedded-smart-loading .dispatch-grid { height: auto; } .embedded-smart-loading .dispatch-grid { height: auto; }
.dispatch-pane + .dispatch-pane { border-top: 1px solid var(--cc-border); border-left: 0; } .dispatch-pane + .dispatch-pane { border-top: 1px solid var(--cc-border); border-left: 0; }
.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; } .candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }
.candidate-pane :deep(.data-table__pagination) { flex-wrap: wrap; }
.container-pane { max-height: min(58vh, 460px); } .container-pane { max-height: min(58vh, 460px); }
.container-list { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); max-height: none; } .container-list { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); max-height: none; }
} }
@@ -17,7 +17,7 @@ const detail = {
}, },
order: { order: {
order: { version: 7, receivingWarehouseId: '700000000000000001', channelCode: 'OLD', channelName: '原渠道', customerRemark: '原备注' }, order: { version: 7, receivingWarehouseId: '700000000000000001', channelCode: 'OLD', channelName: '原渠道', customerRemark: '原备注' },
cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', usage: '照明' }], cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', productUsage: '照明' }],
parties: [], internalNotes: [], statusHistory: [], parties: [], internalNotes: [], statusHistory: [],
}, },
timeline: [], timeline: [],
@@ -68,7 +68,13 @@ describe('OrderDeclarationEditorDialog', () => {
expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('原箱码继续有效,无需更换已打印箱唛'), '确认变更渠道', expect.any(Object)) expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('原箱码继续有效,无需更换已打印箱唛'), '确认变更渠道', expect.any(Object))
expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('纸面上的旧渠道文字不会自动变化'), '确认变更渠道', expect.any(Object)) expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('纸面上的旧渠道文字不会自动变化'), '确认变更渠道', expect.any(Object))
expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({ expectedVersion: 7, estimatedVolumeCbm: 0.625, channelCode: 'NEW', channelName: '新渠道' })) expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({
expectedVersion: 7,
estimatedVolumeCbm: 0.625,
channelCode: 'NEW',
channelName: '新渠道',
cargoes: [expect.objectContaining({ itemNo: 1, usage: '照明' })],
}))
expect(ElMessage.success).toHaveBeenCalledWith(expect.stringContaining('订单已标记为“曾变更渠道”')) expect(ElMessage.success).toHaveBeenCalledWith(expect.stringContaining('订单已标记为“曾变更渠道”'))
expect(wrapper.emitted('updated')).toHaveLength(1) expect(wrapper.emitted('updated')).toHaveLength(1)
expect(wrapper.emitted('update:modelValue')).toContainEqual([false]) expect(wrapper.emitted('update:modelValue')).toContainEqual([false])
@@ -83,11 +89,13 @@ describe('OrderDeclarationEditorDialog', () => {
expect(wrapper.find('.cargo-table').exists()).toBe(true) expect(wrapper.find('.cargo-table').exists()).toBe(true)
expect(wrapper.text()).toContain('箱数 / 数量') expect(wrapper.text()).toContain('箱数 / 数量')
expect(wrapper.text()).toContain('材质 / 用途') expect(wrapper.text()).toContain('材质 / 用途')
expect(wrapper.text()).toContain('铝 / 照明')
expect(wrapper.text()).toContain('2 箱 / 20') expect(wrapper.text()).toContain('2 箱 / 20')
const editButton = wrapper.findAll('button').find((button) => button.text() === '编辑')! const editButton = wrapper.findAll('button').find((button) => button.text() === '编辑')!
await editButton.trigger('click') await editButton.trigger('click')
expect(wrapper.find('input[aria-label="币种"]').attributes('readonly')).toBeDefined() expect(wrapper.find('input[aria-label="币种"]').attributes('readonly')).toBeDefined()
expect(wrapper.findAllComponents({ name: 'ElInput' }).some((input) => input.props('modelValue') === '照明')).toBe(true)
}) })
it('preserves the editor when the versioned update fails', async () => { it('preserves the editor when the versioned update fails', async () => {
@@ -88,7 +88,7 @@ function resetForm() {
hsCode: text(row, 'hsCode'), customsHsCode: text(row, 'customsHsCode'), hsCode: text(row, 'hsCode'), customsHsCode: text(row, 'customsHsCode'),
unitsPerPackage: optionalNumber(row, 'unitsPerPackage'), unitsPerPackage: optionalNumber(row, 'unitsPerPackage'),
unitDeclaredValue: optionalNumber(row, 'unitDeclaredValue'), declaredValue: optionalNumber(row, 'declaredValue'), unitDeclaredValue: optionalNumber(row, 'unitDeclaredValue'), declaredValue: optionalNumber(row, 'declaredValue'),
currency: text(row, 'currency'), material: text(row, 'material'), usage: text(row, 'usage'), currency: text(row, 'currency'), material: text(row, 'material'), usage: text(row, 'productUsage'),
brand: text(row, 'brand'), cargoRemark: text(row, 'cargoRemark') || text(row, 'remark'), brand: text(row, 'brand'), cargoRemark: text(row, 'cargoRemark') || text(row, 'remark'),
} }
}) })