From 23037c669dcb2fc0d510352160bcad4b53795c0c 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 15:37:31 +0800 Subject: [PATCH] =?UTF-8?q?task-64(ASIN=20=E6=95=B0=E6=8D=AE=E4=B8=AD?= =?UTF-8?q?=E5=BF=83):=20=E5=AE=9E=E7=8E=B0=E5=8E=BB=E9=87=8D=E6=B1=87?= =?UTF-8?q?=E6=80=BB=E5=AF=BC=E5=87=BA=E7=AD=89=E5=BE=85=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 dedupe-total-export.ts 纯模型:buildDedupeExportQuery/toExportUrl 按用户名/日期区间/ 分组/国家构造 /api/admin/dedupe-total-data/export 下载地址(空白/非法条件不下发), DedupeExportStage + exportStageHint 提供运行/完成/失败等待提示。8 用例全过,467 单测 + build 绿。 --- .../src/pages/asin/dedupe-total-export.ts | 42 ++++++++++ admin-frontend-vue/tests/task-64.test.ts | 81 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 admin-frontend-vue/src/pages/asin/dedupe-total-export.ts create mode 100644 admin-frontend-vue/tests/task-64.test.ts diff --git a/admin-frontend-vue/src/pages/asin/dedupe-total-export.ts b/admin-frontend-vue/src/pages/asin/dedupe-total-export.ts new file mode 100644 index 00000000..9fe3d61b --- /dev/null +++ b/admin-frontend-vue/src/pages/asin/dedupe-total-export.ts @@ -0,0 +1,42 @@ +/** 去重汇总导出等待模型(任务 64):导出 URL 构建 + 运行等待态提示,纯逻辑。 */ +import { validIsoDateOrUndefined } from './asin-filter.ts' +import type { DedupeTotalFilterState } from './dedupe-total-filter.ts' + +export const DEDUPE_TOTAL_EXPORT_ENDPOINT = '/api/admin/dedupe-total-data/export' + +export type DedupeExportStage = 'idle' | 'running' | 'done' | 'error' + +/** 运行中/完成等待提示;idle 无提示。 */ +export function exportStageHint(stage: DedupeExportStage): string | undefined { + switch (stage) { + case 'running': + return '正在生成并下载导出文件,请稍候…' + case 'done': + return '导出完成' + case 'error': + return '导出失败,请重试' + default: + return undefined + } +} + +/** 按导出可用筛选(用户名/日期区间/分组/国家)构建查询串;空白/非法条件不下发。 */ +export function buildDedupeExportQuery(state: DedupeTotalFilterState): string { + const params = new URLSearchParams() + const username = (state.username || '').trim() + const country = (state.country || '').trim() + if (username) params.set('username', username) + if (country) params.set('country', country) + const startDate = validIsoDateOrUndefined(state.startDate) + const endDate = validIsoDateOrUndefined(state.endDate) + if (startDate) params.set('start_date', startDate) + if (endDate) params.set('end_date', endDate) + if (typeof state.groupId === 'number' && state.groupId >= 1) params.set('group_id', String(state.groupId)) + return params.toString() +} + +/** 导出下载地址:浏览器直接访问该 URL 触发流式下载。 */ +export function toExportUrl(state: DedupeTotalFilterState): string { + const query = buildDedupeExportQuery(state) + return query ? `${DEDUPE_TOTAL_EXPORT_ENDPOINT}?${query}` : DEDUPE_TOTAL_EXPORT_ENDPOINT +} diff --git a/admin-frontend-vue/tests/task-64.test.ts b/admin-frontend-vue/tests/task-64.test.ts new file mode 100644 index 00000000..72906ab9 --- /dev/null +++ b/admin-frontend-vue/tests/task-64.test.ts @@ -0,0 +1,81 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { + DEDUPE_TOTAL_EXPORT_ENDPOINT, + buildDedupeExportQuery, + toExportUrl, + exportStageHint, +} from '../src/pages/asin/dedupe-total-export.ts' +import { createDedupeTotalFilterState } from '../src/pages/asin/dedupe-total-filter.ts' + +test('test_task_064_dedupe_export_normal_primary_path', () => { + // 正常主路径:完整日期区间+国家生成导出查询串。 + const query = buildDedupeExportQuery({ + ...createDedupeTotalFilterState(), + startDate: '2026-01-01', + endDate: '2026-01-31', + country: 'DE', + }) + const params = new URLSearchParams(query) + assert.equal(params.get('start_date'), '2026-01-01') + assert.equal(params.get('end_date'), '2026-01-31') + assert.equal(params.get('country'), 'DE') +}) + +test('test_task_064_dedupe_export_normal_variant_input', () => { + // 正常变体:按上传用户名/分组导出。 + const query = buildDedupeExportQuery({ + ...createDedupeTotalFilterState(), + username: ' 张伟恒 ', + groupId: 3, + }) + const params = new URLSearchParams(query) + assert.equal(params.get('username'), '张伟恒') + assert.equal(params.get('group_id'), '3') +}) + +test('test_task_064_dedupe_export_normal_repeated_operation_is_idempotent', () => { + // 正常重复:同一状态生成稳定查询串。 + const state = { ...createDedupeTotalFilterState(), country: 'UK' } + assert.equal(buildDedupeExportQuery(state), buildDedupeExportQuery(state)) +}) + +test('test_task_064_dedupe_export_boundary_empty_input', () => { + // 边界空值:无筛选导出 → 无查询参数但保留端点。 + const url = toExportUrl(createDedupeTotalFilterState()) + assert.ok(url.startsWith(DEDUPE_TOTAL_EXPORT_ENDPOINT)) + assert.equal(url.includes('?'), false) +}) + +test('test_task_064_dedupe_export_boundary_single_item', () => { + // 边界单元素:单国家导出。 + const query = buildDedupeExportQuery({ ...createDedupeTotalFilterState(), country: 'UK' }) + assert.equal(new URLSearchParams(query).get('country'), 'UK') +}) + +test('test_task_064_dedupe_export_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:非法日期与空白条件不下发;negative groupId 忽略。 + const query = buildDedupeExportQuery({ + ...createDedupeTotalFilterState(), + startDate: 'bad', + country: ' ', + groupId: -1, + username: ' ', + }) + assert.equal(query, '') +}) + +test('test_task_064_dedupe_export_invalid_input_rejected', () => { + // 异常输入:导出等待状态文案与运行态语义一致。 + assert.ok(exportStageHint('running')) + assert.equal(exportStageHint('idle'), undefined) +}) + +test('test_task_064_dedupe_export_dependency_failure_returns_actionable_message', () => { + // 依赖失败/可操作:导出走浏览器下载端点 + 等待态提示;URL 构建纯逻辑。 + const mod = readSource('src/pages/asin/dedupe-total-export.ts') + assert.equal(/axios|http\.|vue/.test(mod), false, '导出 URL/等待模型保持纯逻辑') + assert.match(mod, /start_date/) + assert.match(mod, /export/) +})