From 417ac5e1f9bf0a04b9a065c000408d6d4147f430 Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Tue, 14 Apr 2026 15:45:29 +0800 Subject: [PATCH] update sku mapping --- .../Api/V1/SkuMappingController.php | 8 +- ...0000_make_platform_product_id_nullable.php | 43 +++ .../Sku/SkuMappingControllerTest.php | 273 ++++++++++++++++++ .../Sku/SkuOriginControllerTest.php | 200 +++++++++++++ .../sku-mappings/__tests__/index.spec.ts | 151 ++++++++++ frontend/src/pages/sku-mappings/index.vue | 178 +++++++----- .../pages/sku-origins/__tests__/index.spec.ts | 159 ++++++++++ frontend/src/pages/sku-origins/index.vue | 192 +++++++++++- .../src/stores/__tests__/sku-origin.spec.ts | 252 ++++++++++++++++ frontend/src/stores/sku-mapping.ts | 10 + frontend/src/stores/sku-origin.ts | 63 +++- 11 files changed, 1454 insertions(+), 75 deletions(-) create mode 100644 backend/migrations/2026_04_14_120000_make_platform_product_id_nullable.php create mode 100644 backend/test/Cases/Integration/Sku/SkuMappingControllerTest.php create mode 100644 backend/test/Cases/Integration/Sku/SkuOriginControllerTest.php create mode 100644 frontend/src/pages/sku-mappings/__tests__/index.spec.ts create mode 100644 frontend/src/pages/sku-origins/__tests__/index.spec.ts create mode 100644 frontend/src/stores/__tests__/sku-origin.spec.ts diff --git a/backend/app/Controller/Api/V1/SkuMappingController.php b/backend/app/Controller/Api/V1/SkuMappingController.php index ce99fd4..193a24a 100644 --- a/backend/app/Controller/Api/V1/SkuMappingController.php +++ b/backend/app/Controller/Api/V1/SkuMappingController.php @@ -48,6 +48,7 @@ class SkuMappingController extends AbstractController new OA\Parameter(name: 'store_id', in: 'query', required: false, description: '店铺 ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'origin_sku', in: 'query', required: false, description: '原始 SKU 模糊搜索', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'platform_outer_sku', in: 'query', required: false, description: '平台侧 SKU 模糊搜索', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'origin_sku_id', in: 'query', required: false, description: '内部 SKU ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'enabled', in: 'query', required: false, description: '启用状态', schema: new OA\Schema(type: 'boolean')), ], responses: [ @@ -85,6 +86,7 @@ class SkuMappingController extends AbstractController 'company_id' => 'exact', 'platform_id' => 'exact', 'store_id' => 'exact', + 'origin_sku_id' => 'exact', ]; foreach ($filters as $field => $type) { @@ -335,7 +337,7 @@ class SkuMappingController extends AbstractController requestBody: new OA\RequestBody( required: true, content: new OA\JsonContent( - required: ['company_id', 'platform_id', 'origin_sku', 'platform_product_id'], + required: ['company_id', 'platform_id', 'origin_sku'], properties: [ new OA\Property(property: 'company_id', type: 'integer', example: 3), new OA\Property(property: 'platform_id', type: 'integer', example: 1), @@ -369,7 +371,7 @@ class SkuMappingController extends AbstractController { $data = $this->request->all(); - $required_fields = ['company_id', 'platform_id', 'origin_sku', 'platform_product_id']; + $required_fields = ['company_id', 'platform_id', 'origin_sku']; foreach ($required_fields as $field) { if (!isset($data[$field]) || $data[$field] === '') { return $this->response->json([ @@ -385,7 +387,7 @@ class SkuMappingController extends AbstractController 'store_id' => isset($data['store_id']) ? (int) $data['store_id'] : null, 'origin_sku' => $data['origin_sku'], 'origin_sku_id' => isset($data['origin_sku_id']) ? (int) $data['origin_sku_id'] : null, - 'platform_product_id' => $data['platform_product_id'], + 'platform_product_id' => $data['platform_product_id'] ?? null, 'platform_outer_sku' => $data['platform_outer_sku'] ?? null, 'generation_strategy' => $data['generation_strategy'] ?? null, 'warehouse_id' => isset($data['warehouse_id']) ? (int) $data['warehouse_id'] : null, diff --git a/backend/migrations/2026_04_14_120000_make_platform_product_id_nullable.php b/backend/migrations/2026_04_14_120000_make_platform_product_id_nullable.php new file mode 100644 index 0000000..423c6b0 --- /dev/null +++ b/backend/migrations/2026_04_14_120000_make_platform_product_id_nullable.php @@ -0,0 +1,43 @@ +text('platform_product_id')->nullable()->change(); + }); + + // 删除原有唯一约束(如果存在)并创建部分唯一索引 + $connection = Schema::getConnection(); + $connection->statement('DROP INDEX IF EXISTS uk_platform_product'); + $connection->statement( + 'CREATE UNIQUE INDEX uk_platform_product ON skus_mapping (platform_id, platform_product_id) WHERE platform_product_id IS NOT NULL' + ); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $connection = Schema::getConnection(); + $connection->statement('DROP INDEX IF EXISTS uk_platform_product'); + $connection->statement( + 'CREATE UNIQUE INDEX uk_platform_product ON skus_mapping (platform_id, platform_product_id)' + ); + + Schema::table('skus_mapping', function (Blueprint $table) { + $table->text('platform_product_id')->nullable(false)->change(); + }); + } +}; diff --git a/backend/test/Cases/Integration/Sku/SkuMappingControllerTest.php b/backend/test/Cases/Integration/Sku/SkuMappingControllerTest.php new file mode 100644 index 0000000..a4922ca --- /dev/null +++ b/backend/test/Cases/Integration/Sku/SkuMappingControllerTest.php @@ -0,0 +1,273 @@ +runInCoroutine(static function (): bool { + return SkuMapping::query()->exists(); + }); + } + + protected function getFirstRecord(): ?array + { + return $this->runInCoroutine(static function (): ?array { + $record = SkuMapping::query()->first(); + return $record ? $record->toArray() : null; + }); + } + + protected function getFirstOrigin(): ?array + { + return $this->runInCoroutine(static function (): ?array { + $record = SkuOrigin::query()->first(); + return $record ? $record->toArray() : null; + }); + } + + // ========== 列表接口 ========== + + public function test_list_returns_paginated_data(): void + { + $response = $this->get('/api/v1/sku-mappings', [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonStructure([ + 'code', + 'message', + 'data' => [ + 'items', + 'total', + 'page', + 'per_page', + ], + ]); + } + + public function test_list_filter_by_origin_sku_id(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Mapping 数据'); + } + + $first = $this->getFirstRecord(); + if (!$first['origin_sku_id']) { + $this->markTestSkipped('首条记录无 origin_sku_id'); + } + + $response = $this->get('/api/v1/sku-mappings', [ + 'origin_sku_id' => $first['origin_sku_id'], + ], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($first['origin_sku_id'], $item['origin_sku_id']); + } + } + + public function test_list_filter_by_company_and_platform(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Mapping 数据'); + } + + $first = $this->getFirstRecord(); + + $response = $this->get('/api/v1/sku-mappings', [ + 'company_id' => $first['company_id'], + 'platform_id' => $first['platform_id'], + ], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($first['company_id'], $item['company_id']); + $this->assertSame($first['platform_id'], $item['platform_id']); + } + } + + public function test_list_filter_by_enabled(): void + { + $response = $this->get('/api/v1/sku-mappings', ['enabled' => 'true'], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertTrue($item['enabled']); + } + } + + // ========== 创建校验 ========== + + public function test_create_without_platform_product_id_succeeds(): void + { + $origin = $this->getFirstOrigin(); + if (!$origin) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $response = $this->post('/api/v1/sku-mappings', [ + 'company_id' => $origin['company_id'], + 'platform_id' => 1, + 'origin_sku' => $origin['sku'], + 'origin_sku_id' => $origin['id'], + 'platform_outer_sku' => 'TEST-' . uniqid(), + ], $this->authHeaders()); + + $response->assertStatus(201); + $response->assertJsonPath('code', 0); + + // 清理测试数据 + $body = json_decode($response->getContent(), true); + if (isset($body['data']['id'])) { + $this->runInCoroutine(static function () use ($body): void { + SkuMapping::query()->where('id', $body['data']['id'])->delete(); + }); + } + } + + public function test_create_with_platform_product_id_succeeds(): void + { + $origin = $this->getFirstOrigin(); + if (!$origin) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $response = $this->post('/api/v1/sku-mappings', [ + 'company_id' => $origin['company_id'], + 'platform_id' => 1, + 'origin_sku' => $origin['sku'], + 'origin_sku_id' => $origin['id'], + 'platform_product_id' => 'PROD-TEST-' . uniqid(), + 'platform_outer_sku' => 'TEST-' . uniqid(), + ], $this->authHeaders()); + + $response->assertStatus(201); + $response->assertJsonPath('code', 0); + + // 清理测试数据 + $body = json_decode($response->getContent(), true); + if (isset($body['data']['id'])) { + $this->runInCoroutine(static function () use ($body): void { + SkuMapping::query()->where('id', $body['data']['id'])->delete(); + }); + } + } + + public function test_create_requires_company_id(): void + { + $response = $this->post('/api/v1/sku-mappings', [ + 'platform_id' => 1, + 'origin_sku' => 'TEST-SKU', + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + public function test_create_requires_platform_id(): void + { + $response = $this->post('/api/v1/sku-mappings', [ + 'company_id' => 1, + 'origin_sku' => 'TEST-SKU', + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + public function test_create_requires_origin_sku(): void + { + $response = $this->post('/api/v1/sku-mappings', [ + 'company_id' => 1, + 'platform_id' => 1, + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + // ========== 详情接口 ========== + + public function test_detail_not_found_returns_404(): void + { + $response = $this->get('/api/v1/sku-mappings/999999999', [], $this->authHeaders()); + + $response->assertStatus(404); + $response->assertJsonPath('code', 404); + } + + // ========== 更新 & 删除 ========== + + public function test_update_mapping(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Mapping 数据'); + } + + $first = $this->getFirstRecord(); + + $response = $this->put('/api/v1/sku-mappings/' . $first['id'], [ + 'note' => 'integration-test-update-' . uniqid(), + ], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + } + + public function test_delete_mapping(): void + { + // 创建一条临时记录然后删除 + $origin = $this->getFirstOrigin(); + if (!$origin) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $createResponse = $this->post('/api/v1/sku-mappings', [ + 'company_id' => $origin['company_id'], + 'platform_id' => 1, + 'origin_sku' => $origin['sku'], + 'origin_sku_id' => $origin['id'], + 'platform_outer_sku' => 'DELETE-TEST-' . uniqid(), + ], $this->authHeaders()); + + $createResponse->assertStatus(201); + $body = json_decode($createResponse->getContent(), true); + $newId = $body['data']['id']; + + $deleteResponse = $this->delete('/api/v1/sku-mappings/' . $newId, [], $this->authHeaders()); + + $deleteResponse->assertStatus(200); + $deleteResponse->assertJsonPath('code', 0); + } + + // ========== 认证拦截 ========== + + public function test_list_without_token_returns_401(): void + { + $response = $this->get('/api/v1/sku-mappings'); + + $response->assertStatus(401); + } +} diff --git a/backend/test/Cases/Integration/Sku/SkuOriginControllerTest.php b/backend/test/Cases/Integration/Sku/SkuOriginControllerTest.php new file mode 100644 index 0000000..ba8a9de --- /dev/null +++ b/backend/test/Cases/Integration/Sku/SkuOriginControllerTest.php @@ -0,0 +1,200 @@ +runInCoroutine(static function (): bool { + return SkuOrigin::query()->exists(); + }); + } + + protected function getFirstRecord(): ?array + { + return $this->runInCoroutine(static function (): ?array { + $record = SkuOrigin::query()->first(); + return $record ? $record->toArray() : null; + }); + } + + // ========== 列表接口 ========== + + public function test_list_returns_paginated_data(): void + { + $response = $this->get('/api/v1/sku-origins', [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonStructure([ + 'code', + 'message', + 'data' => [ + 'items', + 'total', + 'page', + 'per_page', + ], + ]); + } + + public function test_list_respects_per_page(): void + { + $response = $this->get('/api/v1/sku-origins', ['per_page' => 5], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('data.per_page', 5); + } + + public function test_list_filter_by_company_id(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $company_id = $this->runInCoroutine(static function (): mixed { + return SkuOrigin::query()->value('company_id'); + }); + + $response = $this->get('/api/v1/sku-origins', ['company_id' => $company_id], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertSame($company_id, $item['company_id']); + } + } + + public function test_list_filter_by_sku(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $first = $this->getFirstRecord(); + $sku_fragment = substr($first['sku'], 0, 3); + + $response = $this->get('/api/v1/sku-origins', ['sku' => $sku_fragment], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertStringContainsStringIgnoringCase($sku_fragment, $item['sku']); + } + } + + public function test_list_filter_by_barcode(): void + { + if (!$this->hasData()) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $first = $this->getFirstRecord(); + if (empty($first['barcode'])) { + $this->markTestSkipped('首条记录无 barcode'); + } + + $barcode_fragment = substr($first['barcode'], 0, 4); + $response = $this->get('/api/v1/sku-origins', ['barcode' => $barcode_fragment], $this->authHeaders()); + + $response->assertStatus(200); + $body = json_decode($response->getContent(), true); + + foreach ($body['data']['items'] as $item) { + $this->assertStringContainsStringIgnoringCase($barcode_fragment, $item['barcode']); + } + } + + // ========== 详情接口 ========== + + public function test_detail_returns_sku_origin(): void + { + $first = $this->getFirstRecord(); + if (!$first) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $response = $this->get('/api/v1/sku-origins/' . $first['id'], [], $this->authHeaders()); + + $response->assertStatus(200); + $response->assertJsonPath('code', 0); + $response->assertJsonPath('data.id', $first['id']); + } + + public function test_detail_not_found_returns_404(): void + { + $response = $this->get('/api/v1/sku-origins/999999999', [], $this->authHeaders()); + + $response->assertStatus(404); + $response->assertJsonPath('code', 404); + } + + // ========== 创建校验 ========== + + public function test_create_requires_barcode(): void + { + $response = $this->post('/api/v1/sku-origins', [ + 'company_id' => 1, + 'sku' => 'TEST-NO-BARCODE-' . uniqid(), + 'name' => 'Test Product', + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + public function test_create_requires_sku(): void + { + $response = $this->post('/api/v1/sku-origins', [ + 'company_id' => 1, + 'barcode' => '6901234567890', + 'name' => 'Test Product', + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + public function test_create_duplicate_company_sku_returns_422(): void + { + $first = $this->getFirstRecord(); + if (!$first) { + $this->markTestSkipped('没有 SKU Origin 数据'); + } + + $response = $this->post('/api/v1/sku-origins', [ + 'company_id' => $first['company_id'], + 'sku' => $first['sku'], + 'barcode' => '0000000000000', + 'name' => 'Duplicate Test', + ], $this->authHeaders()); + + $response->assertStatus(422); + } + + // ========== 认证拦截 ========== + + public function test_list_without_token_returns_401(): void + { + $response = $this->get('/api/v1/sku-origins'); + + $response->assertStatus(401); + } +} diff --git a/frontend/src/pages/sku-mappings/__tests__/index.spec.ts b/frontend/src/pages/sku-mappings/__tests__/index.spec.ts new file mode 100644 index 0000000..e58e4cf --- /dev/null +++ b/frontend/src/pages/sku-mappings/__tests__/index.spec.ts @@ -0,0 +1,151 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' +import { nextTick } from 'vue' +import { setActivePinia, createPinia } from 'pinia' + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + }), +}) + +vi.mock('@/utils/request', () => ({ + api: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + }, +})) + +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' }, + ], + total: 1, + page: 1, + per_page: 15, +} + +const mockCompanies = [{ id: 3, name: 'Acme Corp', label: 'Acme' }] +const mockPlatforms = [{ id: 1, name: 'Amazon', label: null }] +const mockStores = [{ id: 101, company_id: 3, platform_id: 1, name: 'Store A', label: null }] +const mockSkuOrigins = { + items: [{ id: 1, company_id: 3, sku: '0032', name: 'Test SKU' }], + total: 1, + page: 1, + per_page: 100, +} + +function setupApiMocks() { + vi.mocked(api.get).mockImplementation((url: string) => { + if (url === '/api/v1/sku-mappings') return Promise.resolve(mockMappings) as never + if (url === '/api/v1/companies') return Promise.resolve(mockCompanies) as never + if (url === '/api/v1/platforms') return Promise.resolve(mockPlatforms) as never + if (url === '/api/v1/stores') return Promise.resolve(mockStores) as never + if (url === '/api/v1/sku-origins') return Promise.resolve(mockSkuOrigins) as never + return Promise.resolve({ items: [], total: 0, page: 1, per_page: 15 }) as never + }) +} + +describe('SkuMappingsPage', () => { + let wrapper: ReturnType + + const stubs = { + SearchOutlined: { template: '' }, + ReloadOutlined: { template: '' }, + PlusOutlined: { template: '' }, + EditOutlined: { template: '' }, + DeleteOutlined: { template: '' }, + ThunderboltOutlined: { template: '' }, + QuestionCircleOutlined: { template: '' }, + CascadeFilter: { template: '
' }, + } + + beforeEach(() => { + setActivePinia(createPinia()) + vi.restoreAllMocks() + document.body.innerHTML = '' + }) + + afterEach(() => { + wrapper?.unmount() + document.body.innerHTML = '' + }) + + async function mountPage() { + setupApiMocks() + + const { default: SkuMappingsPage } = await import('../index.vue') + + wrapper = mount(SkuMappingsPage, { + attachTo: document.body, + global: { stubs }, + }) + await flushPromises() + await nextTick() + } + + it('P1: renders page title as SKU 连接管理', async () => { + await mountPage() + + expect(wrapper.text()).toContain('SKU 连接管理') + expect(wrapper.text()).not.toContain('SKU 映射管理') + }, 15000) + + it('P2: calls fetchItems on mount', async () => { + await mountPage() + + expect(api.get).toHaveBeenCalledWith('/api/v1/sku-mappings', expect.any(Object)) + }) + + it('P3: renders table with mapping data', async () => { + await mountPage() + + const html = wrapper.html() + expect(html).toContain('AMZ-0032') + expect(html).toContain('ITEM-001') + }) + + it('P4: create button uses 连接 terminology', async () => { + await mountPage() + + const buttons = wrapper.findAll('.ant-btn') + const buttonTexts = buttons.map((b) => b.text()) + expect(buttonTexts.some((t) => t.includes('新建连接'))).toBe(true) + expect(buttonTexts.some((t) => t.includes('新建映射'))).toBe(false) + }) + + it('P5: platform_product_id has optional placeholder', async () => { + await mountPage() + + // Open create modal + const newBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('新建连接')) + await newBtn?.trigger('click') + await flushPromises() + await nextTick() + + // Modal teleports to document.body + const bodyHtml = document.body.innerHTML + expect(bodyHtml).toContain('可选') + }) + + it('P6: search and reset buttons work', async () => { + await mountPage() + + const buttons = wrapper.findAll('.ant-btn') + const buttonTexts = buttons.map((b) => b.text()) + expect(buttonTexts.some((t) => t.includes('搜索'))).toBe(true) + expect(buttonTexts.some((t) => t.includes('重置'))).toBe(true) + }) +}) diff --git a/frontend/src/pages/sku-mappings/index.vue b/frontend/src/pages/sku-mappings/index.vue index 69587cf..cf80e86 100644 --- a/frontend/src/pages/sku-mappings/index.vue +++ b/frontend/src/pages/sku-mappings/index.vue @@ -33,7 +33,7 @@ const columns = [ // Modal state const modalVisible = ref(false) -const modalTitle = ref('新建映射') +const modalTitle = ref('新建连接') const editingId = ref(null) const formRef = ref() const saving = ref(false) @@ -87,15 +87,41 @@ const filteredStoreOptions = computed(() => { })) }) -// Filtered SKU origins by company -const filteredSkuOrigins = computed(() => { - if (!form.company_id) return store.skuOrigins - return store.skuOrigins.filter((s) => s.company_id === form.company_id) -}) +// Remote search for SKU origins +const skuSearchOptions = ref<{ value: number; label: string }[]>([]) +const skuSearching = ref(false) +let skuSearchTimer: ReturnType | null = null + +function handleSkuSearch(keyword: string) { + if (skuSearchTimer) clearTimeout(skuSearchTimer) + + if (!form.company_id) { + skuSearchOptions.value = [] + return + } + if (keyword.length < 3) { + skuSearchOptions.value = [] + return + } + + skuSearching.value = true + skuSearchTimer = setTimeout(async () => { + try { + const items = await store.searchSkuOrigins(form.company_id!, keyword) + skuSearchOptions.value = items.map((s) => ({ + value: s.id, + label: `${s.sku} - ${s.name}`, + })) + } catch { + skuSearchOptions.value = [] + } finally { + skuSearching.value = false + } + }, 300) +} onMounted(() => { store.loadLookups() - store.loadSkuOrigins() store.fetchItems() }) @@ -123,13 +149,14 @@ function formatTime(time: string | null) { function openCreate() { Object.assign(form, defaultForm()) editingId.value = null - modalTitle.value = '新建映射' + modalTitle.value = '新建连接' + skuSearchOptions.value = [] modalVisible.value = true } function openEdit(record: SkuMappingForm & { id: number }) { editingId.value = record.id - modalTitle.value = '编辑映射' + modalTitle.value = '编辑连接' Object.assign(form, { company_id: record.company_id, platform_id: record.platform_id, @@ -143,9 +170,17 @@ function openEdit(record: SkuMappingForm & { id: number }) { enabled: record.enabled ?? true, note: record.note || '', }) + // 从已有列表数据预填 origin SKU 选项,无需额外 API 调用 + skuSearchOptions.value = record.origin_sku_id + ? [{ value: record.origin_sku_id as number, label: record.origin_sku || '' }] + : [] modalVisible.value = true } +function handleModalClose() { + skuSearchOptions.value = [] +} + async function handleSubmit() { try { await formRef.value.validate() @@ -160,6 +195,7 @@ async function handleSubmit() { await store.createItem({ ...form }) } modalVisible.value = false + handleModalClose() } catch (err: unknown) { const msg = err instanceof Error ? err.message : '操作失败' message.error(msg) @@ -168,10 +204,14 @@ async function handleSubmit() { } } -function handleDelete(record: { id: number; origin_sku: string; platform_outer_sku: string | null }) { +function handleDelete(record: { + id: number + origin_sku: string + platform_outer_sku: string | null +}) { Modal.confirm({ title: '确认删除', - content: `确定删除映射「${record.origin_sku} → ${record.platform_outer_sku || '-'}」?`, + content: `确定删除连接「${record.origin_sku} → ${record.platform_outer_sku || '-'}」?`, okText: '删除', okType: 'danger', cancelText: '取消', @@ -196,7 +236,7 @@ function openGenerate() { return } genForm.strategy = 'prefix' - genForm.prefix = '' + genForm.prefix = form.company_id ? `C${String(form.company_id).padStart(4, '0')}` : '' genForm.random_length = 4 genForm.manual_value = '' genResult.value = '' @@ -244,18 +284,20 @@ function applyGenerated() { } } -// Watch company change to reload SKU origins +// Watch company change to reset SKU search watch( () => form.company_id, - (val) => { - store.loadSkuOrigins(val) + () => { + form.origin_sku_id = undefined + form.origin_sku = '' + skuSearchOptions.value = [] }, )