From 4e86506f3e80333ae260e07afe36e9ce1498c838 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 21:01:23 +0800
Subject: [PATCH] =?UTF-8?q?task-248(admin.html=E8=A7=82=E6=84=9F=E5=AF=B9?=
=?UTF-8?q?=E9=BD=90):=20=E6=A8=A1=E6=80=81/=E6=8A=BD=E5=B1=89=E5=AE=B9?=
=?UTF-8?q?=E5=99=A8=E7=BB=9F=E4=B8=80=EF=BC=88ModalShell/DrawerShell=20?=
=?UTF-8?q?=E6=94=B6=E5=8F=A3=E5=B0=BA=E5=AF=B8=E4=B8=8E=E5=85=B3=E9=97=AD?=
=?UTF-8?q?=E8=AF=AD=E4=B9=89=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/components/DrawerShell.vue | 42 +++++++++++
.../src/components/ModalShell.vue | 42 +++++++++++
.../src/components/modal-model.ts | 30 ++++++++
admin-frontend-vue/tests/task-248.test.ts | 74 +++++++++++++++++++
4 files changed, 188 insertions(+)
create mode 100644 admin-frontend-vue/src/components/DrawerShell.vue
create mode 100644 admin-frontend-vue/src/components/ModalShell.vue
create mode 100644 admin-frontend-vue/src/components/modal-model.ts
create mode 100644 admin-frontend-vue/tests/task-248.test.ts
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 @@
+
+
+
+
+ {{ head.subtitle }}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ {{ head.subtitle }}
+
+
+
+
+
+
+
+
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/遮罩关闭语义')
+})