diff --git a/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts b/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts index 52725d85..f38c09ec 100644 --- a/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts +++ b/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts @@ -3,6 +3,7 @@ import { http } from '@/api/http' import { parseDedupeImportProgress, parseDedupeImportStart, type DedupeImportProgress } from './dedupe-import-model' export const DEDUPE_IMPORT_ENDPOINT = '/api/admin/dedupe-total-data/import' +export const DEDUPE_DELETE_IMPORT_ENDPOINT = '/api/admin/dedupe-total-data/delete-import' export async function startDedupeImport(file: File, groupId: number): Promise { const form = new FormData() @@ -17,3 +18,18 @@ export async function fetchDedupeImportProgress(importId: string): Promise(`${DEDUPE_IMPORT_ENDPOINT}/${importId}`) return parseDedupeImportProgress(data) } + +/** 删除导入启动:multipart POST /delete-import(file+groupId) → importId。 */ +export async function startDedupeDeleteImport(file: File, groupId: number): Promise { + const form = new FormData() + form.append('file', file, file.name) + form.append('groupId', String(groupId)) + const { data } = await http.post(DEDUPE_DELETE_IMPORT_ENDPOINT, form) + return parseDedupeImportStart(data) +} + +/** 查询删除导入进度:GET /delete-import/{importId}。 */ +export async function fetchDedupeDeleteImportProgress(importId: string): Promise { + const { data } = await http.get(`${DEDUPE_DELETE_IMPORT_ENDPOINT}/${importId}`) + return parseDedupeImportProgress(data) +} diff --git a/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts b/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts index e7bc5fde..fc7b232f 100644 --- a/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts +++ b/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts @@ -67,3 +67,18 @@ export function importTaskFinished(progress: Pick): boolean { return progress.status === 'success' } + +/** 结果文案:pending/running/success/failed 分别给等待、进度、完成/失败可读行。 */ +export function importOutcomeText(progress: DedupeImportProgress): string { + switch (progress.status) { + case 'running': + return `导入处理中:已处理 ${progress.processedRows}/${progress.totalRows} 行` + case 'success': + return `导入完成:处理 ${progress.insertedCount} 条,跳过 ${progress.skippedCount} 条` + case 'failed': + return progress.errorMessage ? `导入失败:${progress.errorMessage}` : '导入失败,请重试' + case 'pending': + default: + return '等待导入任务开始…' + } +} diff --git a/admin-frontend-vue/tests/task-67.test.ts b/admin-frontend-vue/tests/task-67.test.ts new file mode 100644 index 00000000..4618a961 --- /dev/null +++ b/admin-frontend-vue/tests/task-67.test.ts @@ -0,0 +1,62 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { importOutcomeText, parseDedupeImportProgress } from '../src/pages/asin/dedupe-import-model.ts' + +test('test_task_067_dedupe_delete_import_normal_primary_path', () => { + // 正常主路径:删除导入 running 可继续轮询、有结果文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'running', processedRows: 5, totalRows: 10 } }) + assert.ok(importOutcomeText(progress)) + assert.match(importOutcomeText(progress), /5/) +}) + +test('test_task_067_dedupe_delete_import_normal_variant_input', () => { + // 正常变体:success 终态给出完成文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'success', insertedCount: 3, skippedCount: 1 } }) + const text = importOutcomeText(progress) + assert.match(text, /完成/) + assert.match(text, /3/) +}) + +test('test_task_067_dedupe_delete_import_normal_repeated_operation_is_idempotent', () => { + // 正常重复:文案函数不改输入、结果稳定。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'pending' } }) + assert.equal(importOutcomeText(progress), importOutcomeText(progress)) +}) + +test('test_task_067_dedupe_delete_import_boundary_empty_input', () => { + // 边界空值:pending 给出等待文案,不崩溃。 + const progress = parseDedupeImportProgress({}) + assert.equal(progress.status, 'pending') + assert.ok(importOutcomeText(progress)) +}) + +test('test_task_067_dedupe_delete_import_boundary_single_item', () => { + // 边界单元素:failed 文案带错误信息。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'failed', errorMessage: '读列失败' } }) + assert.match(importOutcomeText(progress), /失败|读列失败/) +}) + +test('test_task_067_dedupe_delete_import_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:processedRows 缺省 0 出现在 running 文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'running' } }) + assert.match(importOutcomeText(progress), /0/) +}) + +test('test_task_067_dedupe_delete_import_invalid_input_rejected', () => { + // 异常输入:success=false 抛后端 message。 + assert.throws( + () => parseDedupeImportProgress({ success: false, message: '无权执行删除导入' }), + /无权执行/, + ) +}) + +test('test_task_067_dedupe_delete_import_dependency_failure_returns_actionable_message', () => { + // 依赖失败/删除导入走 adapter:POST /delete-import + GET /delete-import/{id},复用进度解析。 + const api = readSource('src/pages/asin/dedupe-import-api.ts') + assert.match(api, /startDedupeDeleteImport|deleteImport/) + assert.match(api, /delete-import/) + assert.match(api, /parseDedupeImportProgress/) + const mod = readSource('src/pages/asin/dedupe-import-model.ts') + assert.equal(/axios|http\./.test(mod), false, '导入进度模型保持纯逻辑') +})