From 88f2ec8bb0a6aa43e711b1e9bc04ee112f2a5c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 21:25:33 +0800 Subject: [PATCH] =?UTF-8?q?task-254(admin.html=E8=A7=82=E6=84=9F=E5=AF=B9?= =?UTF-8?q?=E9=BD=90):=20=E5=8E=BB=E9=87=8D=E6=B1=87=E6=80=BB=E9=A1=B5?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=EF=BC=88=E7=AD=9B=E9=80=89=E6=89=A9=E5=85=85?= =?UTF-8?q?/=E8=A1=8C=E7=BC=96=E8=BE=91=E5=88=A0=E9=99=A4/=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=88=A0=E9=99=A4=E5=AF=BC=E5=85=A5=E8=BD=AE=E8=AF=A2?= =?UTF-8?q?/=E5=AF=BC=E5=87=BA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/asin/DedupeRegistryPage.vue | 374 ++++++++++++++++++ .../src/pages/asin/dedupe-total-api.ts | 51 +++ admin-frontend-vue/tests/task-254.test.ts | 72 ++++ 3 files changed, 497 insertions(+) create mode 100644 admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue create mode 100644 admin-frontend-vue/tests/task-254.test.ts diff --git a/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue b/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue new file mode 100644 index 00000000..a805ad48 --- /dev/null +++ b/admin-frontend-vue/src/pages/asin/DedupeRegistryPage.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/admin-frontend-vue/src/pages/asin/dedupe-total-api.ts b/admin-frontend-vue/src/pages/asin/dedupe-total-api.ts index 35428af0..badb7b4a 100644 --- a/admin-frontend-vue/src/pages/asin/dedupe-total-api.ts +++ b/admin-frontend-vue/src/pages/asin/dedupe-total-api.ts @@ -1,12 +1,63 @@ /** 去重汇总列表查询适配(任务 62):GET /api/admin/dedupe-total-data + 共享筛选归一。 */ import { http } from '@/api/http' +import { unwrap } from '@/api/envelope' import { normalizeAsinPageParams, toAsinPageQuery, type AsinListParams } from './asin-filter' import { parseDedupeTotalPage, type DedupeTotalPageResult } from './dedupe-total-model' export const DEDUPE_TOTAL_ENDPOINT = '/api/admin/dedupe-total-data' +/** 数据权限分组选项源(与店铺/去重共用同一分组集)。 */ +export const DEDUPE_GROUPS_ENDPOINT = '/api/admin/shop-manage-groups' export async function fetchDedupeTotalList(params: Partial = {}): Promise { const normalized = normalizeAsinPageParams(params) const { data } = await http.get(DEDUPE_TOTAL_ENDPOINT, { params: toAsinPageQuery(normalized) }) return parseDedupeTotalPage(data) } + +export interface DedupeGroupOption { + id: number + name: string +} + +/** 编辑单行去重总数据(ASIN 值 + 分组):PUT /dedupe-total-data/{id}。 */ +export async function updateDedupeTotal(id: number, dataValue: string, groupId: number): Promise { + const { data } = await http.put(`${DEDUPE_TOTAL_ENDPOINT}/${id}`, { + data_value: dataValue, + group_id: groupId, + }) + unwrap(data) +} + +/** 删除单行去重总数据:DELETE /dedupe-total-data/{id}。 */ +export async function deleteDedupeTotal(id: number): Promise { + const { data } = await http.delete(`${DEDUPE_TOTAL_ENDPOINT}/${id}`) + unwrap(data) +} + +/** 加载数据权限分组选项:GET /shop-manage-groups → {id,name}[]。 */ +export async function fetchDedupeTotalGroups(): Promise { + const { data } = await http.get(DEDUPE_GROUPS_ENDPOINT) + return parseDedupeGroupOptionList(data) +} + +/** 归一化分组选项负载(信封 items 或裸数组),缺 id 行丢弃。 */ +export function parseDedupeGroupOptionList(payload: unknown): DedupeGroupOption[] { + const core = unwrap(payload) + const rawList = Array.isArray(core) + ? core + : core && typeof core === 'object' + ? (core as Record).items + : [] + if (!Array.isArray(rawList)) return [] + const options: DedupeGroupOption[] = [] + for (const raw of rawList) { + if (!raw || typeof raw !== 'object') continue + const record = raw as Record + const id = typeof record.id === 'number' ? Math.floor(record.id) : Number(record.group_id) + if (!Number.isFinite(id) || id <= 0) continue + const nameRaw = record.group_name ?? record.name + const name = typeof nameRaw === 'string' ? nameRaw.trim() : '' + options.push({ id, name: name || `分组${id}` }) + } + return options +} diff --git a/admin-frontend-vue/tests/task-254.test.ts b/admin-frontend-vue/tests/task-254.test.ts new file mode 100644 index 00000000..83ca67f5 --- /dev/null +++ b/admin-frontend-vue/tests/task-254.test.ts @@ -0,0 +1,72 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' + +// module 13 task 254:去重汇总页对齐 admin panel-dedupe-total-data —— 筛选扩充(用户名/分组/日期)、 +// 行编辑/删除、新增导入/删除导入(轮询)、导出(日期校验+等待) 接线既有孤儿模块。 + +test('test_task_254_dedupe_normal_primary_path', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.match(page, /新增导入/, '页头需有新增导入按钮') + assert.match(page, /删除导入/, '页头需有删除导入按钮') + assert.match(page, /导出/, '页头需有导出入口') + assert.match(page, /编辑/, '行内需有编辑操作') + assert.match(page, /删除/, '行内需有删除操作') +}) + +test('test_task_254_dedupe_normal_variant_input', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.match(page, /fetchDedupeTotalGroups/, '需接线分组列表源') + assert.match(page, /toDedupeListParams/, '需走共享筛选归一(dedupe-total-filter)') + assert.match(page, /importFinished|importTaskFinished/, '需用导入终态判断停止轮询') + assert.match(page, /importOutcomeText/, '需展示导入/删除进度文案') +}) + +test('test_task_254_dedupe_normal_repeated_operation_is_idempotent', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.ok(page.includes('新增导入')) + assert.ok(page.includes('新增导入')) +}) + +test('test_task_254_dedupe_boundary_empty_input', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.match(page, /开始日期/, '筛选需含开始日期') + assert.match(page, /结束日期/, '筛选需含结束日期') + assert.match(page, /上传人|用户名/, '筛选需含用户名') + assert.match(page, /分组/, '筛选需含分组下拉') + assert.match(page, /国家/, '筛选需含国家下拉') +}) + +test('test_task_254_dedupe_boundary_single_item', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.match(page, /开始日期不能晚于结束日期|晚于结束日期/, '导出需做日期区间校验') + assert.match(page, /确定删除总数据/, '行删除确认文案应对齐 admin') +}) + +test('test_task_254_dedupe_boundary_limit_or_missing_field', () => { + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + assert.match(page, /\.xlsx|\.xls|xlsx/, '导入需 Excel 文件类型提示') + assert.match(page, /mounted|onMounted/, '需首屏加载') +}) + +test('test_task_254_dedupe_invalid_input_rejected', () => { + // 异常:不得出现空操作按钮(无 @click 的页头主按钮)。 + const page = readSource('src/pages/asin/DedupeRegistryPage.vue') + const headerButtons = page.match(/type="primary"[^>]*>([^<]*?)([^<]*?) { + // 依赖失败:api 模块须暴露单行更新/删除端点;导入/删除导入复用既有常量(防假接线)。 + const api = readSource('src/pages/asin/dedupe-total-api.ts') + assert.match(api, /updateDedupeTotal|method: 'PUT'|\.put