diff --git a/admin-frontend-vue/src/components/DrawerShell.vue b/admin-frontend-vue/src/components/DrawerShell.vue new file mode 100644 index 00000000..b83eadf1 --- /dev/null +++ b/admin-frontend-vue/src/components/DrawerShell.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/admin-frontend-vue/src/components/ModalShell.vue b/admin-frontend-vue/src/components/ModalShell.vue new file mode 100644 index 00000000..50457e02 --- /dev/null +++ b/admin-frontend-vue/src/components/ModalShell.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/admin-frontend-vue/src/components/modal-model.ts b/admin-frontend-vue/src/components/modal-model.ts new file mode 100644 index 00000000..4bd32a6c --- /dev/null +++ b/admin-frontend-vue/src/components/modal-model.ts @@ -0,0 +1,30 @@ +/** + * 模态/抽屉布局模型(module 13 task 248):统一两种承载容器的尺寸与关闭语义, + * 对齐 admin.html —— 居中 modal width min(920px,100vw-48)、radius14;右侧 drawer + * width min(460px,100%);遮罩点击与 Esc 均关闭。页面接入统一尺寸与头/脚槽。 + */ +export const ADMIN_MODAL_WIDTH = 'min(920px, 100vw - 48px)' +export const ADMIN_DRAWER_WIDTH = 'min(460px, 100%)' +export const ADMIN_MODAL_RADIUS = 14 + +export interface AdminContainerSpec { + width: string + radius?: number + maskClosable: boolean + escClosable: boolean +} + +export function modalLayoutSpec(): AdminContainerSpec { + return { width: ADMIN_MODAL_WIDTH, radius: ADMIN_MODAL_RADIUS, maskClosable: true, escClosable: true } +} + +export function drawerLayoutSpec(): AdminContainerSpec { + return { width: ADMIN_DRAWER_WIDTH, maskClosable: true, escClosable: true } +} + +/** 页面头标题归一:空/空白标题兜底「操作」,副标题去空白后无则省略。 */ +export function dialogTitleOf(opts: { title?: string; subtitle?: string }): { title: string; subtitle?: string } { + const title = opts.title?.trim() + const subtitle = opts.subtitle?.trim() + return { title: title || '操作', ...(subtitle ? { subtitle } : {}) } +} diff --git a/admin-frontend-vue/tests/task-248.test.ts b/admin-frontend-vue/tests/task-248.test.ts new file mode 100644 index 00000000..f6d929ee --- /dev/null +++ b/admin-frontend-vue/tests/task-248.test.ts @@ -0,0 +1,74 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { + ADMIN_DRAWER_WIDTH, + ADMIN_MODAL_RADIUS, + ADMIN_MODAL_WIDTH, + dialogTitleOf, + drawerLayoutSpec, + modalLayoutSpec, +} from '../src/components/modal-model.ts' + +// module 13 task 248:模态/抽屉容器统一 —— 居中 modal min(920px,100vw-48)、右侧 drawer +// min(460px,100%)、三段式头/体/脚与关闭语义对齐 admin,页面接入统一宽度/结构。 + +test('test_task_248_container_model_normal_primary_path', () => { + const modal = modalLayoutSpec() + assert.equal(modal.width, ADMIN_MODAL_WIDTH) + assert.equal(modal.radius, ADMIN_MODAL_RADIUS) + assert.equal(ADMIN_MODAL_RADIUS, 14) + const drawer = drawerLayoutSpec() + assert.equal(drawer.width, ADMIN_DRAWER_WIDTH) +}) + +test('test_task_248_container_model_normal_variant_input', () => { + // 正常变体:modal/drawer 都支持遮罩点击与 Esc 关闭(对齐 admin 语义)。 + assert.equal(modalLayoutSpec().maskClosable, true) + assert.equal(modalLayoutSpec().escClosable, true) + assert.equal(drawerLayoutSpec().maskClosable, true) + assert.equal(drawerLayoutSpec().escClosable, true) +}) + +test('test_task_248_container_model_normal_repeated_operation_is_idempotent', () => { + assert.deepEqual(modalLayoutSpec(), modalLayoutSpec()) + assert.deepEqual(drawerLayoutSpec(), drawerLayoutSpec()) +}) + +test('test_task_248_container_model_boundary_empty_input', () => { + // 边界空:无标题时页头兜底「操作」;空白标题同样兜底。 + assert.equal(dialogTitleOf({}).title, '操作') + assert.equal(dialogTitleOf({ title: ' ' }).title, '操作') +}) + +test('test_task_248_container_model_boundary_single_item', () => { + // 边界单元素:带副标题保留、去首尾空白。 + const head = dialogTitleOf({ title: ' 编辑店铺 ', subtitle: ' 店铺分组/账号凭证明细 ' }) + assert.equal(head.title, '编辑店铺') + assert.equal(head.subtitle, '店铺分组/账号凭证明细') +}) + +test('test_task_248_container_model_boundary_limit_or_missing_field', () => { + // 边界上限:宽度表达式满足 admin 尺寸口径。 + assert.match(ADMIN_MODAL_WIDTH, /920px/) + assert.match(ADMIN_MODAL_WIDTH, /100vw/) + assert.match(ADMIN_DRAWER_WIDTH, /460px/) +}) + +test('test_task_248_modal_shell_dependency_failure_returns_actionable_message', () => { + // 依赖失败:ModalShell 真实封装 el-dialog,宽度来自模型,标题透传、带 footer 槽。 + const modal = readSource('src/components/ModalShell.vue') + assert.match(modal, /el-dialog/) + assert.match(modal, /modalLayoutSpec|ADMIN_MODAL_WIDTH/) + assert.match(modal, /#footer/) + assert.ok(readSource('src/components/DrawerShell.vue').length > 0) +}) + +test('test_task_248_drawer_shell_dependency_failure_returns_actionable_message', () => { + const drawer = readSource('src/components/DrawerShell.vue') + assert.match(drawer, /el-drawer/) + assert.match(drawer, /drawerLayoutSpec|ADMIN_DRAWER_WIDTH/) + assert.match(drawer, /:title=|head\.title/, '标题透传到 drawer 默认头部') + assert.match(drawer, /#footer/, '提供 footer 槽以构成头/体/脚三段') + assert.match(drawer, /close-on-press-escape|close-on-click-modal/, '需配置 Esc/遮罩关闭语义') +})