From efc5cabfbb32bc2ba5c28ad4d470d4b5d8f4db49 Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Mon, 20 Apr 2026 13:50:20 +0800 Subject: [PATCH] update sku mapping --- .../sku-mappings/__tests__/index.spec.ts | 71 ++++++++++++++++++- frontend/src/pages/sku-mappings/index.vue | 53 +++++++++++--- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/sku-mappings/__tests__/index.spec.ts b/frontend/src/pages/sku-mappings/__tests__/index.spec.ts index e58e4cf..46c90a1 100644 --- a/frontend/src/pages/sku-mappings/__tests__/index.spec.ts +++ b/frontend/src/pages/sku-mappings/__tests__/index.spec.ts @@ -30,9 +30,10 @@ import { api } from '@/utils/request' const mockMappings = { items: [ - { id: 1, company_id: 3, platform_id: 1, store_id: null, origin_sku: '0032', origin_sku_id: 1, platform_outer_sku: 'AMZ-0032', platform_product_id: 'ITEM-001', enabled: true, note: null, created_at: '2026-04-01T00:00:00', updated_at: '2026-04-01T00:00:00' }, + { id: 1, company_id: 3, platform_id: 1, store_id: null, origin_sku: '0032', origin_sku_id: 1, platform_outer_sku: 'AMZ-0032', platform_product_id: 'ITEM-001', enabled: true, bundled: true, note: null, created_at: '2026-04-01T00:00:00', updated_at: '2026-04-01T00:00:00' }, + { id: 2, company_id: 3, platform_id: 1, store_id: null, origin_sku: '0033', origin_sku_id: 2, platform_outer_sku: 'AMZ-0033', platform_product_id: 'ITEM-002', enabled: true, bundled: false, note: null, created_at: '2026-04-01T00:00:00', updated_at: '2026-04-01T00:00:00' }, ], - total: 1, + total: 2, page: 1, per_page: 15, } @@ -148,4 +149,70 @@ describe('SkuMappingsPage', () => { expect(buttonTexts.some((t) => t.includes('搜索'))).toBe(true) expect(buttonTexts.some((t) => t.includes('重置'))).toBe(true) }) + + it('P7: renders 组合商品 column with blue/default tags per bundled', async () => { + await mountPage() + + const html = wrapper.html() + expect(html).toContain('组合商品') + const rows = wrapper.findAll('tbody tr') + const bundledRow = rows.find((r) => r.text().includes('AMZ-0032')) + const plainRow = rows.find((r) => r.text().includes('AMZ-0033')) + expect(bundledRow?.html()).toMatch(/ant-tag-blue[^>]*>\s*是/) + expect(plainRow?.html()).toMatch(/ant-tag[^>]*>\s*否/) + expect(plainRow?.html()).not.toMatch(/ant-tag-blue[^>]*>\s*否/) + }) + + it('P8: form contains bundled Switch defaulted to off on create', async () => { + await mountPage() + + const newBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('新建连接')) + await newBtn?.trigger('click') + await flushPromises() + await nextTick() + + const bodyHtml = document.body.innerHTML + expect(bodyHtml).toContain('组合商品') + const switches = document.body.querySelectorAll('.ant-switch') + const bundledSwitch = Array.from(switches).find((s) => { + const label = s.closest('.ant-form-item')?.querySelector('.ant-form-item-label') + return label?.textContent?.includes('组合商品') + }) + expect(bundledSwitch).toBeTruthy() + expect(bundledSwitch?.classList.contains('ant-switch-checked')).toBe(false) + }) + + it('P9: origin_sku_id required validation triggers error on empty submit', async () => { + await mountPage() + + const newBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('新建连接')) + await newBtn?.trigger('click') + await flushPromises() + await nextTick() + + const okBtn = Array.from(document.body.querySelectorAll('.ant-modal-footer .ant-btn')).find( + (b) => { + const t = (b as HTMLElement).textContent ?? '' + return t.includes('确 定') || t.includes('确定') || t.includes('OK') + }, + ) as HTMLElement | undefined + okBtn?.click() + await flushPromises() + await nextTick() + + await vi.waitFor( + () => { + expect(document.body.innerHTML).toContain('请选择内部 SKU') + }, + { timeout: 8000, interval: 100 }, + ) + }, 15000) + + it('P10: filter area exposes bundled dropdown with 是/否 options', async () => { + await mountPage() + + const formItemLabels = wrapper.findAll('.filter-form .ant-form-item-label label') + const bundledLabel = formItemLabels.find((l) => l.text().includes('组合商品')) + expect(bundledLabel).toBeTruthy() + }) }) diff --git a/frontend/src/pages/sku-mappings/index.vue b/frontend/src/pages/sku-mappings/index.vue index f07d7ea..3624b8e 100644 --- a/frontend/src/pages/sku-mappings/index.vue +++ b/frontend/src/pages/sku-mappings/index.vue @@ -26,6 +26,7 @@ const columns = [ { title: '内部 SKU', dataIndex: 'origin_sku', width: 140 }, { title: '平台 SKU', dataIndex: 'platform_outer_sku', width: 160 }, { title: '平台商品ID', dataIndex: 'platform_product_id', width: 160, ellipsis: true }, + { title: '组合商品', key: 'bundled', width: 100 }, { title: '状态', key: 'enabled', width: 80 }, { title: '更新时间', key: 'updated_at', width: 170 }, { title: '操作', key: 'action', width: 200, fixed: 'right' as const }, @@ -49,6 +50,7 @@ const defaultForm = (): SkuMappingForm => ({ generation_strategy: 'prefix', warehouse_id: undefined, enabled: true, + bundled: false, note: '', }) @@ -57,7 +59,9 @@ const form = reactive(defaultForm()) const rules: Record = { company_id: [{ required: true, message: '请选择公司', trigger: 'change' }], platform_id: [{ required: true, message: '请选择平台', trigger: 'change' }], - origin_sku: [{ required: true, message: '请输入内部 SKU', trigger: 'blur' }], + origin_sku_id: [ + { required: true, message: '请选择内部 SKU', trigger: 'change', type: 'number' }, + ], platform_outer_sku: [{ required: true, message: '请输入或生成平台 SKU', trigger: 'blur' }], } @@ -175,6 +179,7 @@ function openEdit(record: SkuMappingForm & { id: number }) { generation_strategy: record.generation_strategy || 'prefix', warehouse_id: record.warehouse_id || undefined, enabled: record.enabled ?? true, + bundled: record.bundled ?? false, note: record.note || '', }) nextTick(() => { skipCompanyWatch = false }) @@ -338,6 +343,18 @@ watch( 禁用 + + + + + + @@ -384,6 +401,11 @@ watch( 平台默认 +