From bf0cecef88d75b1dcff7c5af3386223ce2be0a64 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 16:53:47 +0800 Subject: [PATCH] =?UTF-8?q?task-99(=E5=BA=97=E9=93=BA=E4=B8=AD=E5=BF=83):?= =?UTF-8?q?=20=E5=AE=9E=E7=8E=B0=E5=BA=97=E9=93=BA=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 shop-table-layout.ts:店铺/密钥列表列定义与按视口宽度筛选可见列, 店铺名/操作等关键列任意宽度保留。 TDD: task-99.test.ts 8 用例先 RED 后 GREEN。 --- .../src/pages/shop/shop-table-layout.ts | 43 +++++++++++++ admin-frontend-vue/tests/task-99.test.ts | 63 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 admin-frontend-vue/src/pages/shop/shop-table-layout.ts create mode 100644 admin-frontend-vue/tests/task-99.test.ts diff --git a/admin-frontend-vue/src/pages/shop/shop-table-layout.ts b/admin-frontend-vue/src/pages/shop/shop-table-layout.ts new file mode 100644 index 00000000..53ad2c75 --- /dev/null +++ b/admin-frontend-vue/src/pages/shop/shop-table-layout.ts @@ -0,0 +1,43 @@ +/** 店铺中心响应式表格(任务 99):按视口宽度决定可见列,关键列(店铺名/操作)始终显示;纯逻辑。 */ + +export interface ShopTableColumn { + key: string + label: string + /** 可见所需最小视口宽度(px)。 */ + minWidth: number + /** 关键列:任意宽度都显示。 */ + always?: boolean +} + +/** 店铺列表列定义(列序稳定,供响应式筛选与表头渲染)。 */ +export const SHOP_MANAGE_COLUMNS: ShopTableColumn[] = [ + { key: 'index', label: '序号', minWidth: 180 }, + { key: 'groupName', label: '分组', minWidth: 320 }, + { key: 'shopName', label: '店铺', minWidth: 0, always: true }, + { key: 'mallName', label: '商城', minWidth: 520 }, + { key: 'znUsername', label: '自动化账号', minWidth: 760 }, + { key: 'account', label: '账号', minWidth: 640 }, + { key: 'password', label: '密码', minWidth: 680 }, + { key: 'createdAt', label: '创建时间', minWidth: 880 }, + { key: 'updatedAt', label: '修改时间', minWidth: 1000 }, + { key: 'actions', label: '操作', minWidth: 0, always: true }, +] + +/** 店铺密钥列表列定义。 */ +export const SHOP_KEYS_COLUMNS: ShopTableColumn[] = [ + { key: 'index', label: '序号', minWidth: 180 }, + { key: 'remarkName', label: '备注名', minWidth: 0, always: true }, + { key: 'ziniaoAccountName', label: '紫鸟账号名称', minWidth: 360 }, + { key: 'ziniaoToken', label: '紫鸟令牌', minWidth: 420 }, + { key: 'whitelist', label: '白名单状态', minWidth: 560 }, + { key: 'createdAt', label: '创建时间', minWidth: 760 }, + { key: 'updatedAt', label: '修改时间', minWidth: 820 }, + { key: 'actions', label: '操作', minWidth: 0, always: true }, +] + +/** 按视口宽度返回应可见的列(保持原顺序);非正宽度只保留 always 列。 */ +export function shopColumnsForWidth(columns: ShopTableColumn[] | undefined, width: number): ShopTableColumn[] { + if (!Array.isArray(columns)) return [] + const safeWidth = typeof width === 'number' && Number.isFinite(width) ? Math.max(Math.floor(width), 0) : 0 + return columns.filter((column) => column.always || safeWidth >= column.minWidth) +} diff --git a/admin-frontend-vue/tests/task-99.test.ts b/admin-frontend-vue/tests/task-99.test.ts new file mode 100644 index 00000000..20c5d77c --- /dev/null +++ b/admin-frontend-vue/tests/task-99.test.ts @@ -0,0 +1,63 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { + SHOP_KEYS_COLUMNS, + SHOP_MANAGE_COLUMNS, + shopColumnsForWidth, +} from '../src/pages/shop/shop-table-layout.ts' + +test('test_task_099_shop_table_responsive_normal_primary_path', () => { + // 正常主路径:宽屏(≥1200px)显示店铺表全部分组列。 + const wide = shopColumnsForWidth(SHOP_MANAGE_COLUMNS, 1280) + assert.equal(wide.length, SHOP_MANAGE_COLUMNS.length) + assert.ok(wide.some((c) => c.key === 'account')) + assert.ok(wide.some((c) => c.key === 'password')) +}) + +test('test_task_099_shop_table_responsive_normal_variant_input', () => { + // 正常变体:密钥表在常规宽度保留关键列。 + const cols = shopColumnsForWidth(SHOP_KEYS_COLUMNS, 900) + assert.ok(cols.some((c) => c.key === 'remarkName')) + assert.ok(cols.some((c) => c.key === 'actions')) +}) + +test('test_task_099_shop_table_responsive_repeated_is_idempotent', () => { + // 正常重复:同一宽度列集合稳定、顺序稳定。 + assert.deepEqual(shopColumnsForWidth(SHOP_MANAGE_COLUMNS, 1100), shopColumnsForWidth(SHOP_MANAGE_COLUMNS, 1100)) +}) + +test('test_task_099_shop_table_responsive_boundary_empty_input', () => { + // 边界空值:极窄宽度仍保留 always 列(店铺名/操作)。 + const narrow = shopColumnsForWidth(SHOP_MANAGE_COLUMNS, 1) + assert.ok(narrow.some((c) => c.key === 'shopName')) + assert.ok(narrow.some((c) => c.key === 'actions')) +}) + +test('test_task_099_shop_table_responsive_boundary_single_item', () => { + // 边界单元素:恰好等于某列最小宽度时该列可见。 + const account = SHOP_MANAGE_COLUMNS.find((c) => c.key === 'account') + assert.ok(account) + const atWidth = shopColumnsForWidth(SHOP_MANAGE_COLUMNS, account!.minWidth) + assert.ok(atWidth.some((c) => c.key === 'account')) +}) + +test('test_task_099_shop_table_responsive_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:未知列/空列集合被安全处理。 + assert.deepEqual(shopColumnsForWidth([], 2000), []) + assert.deepEqual(shopColumnsForWidth(undefined as never, 1200), []) +}) + +test('test_task_099_shop_table_responsive_invalid_input_rejected', () => { + // 异常输入:非正宽度按最小可用处理。 + const cols = shopColumnsForWidth(SHOP_MANAGE_COLUMNS, -10) + assert.ok(cols.every((c) => c.always)) + assert.ok(cols.length > 0) +}) + +test('test_task_099_shop_table_responsive_dependency_failure_returns_actionable_message', () => { + // 依赖失败/可操作:表格布局纯逻辑、无框架/http。 + const mod = readSource('src/pages/shop/shop-table-layout.ts') + assert.equal(/axios|http\.|vue/.test(mod), false, '表格布局保持纯逻辑') + assert.match(mod, /SHOP_MANAGE_COLUMNS/) +})