From 5d2465d1eadcf478899dbf4721af4cae55b00de5 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 15 Sep 2026 15:24:59 +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 --- .../pages/OutboundsView.selection.test.ts | 123 ++++++++++++++++++ .../pages/OutboundsView.vue | 47 +++++-- .../pages/UnifiedOrdersView.test.ts | 13 ++ .../pages/UnifiedOrdersView.vue | 47 ++++++- 4 files changed, 215 insertions(+), 15 deletions(-) create mode 100644 src/modules/overseas-warehouse/pages/OutboundsView.selection.test.ts diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.selection.test.ts b/src/modules/overseas-warehouse/pages/OutboundsView.selection.test.ts new file mode 100644 index 00000000..2bce547d --- /dev/null +++ b/src/modules/overseas-warehouse/pages/OutboundsView.selection.test.ts @@ -0,0 +1,123 @@ +import ElementPlus, { ElCheckbox, ElInput, ElPagination, ElSelect } from 'element-plus' +import { flushPromises, mount, type VueWrapper } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import OutboundsView from './OutboundsView.vue' + +const api = vi.hoisted(() => ({ candidates: vi.fn(), create: vi.fn(), warehouses: vi.fn() })) +vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) +vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) })) +vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [] }) })) +vi.mock('@/modules/warehouses', () => ({ listPlatformWarehouses: api.warehouses })) +vi.mock('../api/outbounds', () => ({ + listOutbounds: vi.fn().mockResolvedValue({ records: [], total: 0 }), + listOneClickOutboundCandidates: api.candidates, + createOneClickDropshipOutbound: api.create, + cancelOutbound: vi.fn(), createReturnOutbound: vi.fn(), listReturnOutboundCandidates: vi.fn(), +})) + +const fixtures = Array.from({ length: 7 }, (_, index) => ({ + inventoryPositionId: `position-${index}`, orderNo: `ORDER-${index}`, customerId: 'customer-a', + customerName: '客户甲', overseasWarehouseId: 'warehouse-a', palletNo: `PALLET-${index}`, + inboundCode: `IN-${index}`, skuCode: index < 6 ? 'MATCH' : 'OTHER', productName: '测试品名', availablePackages: index + 1, +})) +let wrapper: VueWrapper +async function clickButton(text: string) { + const button = wrapper.findAll('button').find(item => item.text().includes(text)) + expect(button, text).toBeDefined() + await button!.trigger('click') + await flushPromises() +} +async function openCreate() { + wrapper = mount(OutboundsView, { global: { plugins: [ElementPlus], stubs: { + AppDialog: { props: ['modelValue'], emits: ['confirm'], template: '
' }, + } } }) + await flushPromises() + await clickButton('新建出库单') +} +function candidatePagination() { return wrapper.findAllComponents(ElPagination).find(item => item.props('total') === 7)! } +function pageCheckbox() { return wrapper.findAllComponents(ElCheckbox).find(item => item.props('ariaLabel') === '全选本页订单')! } + +describe('outbound candidate search and selection', () => { + beforeEach(() => { + vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} }) + vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => undefined, removeItem: () => undefined }) + api.candidates.mockReset().mockResolvedValue(fixtures.map(item => ({ ...item }))) + api.create.mockReset().mockResolvedValue({}) + api.warehouses.mockReset().mockResolvedValue({ records: [ + { id: 'warehouse-a', warehouseName: '洛杉矶仓', warehouseCode: 'US-LAX-01', warehouseType: 'OVERSEAS', status: 'ACTIVE' }, + { id: 'warehouse-b', warehouseName: '纽约仓', warehouseCode: 'US-NY-01', warehouseType: 'OVERSEAS', status: 'ACTIVE' }, + ] }) + }) + afterEach(() => { wrapper?.unmount(); vi.unstubAllGlobals() }) + + it('selects and deselects the current page while preserving other pages, then submits all selected quantities', async () => { + await openCreate() + pageCheckbox().vm.$emit('change', true) + await flushPromises() + expect(wrapper.text()).toContain('已选择 5 条') + candidatePagination().vm.$emit('update:current-page', 2) + await flushPromises() + expect(pageCheckbox().props('modelValue')).toBe(false) + pageCheckbox().vm.$emit('change', true) + await flushPromises() + expect(wrapper.text()).toContain('已选择 7 条') + pageCheckbox().vm.$emit('change', false) + await flushPromises() + expect(wrapper.text()).toContain('已选择 5 条') + await wrapper.get('[data-test="submit-outbound"]').trigger('click') + await flushPromises() + expect(api.create).toHaveBeenCalledWith('customer-a', expect.objectContaining({ + items: fixtures.slice(0, 5).map(item => ({ inventoryPositionId: item.inventoryPositionId, packages: item.availablePackages, sku: item.skuCode, cartonMarks: [] })), + })) + }) + + it('searches on query, selects every filtered page, shows partial page selection and clears the selection', async () => { + await openCreate() + const query = wrapper.findAllComponents(ElInput).find(item => item.attributes('aria-label') === '搜索可出库订单')! + query.vm.$emit('update:modelValue', 'MATCH') + await flushPromises() + expect(wrapper.text()).toContain('全选筛选结果(7)') + await query.trigger('keyup', { key: 'Enter' }) + await flushPromises() + expect(wrapper.text()).toContain('全选筛选结果(6)') + await clickButton('全选筛选结果') + expect(wrapper.text()).toContain('已选择 6 条') + await clickButton('重置') + candidatePagination().vm.$emit('update:current-page', 2) + await flushPromises() + expect(pageCheckbox().props('indeterminate')).toBe(true) + await clickButton('清空选择') + expect(wrapper.text()).toContain('已选择 0 条') + }) + + it('resets the page after page-size changes and clears selection when changing searchable warehouses', async () => { + await openCreate() + await clickButton('全选筛选结果') + const pagination = candidatePagination() + pagination.vm.$emit('update:current-page', 2) + await flushPromises() + pagination.vm.$emit('update:page-size', 20) + pagination.vm.$emit('size-change', 20) + await flushPromises() + expect(pagination.props('currentPage')).toBe(1) + expect(wrapper.text()).toContain('第 1 / 1 页') + expect(wrapper.text()).toContain('已选择 7 条') + const warehouse = wrapper.findAllComponents(ElSelect).find(item => item.attributes('aria-label') === '搜索海外仓')! + expect(warehouse.props('filterable')).toBe(true) + warehouse.vm.$emit('update:modelValue', 'warehouse-b') + warehouse.vm.$emit('change', 'warehouse-b') + await flushPromises() + expect(api.candidates).toHaveBeenLastCalledWith(undefined, { warehouseId: 'warehouse-b' }) + expect(wrapper.text()).toContain('已选择 0 条') + }) + + it('warns about a multi-customer bulk selection and preserves the submit restriction', async () => { + api.candidates.mockResolvedValue([{ ...fixtures[0] }, { ...fixtures[1], customerId: 'customer-b', customerName: '客户乙' }]) + await openCreate() + await clickButton('全选筛选结果') + expect(wrapper.text()).toContain('当前选择包含多个客户') + await wrapper.get('[data-test="submit-outbound"]').trigger('click') + await flushPromises() + expect(api.create).not.toHaveBeenCalled() + }) +}) diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.vue b/src/modules/overseas-warehouse/pages/OutboundsView.vue index 347bf304..ee0fdc96 100644 --- a/src/modules/overseas-warehouse/pages/OutboundsView.vue +++ b/src/modules/overseas-warehouse/pages/OutboundsView.vue @@ -1,7 +1,7 @@