From d8950e93da9fd8be44276d04b854dfacc31fd7c4 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 17:32:08 +0800 Subject: [PATCH] =?UTF-8?q?task-124(=E8=AE=B0=E5=BD=95=E4=B8=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=B8=AD=E5=BF=83):=20=E5=AE=9E=E7=8E=B0=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E7=BB=93=E6=9E=9C=E9=A2=84=E8=A7=88=E6=8A=BD=E5=B1=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 history-preview.ts:从历史记录提取结果图/长图并限制数量,避免大内容阻塞。 TDD: task-124.test.ts 8 用例 RED→GREEN。 --- .../src/pages/records/history-preview.ts | 29 ++++++++ admin-frontend-vue/tests/task-124.test.ts | 68 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 admin-frontend-vue/src/pages/records/history-preview.ts create mode 100644 admin-frontend-vue/tests/task-124.test.ts diff --git a/admin-frontend-vue/src/pages/records/history-preview.ts b/admin-frontend-vue/src/pages/records/history-preview.ts new file mode 100644 index 00000000..1296109d --- /dev/null +++ b/admin-frontend-vue/src/pages/records/history-preview.ts @@ -0,0 +1,29 @@ +/** 历史结果预览抽屉(任务 124):从历史记录提取可预览图片并限制数量,避免大内容阻塞页面;纯逻辑。 */ +import type { HistoryRecordItem } from './history-dto.ts' + +export interface PreviewDescriptor { + images: string[] +} + +function safeHttpUrl(value: unknown): string { + const url = typeof value === 'string' ? value.trim() : '' + return /^https?:\/\//i.test(url) ? url : '' +} + +/** 构建预览描述(结果图 + 长图,按记录顺序去重,数量受 limit 限制)。 */ +export function buildPreviewDescriptor(item: HistoryRecordItem | null | undefined, limit: number): PreviewDescriptor { + const images: string[] = [] + const seen = new Set() + const max = typeof limit === 'number' && Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20 + const push = (url: string) => { + const clean = safeHttpUrl(url) + if (!clean || seen.has(clean) || images.length >= max) return + seen.add(clean) + images.push(clean) + } + if (item && typeof item === 'object') { + if (Array.isArray(item.resultUrls)) item.resultUrls.forEach((url) => push(url)) + push(item.longImageUrl) + } + return { images } +} diff --git a/admin-frontend-vue/tests/task-124.test.ts b/admin-frontend-vue/tests/task-124.test.ts new file mode 100644 index 00000000..d6fa2c27 --- /dev/null +++ b/admin-frontend-vue/tests/task-124.test.ts @@ -0,0 +1,68 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { buildPreviewDescriptor } from '../src/pages/records/history-preview.ts' + +function item(over: Record) { + return { + id: 1, + userId: null, + username: '', + createdAt: '', + panelType: 'x', + originalUrls: [], + resultUrls: [], + longImageUrl: '', + ...over, + } +} + +test('test_task_124_history_preview_normal_primary_path', () => { + // 正常主路径:预览取结果图与长图。 + const descriptor = buildPreviewDescriptor(item({ resultUrls: ['https://a/1.png', 'http://b/2.png'], longImageUrl: 'https://long.png' }), 10) + assert.deepEqual(descriptor.images, ['https://a/1.png', 'http://b/2.png', 'https://long.png']) +}) + +test('test_task_124_history_preview_normal_variant_input', () => { + // 正常变体:非 http 地址过滤。 + const descriptor = buildPreviewDescriptor(item({ resultUrls: ['ftp://bad', 'https://ok.png'] }), 10) + assert.deepEqual(descriptor.images, ['https://ok.png']) +}) + +test('test_task_124_history_preview_repeated_is_idempotent', () => { + // 正常重复:预览构建稳定、不改记录。 + const record = item({ resultUrls: ['https://a.png'] }) + assert.deepEqual(buildPreviewDescriptor(record, 10), buildPreviewDescriptor(record, 10)) + assert.equal((record as { resultUrls: string[] }).resultUrls.length, 1) +}) + +test('test_task_124_history_preview_boundary_empty_input', () => { + // 边界空值:无图预览回空。 + const descriptor = buildPreviewDescriptor(item({}), 10) + assert.deepEqual(descriptor.images, []) +}) + +test('test_task_124_history_preview_boundary_single_item', () => { + // 边界单元素:单图预览。 + const descriptor = buildPreviewDescriptor(item({ resultUrls: ['https://a.png'] }), 10) + assert.equal(descriptor.images.length, 1) +}) + +test('test_task_124_history_preview_boundary_limit_or_missing_field', () => { + // 边界上限:超上限截断。 + const urls = Array.from({ length: 30 }, (_, i) => `https://a/${i}.png`) + const descriptor = buildPreviewDescriptor(item({ resultUrls: urls }), 10) + assert.equal(descriptor.images.length, 10) +}) + +test('test_task_124_history_preview_invalid_input_rejected', () => { + // 异常输入:空记录预览为空。 + assert.deepEqual(buildPreviewDescriptor(null as never, 10).images, []) +}) + +test('test_task_124_history_preview_dependency_failure_returns_actionable_message', () => { + // 依赖失败/可操作:预览纯逻辑、无框架/http;限制长度避免大内容阻塞。 + const mod = readSource('src/pages/records/history-preview.ts') + assert.equal(/axios|http\.|vue/.test(mod), false, '预览保持纯逻辑') + assert.match(mod, /limit/) +})