From bd6d54ac33dda30f35204e78e8721b023600b340 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 14 Sep 2026 20:21:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OrderReferenceText.vue | 3 +- .../components/OverseasDetailShell.vue | 30 +++++----- .../layoutAcceptance.test.ts | 2 +- .../pages/DispatchesView.test.ts | 19 +++++- .../pages/DispatchesView.vue | 5 +- .../InventoryCustomerName.behavior.test.ts | 6 +- .../pages/LastMileOrderDetailView.test.ts | 58 +++++++++++++++++++ .../pages/LastMileOrderDetailView.vue | 38 ++++++++---- .../pages/LastMileOrdersView.behavior.test.ts | 16 ++++- .../pages/LastMileOrdersView.vue | 11 +++- .../pages/prototypeContract.test.ts | 4 +- .../pages/UnifiedOrdersView.test.ts | 2 +- .../pages/UnifiedOrdersView.vue | 20 +++---- src/shared/components/StatusBadge.vue | 2 +- src/styles/adminDensity.css | 37 ++++++++---- src/styles/adminDensity.test.ts | 17 ++++++ 16 files changed, 208 insertions(+), 62 deletions(-) create mode 100644 src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts diff --git a/src/modules/overseas-warehouse/components/OrderReferenceText.vue b/src/modules/overseas-warehouse/components/OrderReferenceText.vue index fbf99f24..56dc2689 100644 --- a/src/modules/overseas-warehouse/components/OrderReferenceText.vue +++ b/src/modules/overseas-warehouse/components/OrderReferenceText.vue @@ -6,6 +6,7 @@ const props = withDefaults(defineProps<{ value?: string | null emptyText?: string copyable?: boolean + compact?: boolean copyLabel?: string }>(), { value: null, emptyText: '—', copyable: false, copyLabel: '尾程单号' }) @@ -18,7 +19,7 @@ const fullTitle = computed(() => displayValue.value === props.emptyText ? undefi diff --git a/src/modules/overseas-warehouse/layoutAcceptance.test.ts b/src/modules/overseas-warehouse/layoutAcceptance.test.ts index e0fbe683..0d2d0ea5 100644 --- a/src/modules/overseas-warehouse/layoutAcceptance.test.ts +++ b/src/modules/overseas-warehouse/layoutAcceptance.test.ts @@ -8,7 +8,7 @@ const component = (name: string) => readFileSync(resolve(process.cwd(), 'src/mod describe('overseas warehouse 1920px layout acceptance', () => { it('keeps action columns first and wide enough for every visible action', () => { const widths: Record = { - 'LastMileOrdersView.vue': 230, + 'LastMileOrdersView.vue': 330, 'InboundOrdersView.vue': 300, 'DispatchesView.vue': 380, 'OutboundsView.vue': 180, diff --git a/src/modules/overseas-warehouse/pages/DispatchesView.test.ts b/src/modules/overseas-warehouse/pages/DispatchesView.test.ts index 31040086..d751fd99 100644 --- a/src/modules/overseas-warehouse/pages/DispatchesView.test.ts +++ b/src/modules/overseas-warehouse/pages/DispatchesView.test.ts @@ -7,7 +7,7 @@ import QueryToolbar, { type QueryModel } from '@/shared/components/QueryToolbar. import { cancelDispatch, getDispatchBolPdf, listDispatches, removeDispatchItem, transferDispatchToDelivery, updateDispatch } from '../api/dispatches' import DispatchesView from './DispatchesView.vue' -const canPermission = vi.hoisted(() => vi.fn(() => true)) +const canPermission = vi.hoisted(() => vi.fn<(permissionCode: string) => boolean>(() => true)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) })) vi.mock('../api/dispatches', () => ({ listDispatches: vi.fn(), removeDispatchItem: vi.fn(), getDispatchBolPdf: vi.fn(), updateDispatch: vi.fn(), cancelDispatch: vi.fn(), transferDispatchToDelivery: vi.fn() })) @@ -54,6 +54,23 @@ describe('DispatchesView', () => { canPermission.mockReset().mockReturnValue(true) }) + + it.each([ + [[], ['查看', '打印BOL'], false], + [['edit'], ['查看', '打印BOL', '编辑'], false], + [['cancel'], ['查看', '打印BOL', '取消'], false], + [['transfer-delivery'], ['查看', '打印BOL', '转派送'], false], + [['edit', 'cancel'], ['查看', '打印BOL', '编辑', '更多'], true], + [['edit', 'cancel', 'transfer-delivery'], ['查看', '打印BOL', '编辑', '更多'], true], + ])('uses the row action threshold for permissions %j', async (allowed, labels, overflow) => { + canPermission.mockImplementation(code => allowed.some(action => code === 'platform:overseas-warehouse:dispatch:' + action)) + const wrapper = mount(DispatchesView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + const actions = wrapper.get('.el-table__body-wrapper .density-actions') + expect(actions.findAll('button').map(button => button.text().trim())).toEqual(labels) + expect(actions.find('.el-dropdown').exists()).toBe(overflow) + }) + it('loads immediately and sends only supported page filters', async () => { const wrapper = mount(DispatchesView, { global: { plugins: [ElementPlus] } }) await flushPromises() diff --git a/src/modules/overseas-warehouse/pages/DispatchesView.vue b/src/modules/overseas-warehouse/pages/DispatchesView.vue index c5f562ec..41f65e7b 100644 --- a/src/modules/overseas-warehouse/pages/DispatchesView.vue +++ b/src/modules/overseas-warehouse/pages/DispatchesView.vue @@ -341,6 +341,9 @@ async function openBol(row: DispatchListItem) { bolLoadingNo.value = '' } } +function rowActionCount(row: DispatchListItem) { + return 2 + Number(canEdit(row)) + Number(canTransfer(row)) + Number(canCancel(row)) +} function isPendingDeparture(row: DispatchListItem) { return row.statusCode === 'PENDING_DEPARTURE' } @@ -557,7 +560,7 @@ onMounted(load)
- +
diff --git a/src/modules/overseas-warehouse/pages/InventoryCustomerName.behavior.test.ts b/src/modules/overseas-warehouse/pages/InventoryCustomerName.behavior.test.ts index 8cfe45f6..0af191b4 100644 --- a/src/modules/overseas-warehouse/pages/InventoryCustomerName.behavior.test.ts +++ b/src/modules/overseas-warehouse/pages/InventoryCustomerName.behavior.test.ts @@ -64,7 +64,7 @@ describe('inventory customer-name snapshots', () => { await flushPromises() expect(wrapper.text()).toContain('华远贸易') expect(wrapper.text()).toContain('洛杉矶仓') - expect(wrapper.find('.overseas-detail-title p').text()).toBe(`入库单号 / 入仓码 / SKU ${balance.inboundOrderNo} / ${balance.inboundCode} / ${balance.skuCode}`) + expect(wrapper.find('.density-detail-title').text()).toBe(`${balance.inboundOrderNo} / ${balance.inboundCode} / ${balance.skuCode}`) expect(wrapper.text()).toContain('美本土散板') expect(wrapper.text()).not.toContain('美本土散版') expect(wrapper.text()).not.toContain('ONT8目的仓') @@ -78,7 +78,7 @@ describe('inventory customer-name snapshots', () => { routing.route.query = { inboundCode: balance.inboundCode } const inboundCodeWrapper = mount(InventoryDetailView, { attachTo: document.body, global: { plugins: [createPinia(), ElementPlus] } }) await flushPromises() - expect(inboundCodeWrapper.find('.overseas-detail-title p').text()).toBe(`入仓码 ${balance.inboundCode}`) + expect(inboundCodeWrapper.find('.density-detail-title').text()).toBe(`${balance.inboundCode}`) expect(inboundCodeWrapper.text()).not.toContain(balance.id) inboundCodeWrapper.unmount() @@ -86,7 +86,7 @@ describe('inventory customer-name snapshots', () => { routing.route.query = {} const emptyWrapper = mount(InventoryDetailView, { attachTo: document.body, global: { plugins: [createPinia(), ElementPlus] } }) await flushPromises() - expect(emptyWrapper.find('.overseas-detail-title p').text()).toBe('业务标识 —') + expect(emptyWrapper.find('.density-detail-title').text()).toBe('—') expect(emptyWrapper.text()).not.toContain(balance.id) }) }) diff --git a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts new file mode 100644 index 00000000..ff88879f --- /dev/null +++ b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts @@ -0,0 +1,58 @@ +import ElementPlus from 'element-plus' +import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { getLastMileOrder } from '../api/lastMileOrders' +import LastMileOrderDetailView from './LastMileOrderDetailView.vue' + +const push = vi.hoisted(() => vi.fn()) +vi.mock('vue-router', () => ({ useRoute: () => ({ params: { orderId: 'internal-order-id' } }), useRouter: () => ({ push }) })) +vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => false }) })) +vi.mock('../api/lastMileOrders', () => ({ getLastMileOrder: vi.fn(), createLastMileOrderFee: vi.fn() })) +enableAutoUnmount(afterEach) + +describe('LastMileOrderDetailView domestic detail alignment', () => { + beforeEach(() => { + push.mockReset() + vi.mocked(getLastMileOrder).mockResolvedValue({ + order: { id: 'internal-order-id', customerId: 'customer-1', orderNo: 'FL-WH-001', currentStatus: 'PENDING_ASSIGNMENT', customerName: '示例客户', overseasWarehouseName: '洛杉矶仓', packageCount: 0, palletCount: 2, volumeCbm: 6.374, inboundCode: 'IN-001', containerNo: 'CONT-001', customerRemark: '完整保留客户备注', updatedAt: '2026-09-14T10:00:00' }, + cargo: [{ id: 'cargo-1', skuCode: 'SKU-001', productNameZh: '测试货物' }], fees: [], statusHistory: [], operationHistory: [], + }) + }) + + it('shows one business heading, a semantic status and summary without losing basic fields', async () => { + const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + expect(wrapper.findAll('h1')).toHaveLength(1) + expect(wrapper.get('h1').text()).toBe('FL-WH-001') + const header = wrapper.get('.density-detail-header') + expect(header.text()).not.toContain('尾程订单详情') + expect(header.text()).not.toContain('尾程单号') + expect(header.get('.status-badge').classes()).toContain('is-warning') + expect(header.get('.order-reference-text__copy').classes()).toContain('is-text') + const summary = wrapper.get('[aria-label="尾程订单概览"]') + expect(summary.findAll('dd').map(item => item.text())).toEqual(['示例客户', '洛杉矶仓', '0 箱', '2 托', '6.374 m³', expect.stringContaining('2026-09-14')]) + const fields = wrapper.get('dl[aria-label="基本信息"]') + expect(fields.findAll('dt')).toHaveLength(19) + expect(fields.text()).toContain('CONT-001') + expect(fields.text()).toContain('完整保留客户备注') + expect(fields.findAll('.wide')).toHaveLength(2) + expect(wrapper.text()).not.toContain('internal-order-id') + await header.get('.density-back').trigger('click') + expect(push).toHaveBeenCalledWith({ name: 'overseas-last-mile-orders' }) + }) + + it('keeps all five tab panels reachable after applying the shared tab style', async () => { + const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + const tabs = wrapper.findAll('[role="tab"]') + expect(tabs.map(tab => tab.text())).toEqual(['📋基本信息', '📦货物信息', '🗓️计划信息', '💰费用明细', '📝操作记录']) + for (const [index, tab] of tabs.entries()) { + await tab.trigger('click') + await flushPromises() + expect(tab.attributes('aria-selected')).toBe('true') + const panel = wrapper.findAll('[role="tabpanel"]')[index]! + expect(panel.isVisible()).toBe(true) + if (index === 1) expect(panel.text()).toContain('SKU-001') + } + }) +}) diff --git a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue index fb668a88..f9baa238 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue @@ -18,7 +18,7 @@ import { overseasWarehousePermissions as permission } from '../permissions' import OverseasDetailShell from '../components/OverseasDetailShell.vue' import OrderReferenceText from '../components/OrderReferenceText.vue' -interface DetailField { label: string; value: string | number } +interface DetailField { label: string; value: string | number; wide?: boolean } interface CargoRow { id: string; sku: string; productName: string; pallets: number; cartons: number; quantity: number; weight: number; volume: number } interface DocumentRow { type: string; number: string; createdAt: string; quote: string } interface FeeRow { id: string; item: string; currency: string; amount: string; status: string; description: string } @@ -65,8 +65,8 @@ const orderFields = computed(() => { { label: '目的仓', value: text(order?.fbaWarehouseCode || order?.transferWarehouseName) }, { label: '是否暂扣', value: yesNo(order?.detained) }, { label: '是否异常', value: yesNo(order?.abnormal) }, - { label: '异常原因', value: order?.abnormal ? text(order.subOrderStatus) : '—' }, - { label: '备注(客户)', value: text(order?.customerRemark) }, + { label: '异常原因', wide: true, value: order?.abnormal ? text(order.subOrderStatus) : '—' }, + { label: '备注(客户)', wide: true, value: text(order?.customerRemark) }, ] }) const containerFields = computed(() => { @@ -83,6 +83,18 @@ const containerFields = computed(() => { { label: '货柜节点', value: containerNodeLabel(order?.containerNode) }, ] }) +const basicFields = computed(() => [...orderFields.value, ...containerFields.value]) +const overviewFields = computed(() => { + const order = detail.value?.order + return [ + { label: '客户', value: text(order?.customerName) }, + { label: '海外仓', value: text(order?.overseasWarehouseName) }, + { label: '总箱数', value: order?.packageCount == null ? '—' : order.packageCount + ' 箱' }, + { label: '总托数', value: order?.palletCount == null ? '—' : order.palletCount + ' 托' }, + { label: '体积', value: order?.volumeCbm == null ? '—' : order.volumeCbm + ' m³' }, + { label: '最近更新', value: formatOverseasDateTime(order?.updatedAt) }, + ] +}) const planFields = computed(() => { const order = detail.value?.order return [ @@ -171,16 +183,20 @@ onMounted(load)