From f7ad99dedaa83d399bf33404cb836f0b6159ae15 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 20:59:54 +0800 Subject: [PATCH] =?UTF-8?q?task-247(admin.html=E8=A7=82=E6=84=9F=E5=AF=B9?= =?UTF-8?q?=E9=BD=90):=20=E8=A1=8C=E5=86=85=E5=B7=A5=E5=85=B7=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=8C=96=EF=BC=88=E7=8A=B6=E6=80=81=E8=8D=AF=E4=B8=B8?= =?UTF-8?q?/=E5=A4=8D=E5=88=B6=E6=96=87=E6=9C=AC/=E6=8E=A9=E7=A0=81?= =?UTF-8?q?=E6=8F=AD=E7=A4=BA=E5=A4=8D=E7=94=A8=20secret-mask=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CopyText.vue | 27 +++++++ .../src/components/MaskReveal.vue | 81 +++++++++++++++++++ .../src/components/StatusPill.vue | 25 ++++++ .../src/components/copy-model.ts | 13 +++ .../src/components/status-pill-model.ts | 37 +++++++++ admin-frontend-vue/tests/task-247.test.ts | 73 +++++++++++++++++ 6 files changed, 256 insertions(+) create mode 100644 admin-frontend-vue/src/components/CopyText.vue create mode 100644 admin-frontend-vue/src/components/MaskReveal.vue create mode 100644 admin-frontend-vue/src/components/StatusPill.vue create mode 100644 admin-frontend-vue/src/components/copy-model.ts create mode 100644 admin-frontend-vue/src/components/status-pill-model.ts create mode 100644 admin-frontend-vue/tests/task-247.test.ts diff --git a/admin-frontend-vue/src/components/CopyText.vue b/admin-frontend-vue/src/components/CopyText.vue new file mode 100644 index 00000000..a07415f3 --- /dev/null +++ b/admin-frontend-vue/src/components/CopyText.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/admin-frontend-vue/src/components/MaskReveal.vue b/admin-frontend-vue/src/components/MaskReveal.vue new file mode 100644 index 00000000..f29094c2 --- /dev/null +++ b/admin-frontend-vue/src/components/MaskReveal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/admin-frontend-vue/src/components/StatusPill.vue b/admin-frontend-vue/src/components/StatusPill.vue new file mode 100644 index 00000000..3727df5b --- /dev/null +++ b/admin-frontend-vue/src/components/StatusPill.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/admin-frontend-vue/src/components/copy-model.ts b/admin-frontend-vue/src/components/copy-model.ts new file mode 100644 index 00000000..ddaa6db3 --- /dev/null +++ b/admin-frontend-vue/src/components/copy-model.ts @@ -0,0 +1,13 @@ +/** + * 复制文本反馈模型(module 13 task 247):非空给出「已复制 X」,失败/空值给降级文案。 + */ +export const COPY_FEEDBACK_FALLBACK = '复制失败,请手动选择' + +export function copyFeedback(text: string): string { + const t = typeof text === 'string' ? text.trim() : '' + return t ? `已复制 ${t}` : COPY_FEEDBACK_FALLBACK +} + +export function isCopySupported(): boolean { + return typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function' +} diff --git a/admin-frontend-vue/src/components/status-pill-model.ts b/admin-frontend-vue/src/components/status-pill-model.ts new file mode 100644 index 00000000..0a331f05 --- /dev/null +++ b/admin-frontend-vue/src/components/status-pill-model.ts @@ -0,0 +1,37 @@ +/** + * 状态药丸配色/分类模型(module 13 task 247):统一 admin.html 的三态语义色 + * (success 绿 / danger 红 / warning 琥珀 / info 蓝 / primary 深蓝),供视频任务、 + * 店铺数据任务、数字人版本等页面复用;未知文本回落 info。 + */ +export type PillTone = 'success' | 'danger' | 'warning' | 'info' | 'primary' + +export interface PillToneStyle { + text: string + bg: string +} + +export const PILL_TONES: Record = { + success: { text: '#4e806d', bg: '#e6f6ec' }, + danger: { text: '#b35f6a', bg: '#fdecec' }, + warning: { text: '#a8793e', bg: '#fdf0dc' }, + info: { text: '#4f78a5', bg: '#e7f0f8' }, + primary: { text: '#2f5d8b', bg: '#e6f0f8' }, +} + +/** 取某 tone 的配色;未知 tone 回退 info(不抛错)。 */ +export function pillToneStyle(tone: PillTone): PillToneStyle { + return PILL_TONES[tone] ?? PILL_TONES.info +} + +const SUCCESS_PATTERN = /success|succeed|已发布|发布|成功|正常|可用/ +const DANGER_PATTERN = /fail|error|已废弃|失败|不可用|异常/ +const WARNING_PATTERN = /running|pending|waiting|进行|处理中|草稿|待执行|待/ + +/** 由状态文本(中英文)归类语义 tone。 */ +export function statusTone(raw: string): PillTone { + const s = (raw || '').toLowerCase() + if (SUCCESS_PATTERN.test(s)) return 'success' + if (DANGER_PATTERN.test(s)) return 'danger' + if (WARNING_PATTERN.test(s)) return 'warning' + return 'info' +} diff --git a/admin-frontend-vue/tests/task-247.test.ts b/admin-frontend-vue/tests/task-247.test.ts new file mode 100644 index 00000000..f606ccbe --- /dev/null +++ b/admin-frontend-vue/tests/task-247.test.ts @@ -0,0 +1,73 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { pillToneStyle, PILL_TONES, statusTone, type PillTone } from '../src/components/status-pill-model.ts' +import { copyFeedback } from '../src/components/copy-model.ts' + +// module 13 task 247:行内工具组件化 —— 状态药丸/复制文本/掩码揭示,供视频/店铺数据/ +// 数字人版本等页面复用;掩码复用店铺中心 secret-mask 语义。 + +test('test_task_247_pill_model_normal_primary_path', () => { + // 正常主路径:状态文本 → 语义 tone。 + assert.equal(statusTone('SUCCESS'), 'success') + assert.equal(statusTone('FAILED'), 'danger') + assert.equal(statusTone('已发布'), 'success') + assert.equal(statusTone('失败'), 'danger') +}) + +test('test_task_247_pill_model_normal_variant_input', () => { + // 正常变体:运行/草稿等中间态落 warning;未知回落 info。 + assert.equal(statusTone('RUNNING'), 'warning') + assert.equal(statusTone('草稿'), 'warning') + assert.equal(statusTone('something-unknown'), 'info') +}) + +test('test_task_247_pill_model_normal_repeated_operation_is_idempotent', () => { + assert.equal(statusTone('SUCCESS'), statusTone('success')) + assert.deepEqual(pillToneStyle('success'), pillToneStyle('success')) +}) + +test('test_task_247_pill_model_boundary_empty_input', () => { + // 边界空:空值/未知名不崩溃。 + assert.equal(statusTone(''), 'info') + assert.equal(statusTone(undefined as unknown as string), 'info') +}) + +test('test_task_247_pill_model_boundary_single_item', () => { + // 边界单元素:每个 tone 都有可读配色(药丸文字/底色为 admin 三态色)。 + for (const tone of Object.keys(PILL_TONES) as PillTone[]) { + const s = pillToneStyle(tone) + assert.match(s.text, /^#[0-9a-f]{6}$/) + assert.match(s.bg, /^#[0-9a-f]{6}$/) + } + assert.equal(pillToneStyle('success').text, '#4e806d') + assert.equal(pillToneStyle('danger').text, '#b35f6a') +}) + +test('test_task_247_pill_model_invalid_input_rejected', () => { + // 异常输入:非法 tone 回退 info,不抛错;三态主色与 admin 语义一致。 + assert.equal(pillToneStyle('bogus' as PillTone).text, PILL_TONES.info.text) + assert.equal(PILL_TONES.warning.text, '#a8793e') + assert.equal(PILL_TONES.info.text, '#4f78a5') +}) + +test('test_task_247_copy_model_boundary_limit_or_missing_field', () => { + // 复制反馈:非空给「已复制 X」,空/失败给降级文案。 + assert.equal(copyFeedback('B001'), '已复制 B001') + assert.equal(copyFeedback(' '), '复制失败,请手动选择') + assert.equal(copyFeedback(''), '复制失败,请手动选择') +}) + +test('test_task_247_components_dependency_failure_returns_actionable_message', () => { + // 依赖失败:组件真实存在且接入语义(药丸用 PILL_TONES、复制走 copyFeedback、掩码复用 secret-mask)。 + assert.ok(readSource('src/components/StatusPill.vue').length > 0) + assert.ok(readSource('src/components/CopyText.vue').length > 0) + assert.ok(readSource('src/components/MaskReveal.vue').length > 0) + const copy = readSource('src/components/CopyText.vue') + assert.match(copy, /copyFeedback/) + assert.match(copy, /navigator\.clipboard/) + const mask = readSource('src/components/MaskReveal.vue') + assert.match(mask, /secret-mask|secretPlaceholder|SECRET_PLACEHOLDER/, '掩码应复用店铺 secret-mask 语义') + const pill = readSource('src/components/StatusPill.vue') + assert.match(pill, /pillToneStyle|PILL_TONES/, '药丸应使用统一配色模型') +})