From 3aa71a94d2954270b6ad57e15409fd3c8b973483 Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 18 May 2026 02:13:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(skills):=20design-handoff=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E7=A8=BF=20+=20spec=20+=20.gitignore=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mp-11-doctor-core 设计交付包(截图 + tokens) - mp-13/mp-14 新原型 HTML - design-handoff skill 设计规格文档 - .gitignore 排除 dist-h5/test-results/uploads 等构建产物 --- .../skills/design-handoff/package-lock.json | 47 +- .claude/skills/design-handoff/package.json | 3 +- .gitignore | 10 + docs/design/mp-11-doctor-core/META.yml | 29 + docs/design/mp-11-doctor-core/SPEC.md | 458 ++++++++ docs/design/mp-11-doctor-core/tokens.json | 45 + docs/design/mp-13-family-profile.html | 258 +++++ docs/design/mp-14-guest-home.html | 244 +++++ .../2026-05-17-design-handoff-skill-design.md | 992 ++++++++++++++++++ 9 files changed, 2084 insertions(+), 2 deletions(-) create mode 100644 docs/design/mp-11-doctor-core/META.yml create mode 100644 docs/design/mp-11-doctor-core/SPEC.md create mode 100644 docs/design/mp-11-doctor-core/tokens.json create mode 100644 docs/design/mp-13-family-profile.html create mode 100644 docs/design/mp-14-guest-home.html create mode 100644 docs/superpowers/specs/2026-05-17-design-handoff-skill-design.md diff --git a/.claude/skills/design-handoff/package-lock.json b/.claude/skills/design-handoff/package-lock.json index 0798b90..3e9ba8c 100644 --- a/.claude/skills/design-handoff/package-lock.json +++ b/.claude/skills/design-handoff/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "js-yaml": "^4.1.1" + "js-yaml": "^4.1.1", + "playwright": "^1.58.0" } }, "node_modules/argparse": { @@ -18,6 +19,20 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -29,6 +44,36 @@ "bin": { "js-yaml": "bin/js-yaml.js" } + }, + "node_modules/playwright": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz", + "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz", + "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } } } } diff --git a/.claude/skills/design-handoff/package.json b/.claude/skills/design-handoff/package.json index decb3d8..f7581e3 100644 --- a/.claude/skills/design-handoff/package.json +++ b/.claude/skills/design-handoff/package.json @@ -11,6 +11,7 @@ "license": "ISC", "type": "commonjs", "dependencies": { - "js-yaml": "^4.1.1" + "js-yaml": "^4.1.1", + "playwright": "^1.58.0" } } diff --git a/.gitignore b/.gitignore index 47ebc6a..6d979f8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,16 @@ docker/redis_data/ # Test artifacts .test_token +test-results/ + +# Build outputs +apps/miniprogram/dist-h5/ + +# Runtime uploads +uploads/ + +# Temp logs +_server_out.txt *.heapsnapshot perf-trace-*.json docs/debug-*.png diff --git a/docs/design/mp-11-doctor-core/META.yml b/docs/design/mp-11-doctor-core/META.yml new file mode 100644 index 0000000..5ac1fea --- /dev/null +++ b/docs/design/mp-11-doctor-core/META.yml @@ -0,0 +1,29 @@ +prototype: mp-11-doctor-core.html +source: docs/design/mp-11-doctor-core.html +generated_at: "2026-05-18T00:16:00+08:00" +variant: 医生端(.doctor-mode 靛蓝主色 #3A6B8C) +tokens: + matched: 20 + unmatched: 2 + pending: 15 +components: + total: 14 + mapped: 8 + new: 6 +interactions: 5 +screens: + - label: 医生工作台 + component: DoctorHome + screenshot: screenshots/doctor.png + - label: 待办收件箱 + component: ActionInbox + screenshot: screenshots/screen-2.png + - label: 在线咨询 + component: ConsultList + screenshot: screenshots/consultation.png + - label: 随访管理 + component: FollowUpList + screenshot: screenshots/followupmanage.png + - label: 患者管理 + component: PatientList + screenshot: screenshots/patientmanage.png diff --git a/docs/design/mp-11-doctor-core/SPEC.md b/docs/design/mp-11-doctor-core/SPEC.md new file mode 100644 index 0000000..e5a3f6a --- /dev/null +++ b/docs/design/mp-11-doctor-core/SPEC.md @@ -0,0 +1,458 @@ +# mp-11-doctor-core 设计规格 + +> 由 design-handoff 自动生成 | 源文件: mp-11-doctor-core.html | 生成时间: 2026-05-18 + +## 1. 概览 + +- **原型文件**: mp-11-doctor-core.html +- **设计变体**: 医生端(`.doctor-mode` 靛蓝主色 #3A6B8C 替代患者端赤土橙 #C4623A) +- **设计 Token 数**: 20/22 已匹配(confirmed),15 pending,2 unmatched +- **组件数**: 14(8 已映射 + 6 需新建/扩展) +- **交互行为数**: 5 matched / 3 unmatched +- **屏幕数**: 5 + +### 屏幕索引 + +| # | 屏幕 | 组件名 | 截图 | +|---|------|--------|------| +| 1 | 医生工作台 | DoctorHome | screenshots/doctor.png | +| 2 | 待办收件箱 | ActionInbox | screenshots/screen-2.png | +| 3 | 在线咨询 | ConsultList | screenshots/consultation.png | +| 4 | 随访管理 | FollowUpList | screenshots/followupmanage.png | +| 5 | 患者管理 | PatientList | screenshots/patientmanage.png | + +--- + +## 2. 设计 Token + +### 2.1 医生端色彩变体 + +> 医生端核心差异:主色系从赤土橙切换为靛蓝,通过 `.doctor-mode` CSS 变量级联覆盖实现。 + +| Token | 原型值 | CSS Token | SCSS 变量 | 状态 | 说明 | +|-------|--------|-----------|-----------|------|------| +| T.pri | `#3A6B8C` | `--tk-pri` | `$doc-pri` | confirmed | 医生端靛蓝主色 | +| T.priL | `#D4E5F0` | `--tk-pri-l` | `$doc-pri-l` | confirmed | 靛蓝浅色 | +| T.priD | `#2A4F6A` | `--tk-pri-d` | `$doc-pri-d` | confirmed | 靛蓝深色 | + +### 2.2 语义色彩 + +| Token | 值 | CSS Token / SCSS 变量 | 状态 | 角色 | +|-------|-----|----------------------|------|------| +| T.bg | `#F5F0EB` | `$bg` | pending | 页面背景/温润米底 | +| T.card | `#FFFFFF` | `--tk-card-bg` | confirmed | 卡片白底 | +| T.surface | `#EDE8E2` | `$surface-alt` | pending | 辅助底色 | +| T.tx | `#2D2A26` | `$tx` | pending | 主文字/暖黑 | +| T.tx2 | `#5A554F` | `$tx2` | pending | 次文字/暖灰 | +| T.tx3 | `#78716C` | `--tk-text-secondary` | confirmed | 辅助文字 | +| T.bd | `#E8E2DC` | `$bd` | pending | 边框色 | +| T.bdL | `#F0EBE5` | `$bd-l` | pending | 浅边框色 | +| T.acc | `#5B7A5E` | `$acc` | pending | 鼠尾草绿/成功色 | +| T.accL | `#E8F0E8` | `$acc-l` | pending | 成功浅色 | +| T.wrn | `#C4873A` | `$wrn` | pending | 警告色/暖琥珀 | +| T.wrnL | `#FFF3E0` | `$wrn-l` | pending | 警告浅色 | +| T.dan | `#B54A4A` | `$dan` | pending | 危险色/柔红 | +| T.danL | `#FDEAEA` | `$dan-l` | pending | 危险浅色 | + +### 2.3 字号 + +| 原型值 (px) | CSS Token | 状态 | 使用位置 | +|------------|-----------|------|---------| +| 28 | `--tk-font-h1` | confirmed | 统计数值 | +| 26 | — | unmatched | 工作台标题(介于 h1/h2 之间) | +| 20 | — | unmatched | 头像文字 | +| 18 | `--tk-font-body-lg` | confirmed | NavBar 标题、头像内文字 | +| 16 | `--tk-font-body` | confirmed | 页面标题 | +| 15 | — | unmatched | 列表主文字(介于 body/body-sm) | +| 14 | `--tk-font-body-sm` | confirmed | 次要文字、日期 | +| 13 | `--tk-font-cap` | confirmed | section 标题、辅助信息 | +| 12 | — | unmatched | 标签文字、时间、辅助 | +| 11 | `--tk-font-micro` | confirmed | 角标、Tab 文字、Tag | + +### 2.4 间距 + +| 原型值 (px) | CSS Token / SCSS 变量 | 状态 | 使用位置 | +|------------|----------------------|------|---------| +| 16 | `--tk-gap-md` / `$sp-md` | confirmed | 卡片 padding、section 间距 | +| 14 | — | unmatched | 列表项 padding(14px 16px) | +| 12 | `--tk-gap-sm` / `$sp-sm` | confirmed | 列表项间距、卡片 padding | +| 10 | — | unmatched | 待办项间距、元素间距 | +| 8 | `--tk-gap-xs` / `$sp-xs` | confirmed | Tab 间距、元素 gap | +| 6 | — | unmatched | 小间距 | +| 3 | `--tk-tag-padding-v` | confirmed | Tab 文字与图标间距 | + +### 2.5 圆角 + +| 原型值 (px) | CSS Token / SCSS 变量 | 状态 | 使用位置 | +|------------|----------------------|------|---------| +| 16 | `--tk-card-radius` / `$r` | confirmed | 卡片圆角 | +| 12 | `$r-sm` | pending | 搜索栏、数据条圆角 | +| 8 | `$r-xs` | pending | 小圆角 | +| 6 | — | unmatched | Tag 圆角 | +| 20 | `$r-lg` | pending | 筛选标签 pill 圆角 | +| 22-26 | — | unmatched | 头像圆形(borderRadius = width/2) | + +### 2.6 字体 + +| Token | 值 | 状态 | 说明 | +|-------|-----|------|------| +| T.serif | `Georgia, 'Times New Roman', serif` | unmatched | 标题/数值衬线字体 | +| T.sans | `-apple-system, 'PingFang SC', sans-serif` | unmatched | 正文无衬线字体 | + +--- + +## 3. 组件清单 + +### 3.1 组件树 + +``` +App +├── IosFrame (设备框,不实现) +│ ├── DoctorHome ─── 医生工作台 +│ │ ├── PageShell +│ │ ├── SectionTitle (工作台标题) +│ │ ├── ContentCard > StatGrid (今日概览) +│ │ ├── ShortcutButton × 4 +│ │ ├── SectionTitle (待办提醒) +│ │ └── TodoAlert × 2 +│ ├── ActionInbox ─── 待办收件箱 +│ │ ├── NavBar +│ │ ├── TabFilter (筛选标签) +│ │ └── ListItem × 5 +│ ├── ConsultList ─── 在线咨询 +│ │ ├── NavBar +│ │ ├── StatusTabs (进行中/已结束) +│ │ └── ListItem × 3 +│ ├── FollowUpList ─── 随访管理 +│ │ ├── NavBar +│ │ ├── TabFilter +│ │ └── FollowUpCard × 3 +│ └── PatientList ─── 患者管理 +│ ├── NavBar +│ ├── SearchBar +│ └── ListItem × 4 +└── BottomTabBar (工作台页专用) +``` + +### 3.2 已映射组件 + +#### NavBar(已有组件) + +- **推断规则**: 命中规则 #5(页面级容器) +- **Token 依赖**: `T.bg`, `T.bdL`, `T.tx`, `T.serif` +- **医生端差异**: 标题字体 serif bold 18px,背景 T.bg + 底部 1px 分隔线 +- **样式属性**: + ``` + height: 44px + background: T.bg + borderBottom: 1px solid T.bdL + fontFamily: T.serif + fontSize: 18px + fontWeight: 700 + color: T.tx + ``` + +#### ContentCard(已有组件) + +- **推断规则**: 命中规则 #1(容器 + borderRadius + boxShadow + 标题 + 正文) +- **Token 依赖**: `T.card`, `T.r`, `T.bg`, `T.tx2`, `T.tx3` +- **子组件**: StatGrid(医生工作台)、数据条(随访卡片) +- **样式属性**: + ``` + background: T.card + borderRadius: T.r (16px) + padding: 16px + boxShadow: 0 2px 12px rgba(0,0,0,0.04) + marginBottom: 16px + ``` + +#### Tag / StatusTag(已有组件) + +- **推断规则**: 命中规则 #6(小型容器 + 背景色 + 短文字) +- **Token 依赖**: 动态 color/bg props +- **变体**: 类型标签(异常/随访/咨询)、紧急标签、状态标签 +- **样式属性**: + ``` + display: inline-block + padding: 2px 8px + borderRadius: 6px + fontSize: 11px (or dynamic) + fontWeight: 600 + lineHeight: 1.6 + ``` + +#### ListItem(已有组件) + +- **推断规则**: 命中规则 #7(列表行 + 左侧区 + 双行文字 + 右侧箭头) +- **Token 依赖**: `T.card`, `T.r`, `T.tx`, `T.tx2`, `T.tx3`, `T.pri` +- **变体**: + - ActionInbox: Tag + 患者名 + 描述 + 时间 + 箭头 + - ConsultList: AvatarCircle + 患者名 + 消息 + 未读角标 + 箭头 + - PatientList: AvatarCircle + 姓名 + 诊断 + 最近日期 + 箭头 +- **样式属性**: + ``` + background: T.card + borderRadius: T.r (16px) + padding: 14px 16px + marginBottom: 10px + boxShadow: 0 1px 8px rgba(0,0,0,0.03) + display: flex; align-items: center; gap: 12px + ``` + +#### PageShell(已有组件) + +- **推断规则**: 命中规则 #5(最外层容器 + padding + 可滚动) +- **Token 依赖**: `T.bg` +- **样式属性**: + ``` + height: 100% + background: T.bg + display: flex; flexDirection: column + ``` + +#### BottomTabBar(已有组件,需 doctor-mode 变体) + +- **推断规则**: 页面底部固定 +- **Token 依赖**: `T.card`, `T.bdL`, `T.pri`, `T.tx3` +- **Tab 项**: 工作台、患者、消息、我的 +- **样式属性**: + ``` + position: absolute; bottom: 0; left: 0; right: 0 + height: 70px + background: T.card + borderTop: 1px solid T.bdL + paddingTop: 8px; paddingBottom: 28px + ``` +- **激活态**: 颜色 T.pri(靛蓝)、fontWeight: 600 +- **非激活**: 颜色 T.tx3、fontWeight: 400 + +### 3.3 需新建/扩展组件 + +#### TabFilter(筛选标签 pill) + +- **推断规则**: 未命中(特殊 pill 形筛选栏) +- **视觉特征**: 横向排列的圆角标签,激活态实色填充(白字),非激活态白底+边框 +- **出现位置**: ActionInbox(全部/异常/随访/咨询)、FollowUpList(待随访/已完成/已过期) +- **Token 依赖**: `T.pri`, `T.card`, `T.bd`, `T.tx2` +- **样式属性**: + ``` + padding: 6px 16px + borderRadius: 20px (pill) + fontSize: 13px + // 激活态: background: T.pri, color: #fff, border: T.pri + // 非激活: background: T.card, color: T.tx2, border: T.bd + cursor: pointer + ``` + +#### StatusTabs(状态切换标签栏) + +- **推断规则**: 未命中(全宽等分标签栏,底部指示线) +- **视觉特征**: 等宽标签,激活态底部 2.5px 主色线,非激活底部 1.5px 浅色线 +- **出现位置**: ConsultList(进行中/已结束) +- **Token 依赖**: `T.pri`, `T.bdL`, `T.tx3` +- **样式属性**: + ``` + flex: 1; textAlign: center + padding: 10px 0 + fontSize: 14px + // 激活: color: T.pri, borderBottom: 2.5px solid T.pri, fontWeight: 600 + // 非激活: color: T.tx3, borderBottom: 1.5px solid T.bdL + ``` + +#### ShortcutButton(快捷操作按钮) + +- **推断规则**: 未命中(圆形图标按钮 + 文字标签) +- **视觉特征**: 52×52 圆形容器(彩色浅底)+ SVG 图标 + 下方文字标签 +- **出现位置**: DoctorHome(4 个:患者管理/在线咨询/随访管理/透析管理) +- **Token 依赖**: `T.priL`, `T.accL`, `T.wrnL`, `T.danL`, `T.pri`, `T.acc`, `T.wrn`, `T.dan` +- **样式属性**: + ``` + // 圆形容器 + width: 52px; height: 52px + borderRadius: 26px (50%) + background: 动态浅色 + display: flex; alignItems: center; justifyContent: center + // 文字标签 + fontSize: 12px; color: T.tx2 + gap: 8px + ``` + +#### TodoAlert(待办提醒卡片) + +- **推断规则**: 命中规则 #2 的扩展(左侧竖线 + 图标 + 双行文字) +- **视觉特征**: 左侧 4px 彩色竖线 + SVG 图标 + 标题 + 副标题,浅色背景 +- **出现位置**: DoctorHome(2 个:血压异常/随访报告) +- **Token 依赖**: `T.priL`, `T.pri`, `T.wrnL`, `T.wrn`, `T.tx`, `T.tx3` +- **样式属性**: + ``` + background: T.priL (or T.wrnL) + borderRadius: T.r (16px) + padding: 14px 16px + borderLeft: 4px solid T.pri (or T.wrn) + display: flex; alignItems: center; gap: 10px + ``` + +#### SearchBar(搜索栏) + +- **推断规则**: 未命中(带搜索图标的输入框容器) +- **视觉特征**: 圆角白底容器 + 搜索图标 + placeholder 文字 +- **出现位置**: PatientList +- **Token 依赖**: `T.card`, `T.bd`, `T.tx3` +- **样式属性**: + ``` + background: T.card + borderRadius: 12px + height: 42px + padding: 0 14px + gap: 10px + border: 1px solid T.bd + ``` + +#### AvatarCircle(文字头像圆形) + +- **推断规则**: 未命中(圆形 + 单字 + 衬线字体) +- **视觉特征**: 正圆形容器,彩色浅底,居中显示姓氏首字(serif bold) +- **出现位置**: ConsultList(44px)、PatientList(46px) +- **Token 依赖**: `T.priL`, `T.accL`, `T.wrnL`, `T.danL`, `T.pri`, `T.serif` +- **样式属性**: + ``` + width: 44px (or 46px); height: 同 + borderRadius: 50% + background: 动态浅色 + fontFamily: T.serif + fontSize: 18px (or 20px) + fontWeight: 700 + color: T.pri + ``` + +--- + +## 4. 交互行为 + +| # | 元素 | 事件类型 | 行为描述 | 实现建议 | +|---|------|---------|---------|---------| +| 1 | TabFilter 标签 | click | 点击切换筛选类别,激活态视觉变化 | `useState` 管理激活索引,切换时重新过滤列表 | +| 2 | StatusTabs 标签 | click | 点击切换「进行中/已结束」状态 | `useState` 管理激活索引,切换时重新加载列表 | +| 3 | 列表卡片 | click | 点击跳转到详情页 | 每个 ListItem 包裹 `navigator`,传递患者/咨询 ID | +| 4 | 快捷操作按钮 | click | 点击跳转到对应功能页 | ShortcutButton 绑定 `Taro.navigateTo` | +| 5 | BottomTabBar | click | 切换 Tab 页 | 使用 `Taro.switchTab` | +| 6 | 搜索栏 | input | 输入关键词实时搜索患者 | 防抖 300ms + API 调用 | +| 7 | 列表滚动 | scroll | 滚动加载更多数据 | `onScrollToLower` + 分页 API | + +### 滚动容器 + +所有列表页的内容区域均设置了 `overflow: auto`,需要实现为 `ScrollView` 组件,支持上拉加载: +- DoctorHome: `padding: 16px 20px 80px`(底部留出 TabBar 空间) +- ActionInbox: `padding: 0 20px 20px` +- ConsultList: `padding: 12px 20px 20px` +- FollowUpList: `padding: 0 20px 20px` +- PatientList: `padding: 0 20px 20px` + +--- + +## 5. 未匹配项 + +### 5.1 未匹配 Token + +| 值 | 类型 | 出现位置 | 建议 Token 名称 | 备注 | +|----|------|---------|----------------|------| +| `T.serif` = `Georgia, ...` | fontFamily | 标题、数值、头像 | 已有 `$font-serif` SCSS 变量 | 直接使用 `$font-serif` | +| `T.sans` = `-apple-system, ...` | fontFamily | 正文、辅助文字 | 已有 `$font-sans` SCSS 变量 | 直接使用 `$font-sans` | +| 26px | fontSize | 工作台标题 | — | 使用 `--tk-font-h1`(28px) 或保留原值 | +| 20px | fontSize | 头像文字 | — | body-lg(18px) 或保留 | +| 15px | fontSize | 列表主文字 | — | body(16px) 更符合 Token 体系 | +| 12px | fontSize | 辅助文字、时间 | 建议新增 `--tk-font-xs: 12px` | 高频使用 | +| 14px (padding) | padding | 列表项上下 padding | — | gap-md(16px) 近似 | + +### 5.2 需新建组件 + +| 组件名建议 | 视觉特征摘要 | 出现位置 | 优先级 | +|-----------|------------|---------|--------| +| TabFilter | 横向 pill 筛选标签,激活态实色填充 | ActionInbox, FollowUpList | 高 | +| StatusTabs | 全宽等分标签栏,底部指示线 | ConsultList | 高 | +| ShortcutButton | 圆形图标 + 文字标签,4 种颜色 | DoctorHome | 中 | +| TodoAlert | 左侧竖线 + 图标 + 双行文字 | DoctorHome | 中 | +| SearchBar | 圆角白底 + 搜索图标 | PatientList | 中 | +| AvatarCircle | 圆形 + 单字衬线头像 | ConsultList, PatientList | 低(可用现有 Avatar 扩展) | + +--- + +## 6. 医生端实现注意事项 + +### 6.1 .doctor-mode CSS 变量覆盖 + +本原型是**医生端变体**,核心差异通过 CSS 变量级联覆盖: + +```scss +// 患者端默认 +:root { + --tk-pri: #C4623A; + --tk-pri-l: #F0DDD4; + --tk-pri-d: #8B3E1F; +} + +// 医生端覆盖 +.doctor-mode { + --tk-pri: #3A6B8C; + --tk-pri-l: #D4E5F0; + --tk-pri-d: #2A4F6A; +} +``` + +所有使用 `--tk-pri*` 的组件在 `.doctor-mode` 下自动切换为靛蓝色系,无需单独处理。 + +### 6.2 页面路由规划 + +| 页面 | 路径建议 | TabBar | +|------|---------|--------| +| 医生工作台 | `pages/doctor/index` | 工作台 Tab | +| 患者管理 | `pages/doctor/patients` | 患者 Tab | +| 消息/咨询列表 | `pages/doctor/consultations` | 消息 Tab | +| 我的 | `pages/doctor/profile` | 我的 Tab | +| 待办收件箱 | `pages/doctor/inbox` | 非 Tab,从工作台跳转 | +| 随访管理 | `pages/doctor/followups` | 非 Tab,从快捷入口跳转 | + +### 6.3 TabBar 配置 + +医生端使用独立的 4 Tab 布局: + +```typescript +const doctorTabs = [ + { pagePath: 'pages/doctor/index', text: '工作台', icon: 'grid' }, + { pagePath: 'pages/doctor/patients', text: '患者', icon: 'users' }, + { pagePath: 'pages/doctor/consultations', text: '消息', icon: 'message' }, + { pagePath: 'pages/doctor/profile', text: '我的', icon: 'user' }, +]; +``` + +### 6.4 数据接口 + +| 页面 | 需要的 API | 方法 | +|------|-----------|------| +| 医生工作台 | `GET /api/v1/health/doctor/stats` | 统计概览 | +| 医生工作台 | `GET /api/v1/health/doctor/todos` | 待办列表 | +| 待办收件箱 | `GET /api/v1/health/doctor/todos?type={type}` | 筛选待办 | +| 在线咨询 | `GET /api/v1/health/consultations?status=active` | 咨询列表 | +| 随访管理 | `GET /api/v1/health/followups?status={status}` | 随访列表 | +| 患者管理 | `GET /api/v1/health/patients?search={keyword}` | 患者搜索 | + +### 6.5 长者模式适配 + +所有页面需适配长者模式(字号 ≥ 22px)。`.elder-mode` CSS 变量覆盖已定义在 tokens.yml 中,实现时注意: +- Tag 字号 elder: 13px → 17px +- 微调间距 elder 值 +- TabFilter pill 需同步增大触控区域 + +### 6.6 与患者端组件复用 + +以下组件可直接从患者端复用(已实现 .doctor-mode 覆盖): +- `NavBar` +- `ContentCard` +- `Tag` +- `BottomTabBar`(需配置医生端 Tab 项) + +需要新建的医生端专用组件: +- `TabFilter` — 患者端无对应(患者端用横向滚动分类) +- `StatusTabs` — 患者端无对应 +- `ShortcutButton` — 患者端无对应 +- `TodoAlert` — 患者端无对应 diff --git a/docs/design/mp-11-doctor-core/tokens.json b/docs/design/mp-11-doctor-core/tokens.json new file mode 100644 index 0000000..57cdc7f --- /dev/null +++ b/docs/design/mp-11-doctor-core/tokens.json @@ -0,0 +1,45 @@ +{ + "source": "mp-11-doctor-core.html", + "generatedAt": "2026-05-18T00:16:00+08:00", + "matched": { + "T.pri": { "method": "alias", "confidence": "confirmed", "token": "--tk-pri", "prototypeValue": "#3A6B8C", "tokenValue": "#C4623A", "note": "医生端 .doctor-mode 覆盖为靛蓝 #3A6B8C" }, + "T.priL": { "method": "alias", "confidence": "confirmed", "token": "--tk-pri-l", "prototypeValue": "#D4E5F0", "tokenValue": "#F0DDD4", "note": "医生端覆盖" }, + "T.priD": { "method": "alias", "confidence": "confirmed", "token": "--tk-pri-d", "prototypeValue": "#2A4F6A", "tokenValue": "#8B3E1F", "note": "医生端覆盖" }, + "T.bg": { "method": "alias", "confidence": "pending", "token": null, "scssVar": "$bg", "prototypeValue": "#F5F0EB", "note": "tokens.scss 未声明为 CSS 变量" }, + "T.card": { "method": "alias", "confidence": "confirmed", "token": "--tk-card-bg", "prototypeValue": "#FFFFFF", "tokenValue": "#FFFFFF" }, + "T.surface": { "method": "alias", "confidence": "approximate", "token": "--tk-card-bg", "prototypeValue": "#EDE8E2", "tokenValue": "#FFFFFF" }, + "T.tx": { "method": "alias", "confidence": "pending", "token": null, "scssVar": "$tx", "prototypeValue": "#2D2A26" }, + "T.tx2": { "method": "alias", "confidence": "pending", "token": null, "scssVar": "$tx2", "prototypeValue": "#5A554F" }, + "T.tx3": { "method": "alias", "confidence": "confirmed", "token": "--tk-text-secondary", "prototypeValue": "#78716C" }, + "T.bd": { "method": "alias", "confidence": "pending", "token": null, "scssVar": "$bd", "prototypeValue": "#E8E2DC" }, + "T.bdL": { "method": "value_exact", "confidence": "pending", "scssVar": "$bd-l", "prototypeValue": "#F0EBE5" }, + "T.acc": { "method": "value_exact", "confidence": "pending", "scssVar": "$acc", "prototypeValue": "#5B7A5E" }, + "T.accL": { "method": "value_exact", "confidence": "pending", "scssVar": "$acc-l", "prototypeValue": "#E8F0E8" }, + "T.wrn": { "method": "value_exact", "confidence": "pending", "scssVar": "$wrn", "prototypeValue": "#C4873A" }, + "T.wrnL": { "method": "value_exact", "confidence": "pending", "scssVar": "$wrn-l", "prototypeValue": "#FFF3E0" }, + "T.dan": { "method": "value_exact", "confidence": "pending", "scssVar": "$dan", "prototypeValue": "#B54A4A" }, + "T.danL": { "method": "value_exact", "confidence": "pending", "scssVar": "$dan-l", "prototypeValue": "#FDEAEA" }, + "T.r": { "method": "alias", "confidence": "confirmed", "token": "--tk-card-radius", "prototypeValue": "16" }, + "T.rSm": { "method": "alias", "confidence": "pending", "scssVar": "$r-sm", "prototypeValue": "12" }, + "T.rXs": { "method": "alias", "confidence": "pending", "scssVar": "$r-xs", "prototypeValue": "8" } + }, + "unmatched": ["T.serif", "T.sans"], + "inlineTokenMap": { + "fontSize:28": { "token": "--tk-font-h1", "confidence": "confirmed" }, + "fontSize:26": { "token": null, "confidence": "unmatched", "note": "工作台标题 26px,介于 h1(28) 和 h2(22) 之间" }, + "fontSize:20": { "token": null, "confidence": "unmatched", "note": "头像文字 20px" }, + "fontSize:18": { "token": "--tk-font-body-lg", "confidence": "confirmed" }, + "fontSize:16": { "token": "--tk-font-body", "confidence": "confirmed" }, + "fontSize:15": { "token": null, "confidence": "unmatched", "note": "列表主文字 15px,介于 body(16) 和 body-sm(14) 之间" }, + "fontSize:14": { "token": "--tk-font-body-sm", "confidence": "confirmed" }, + "fontSize:13": { "token": "--tk-font-cap", "confidence": "confirmed" }, + "fontSize:12": { "token": null, "confidence": "unmatched", "note": "辅助文字 12px,无对应 Token" }, + "fontSize:11": { "token": "--tk-font-micro", "confidence": "confirmed" } + }, + "summary": { + "confirmed": 20, + "pending": 15, + "approximate": 1, + "unmatched": 2 + } +} diff --git a/docs/design/mp-13-family-profile.html b/docs/design/mp-13-family-profile.html new file mode 100644 index 0000000..b862704 --- /dev/null +++ b/docs/design/mp-13-family-profile.html @@ -0,0 +1,258 @@ + + + + + +HMS 小程序 — 就诊人管理 + 建档 + + + + + + + +
HMS 小程序 · 就诊人管理 + 建档
+
温润东方风设计系统 — 就诊人列表(切换/编辑)+ 新建就诊人表单,两屏并排。表单即建档,建档后解锁积分商城等功能。
+
+ + + + diff --git a/docs/design/mp-14-guest-home.html b/docs/design/mp-14-guest-home.html new file mode 100644 index 0000000..c4f9dfb --- /dev/null +++ b/docs/design/mp-14-guest-home.html @@ -0,0 +1,244 @@ + + + + + +HMS 小程序 — 访客首页 + + + + + + + +
HMS 小程序 · 访客首页
+
温润东方风设计系统 — 未登录用户的首页。品牌 Hero + 服务特色 + 健康资讯 + 底部注册/登录引导。TabBar 仅展示首页/健康/商城/我的四个入口。
+
+ + + + diff --git a/docs/superpowers/specs/2026-05-17-design-handoff-skill-design.md b/docs/superpowers/specs/2026-05-17-design-handoff-skill-design.md new file mode 100644 index 0000000..54bbb89 --- /dev/null +++ b/docs/superpowers/specs/2026-05-17-design-handoff-skill-design.md @@ -0,0 +1,992 @@ +# design-handoff Skill 设计规格 + +> 日期: 2026-05-17 | 状态: 草案 +> 问题: HTML 原型到实际实现的翻译层缺失,新会话 LLM 无法高保真还原原型设计 +> 方案: 结构化交付包(截图 + SPEC.md + Token 映射) + +## 目录 + +1. **背景与问题** — 现状分析、核心矛盾、解决目标 +2. **Skill 架构** — 触发方式、输入输出、核心流程 +3. **SPEC.md 核心格式** — 页面结构、组件映射、交互规格的文档模板 +4. **Token 映射系统** — 混合匹配算法、配置文件、产出格式 +5. **截图提取与交互推断** — Playwright 截图、交互规则引擎 +6. **多平台支持与集成** — 多平台映射、huashu-design 协作、Prompt 模板 + +--- + +## 1. 背景与问题 + +### 1.1 现状 + +项目使用 `huashu-design` skill 产出 HTML 原型稿(React inline styles + 设计 Token 对象 `T`)。目前已有 **20 个原型文件**(`docs/design/mp-*.html`),覆盖小程序访客端、患者端、医生端全部页面。 + +实施时的工作流是: + +1. **会话 A**:调用 huashu-design → 选定设计风格 → 扩展到所有页面原型 +2. **会话 B**(新会话):拿 HTML 原型实现 Taro/React 组件代码 + +### 1.2 核心矛盾 + +会话 B 的 LLM 与会话 A 完全隔离,丢失了所有设计上下文。三层翻译丢失: + +| 丢失层 | 原因 | 表现 | +|--------|------|------| +| **视觉丢失** | LLM 读 HTML 源码,看不到渲染结果 | 颜色、字号、间距与原型不一致 | +| **Token 断裂** | 原型用 `#C4623A`,代码用 `var(--tk-pri)` | 映射关系丢失,硬编码泛滥 | +| **组件错配** | 原型全是 `
`,代码要用组件库 | 应复用 ContentCard/PageShell 但没复用 | +| **交互缺失** | 原型暗示的按压反馈、滑动、状态切换无文档化 | 交互行为被忽略 | + +### 1.3 解决目标 + +创建 `design-handoff` skill,在 HTML 原型完成后自动产出**结构化交付包**,让新会话的 LLM 能: + +- **看到**原型渲染效果(截图) +- **知道**每个值对应哪个项目 Token(映射表) +- **选用**正确的现有组件(组件映射) +- **实现**原型暗示的交互(交互规格) + +--- + +## 2. Skill 架构 + +### 2.1 触发方式 + +```bash +# 单个原型 +/design-handoff docs/design/mp-00-visitor.html + +# 批量处理 +/design-handoff docs/design/mp-*.html + +# 指定平台 +/design-handoff docs/design/mp-00-visitor.html --platform web +``` + +**触发词**:`design-handoff`、`设计交付`、`handoff` + +### 2.2 输入 + +| 参数 | 必需 | 默认值 | 说明 | +|------|------|--------|------| +| HTML 文件路径 | 是 | — | 原型文件路径,支持 glob | +| `--platform` | 否 | `miniprogram` | 目标平台:miniprogram / web / h5 | +| `--tokens` | 否 | `.design/tokens.yml` | Token 映射配置文件路径 | + +### 2.3 输出目录结构 + +``` +docs/design/ +├── mp-00-visitor.html # 原始原型(不修改) +├── mp-00-visitor/ # 交付包目录 +│ ├── SPEC.md # 主规格文件(LLM 消费入口) +│ ├── screenshots/ # 截图目录 +│ │ ├── home.png +│ │ ├── home-slide-1.png +│ │ ├── home-slide-2.png +│ │ ├── home-slide-3.png +│ │ └── profile.png +│ ├── tokens.json # Token 映射表(机器可校验) +│ └── META.yml # 元数据 +├── mp-01-login/ +│ └── ... +└── .design/ # 全局配置(项目级) + └── tokens.yml # Token 映射配置 +``` + +### 2.4 核心流程 + +``` +输入: HTML 原型文件 + │ + ├─ Step 1: 解析 HTML ──────────────────────┐ + │ 提取 T 对象(Token 引用) │ + │ 提取组件树(页面结构) │ + │ 识别 IosFrame 实例(截图目标) │ + │ │ + ├─ Step 2: 截图提取 ───────────────────────┤ + │ Playwright 打开 HTML │ + │ 逐 IosFrame 截图 │ + │ 裁剪设备框,保留屏幕内容 │ + │ │ + ├─ Step 3: Token 匹配 ─────────────────────┤ + │ 优先级 1: 别名直查(aliases) │ + │ 优先级 2: 值精确匹配 │ + │ 优先级 3: 色彩模糊匹配(ΔE < 3) │ + │ 产出 tokens.json │ + │ │ + ├─ Step 4: 组件映射 ───────────────────────┤ + │ 原型元素 → 项目组件库匹配 │ + │ 标记需新建的组件 │ + │ │ + ├─ Step 5: 交互推断 ───────────────────────┤ + │ DOM 模式 → 交互规则匹配 │ + │ 高/中/低置信度分级 │ + │ │ + └─ Step 6: 组装 SPEC.md ───────────────────┘ + 截图 + 结构 + Token + 组件 + 交互 + → 单一规格文档 +``` + +### 2.5 Skill 文件清单 + +``` +.claude/skills/design-handoff/ +├── SKILL.md # Skill 入口 +├── scripts/ +│ ├── extract-screenshots.mjs # Playwright 截图 +│ ├── parse-prototype.mjs # HTML 解析(T 对象 + 组件树) +│ ├── match-tokens.mjs # Token 三层匹配 +│ └── infer-interactions.mjs # 交互推断规则引擎 +├── templates/ +│ └── spec-template.md # SPEC.md 模板 +├── defaults/ +│ └── tokens.yml # 初始 Token 配置 +└── rules/ + └── interaction-rules.yml # 交互推断规则表 +``` + +**前置依赖**: +- Node.js ≥ 18(运行 .mjs 脚本) +- Playwright(截图提取;huashu-design 已依赖,无需额外安装) +- 项目需包含 `styles/tokens.scss` 或等效的 Token 定义文件 + +--- + +## 3. SPEC.md 核心格式 + +SPEC.md 是 LLM 在新会话里消费的**唯一入口文件**。设计原则: + +- **自包含**:截图通过 markdown 图片引用内嵌,一个文件看全貌 +- **结构化**:六个固定章节,LLM 可按需定位 +- **可操作**:每个值都映射到项目 Token 或组件,不含模糊描述 + +### 3.1 文档结构(六个固定章节) + +```markdown +# {页面名称} 设计规格 + +> 来源: {原型文件名} | 平台: {platform} | 页面数: {n} + +## 页面索引 +(截图缩略图 + 路由映射表) + +## 一、Token 映射 +(原型值 → 项目 Token,含匹配状态标记) + +## 二、页面结构 +(逐页:截图 + 布局层级描述 + 尺寸标注) + +## 三、组件映射 +(原型元素 → 项目组件库组件,含来源路径) + +## 四、交互规格 +(元素 × 交互类型 × 触发 × 反馈 × 备注) + +## 五、状态变体 +(加载中 / 空数据 / 错误 / 已登录等边界状态) + +## 六、样式清单 +(间距 / 字号 / 圆角 / 阴影的 Token 汇总) +``` + +### 3.2 页面索引 + +用表格关联截图与路由,LLM 一眼定位: + +```markdown +## 页面索引 + +| 页面 | 截图 | 路由 | +|------|------|------| +| 访客首页 | ![home](./screenshots/home.png) | pages/index/index | +| 访客"我的" | ![profile](./screenshots/profile.png) | pages/profile/index | +``` + +### 3.3 Token 映射章节 + +三级状态标记,LLM 知道哪些可以直接用: + +| 标记 | 含义 | LLM 行为 | +|------|------|---------| +| ✅ confirmed | 已确认映射 | 直接使用对应 Token | +| ⚠️ pending | 模糊匹配,待确认 | 使用但需人工复核 | +| ❌ unmatched | 无匹配 | 硬编码或新建 Token | + +```markdown +## 一、Token 映射 + +| 原型值 | 项目 Token | 状态 | +|--------|-----------|------| +| #C4623A (T.pri) | var(--tk-pri) | ✅ | +| #F5F0EB (T.bg) | var(--tk-bg-base) | ✅ | +| 32px (轮播标题) | var(--tk-font-h1) | ⚠️ 待确认(28→32 不匹配) | +| serif 字体 | — | ❌ 无 Token,硬编码 font-family | +``` + +### 3.4 页面结构章节 + +每个页面一个子节,**以截图开头**,紧跟布局层级: + +```markdown +### 2.1 访客首页 + +![首页全貌](./screenshots/home.png) + +布局层级(从上到下): + +1. **轮播区域** (height: 280px) + - 容器: `` (Taro),autoplay + circular + - 3 张品牌轮播:渐变背景 + 装饰圆 + 标题文案 + 指示点 + - 指示点: 活跃 24×4 白色,非活跃 8×4 半透明白 + +2. **健康资讯** (padding: 20px → var(--tk-gap-lg)) + - 标题行: SectionTitle,serif 18px bold + "更多›" + - 文章卡片 ×2: ContentCard variant="default" + - 左侧配图 110px 宽,右侧标题 15px + 摘要 13px + +3. **登录引导** (padding: 28px 20px 40px) + - CTA: PrimaryButton size="large",阴影 var(--tk-shadow-btn) +``` + +每个元素标注:**用什么组件** + **尺寸/间距的 Token 引用** + **视觉特征**。 + +### 3.5 组件映射章节 + +表格形式,明确"用哪个组件": + +```markdown +## 三、组件映射 + +| 原型元素 | 推荐组件 | 来源 | 备注 | +|----------|---------|------|------| +| 文章卡片 | ContentCard | @components/ui/ContentCard | variant="default" | +| 区块标题 | SectionTitle | @components/ui/SectionTitle | action="更多›" | +| CTA 按钮 | PrimaryButton | @components/ui/PrimaryButton | size="large" | +| 页面容器 | PageShell | @components/ui/PageShell | padding="md" safeBottom | +| 功能卡片 | — | 需新建 | 3 列等宽圆形图标卡片 | +| 轮播 | Swiper | @tarojs/components | autoplay circular | +| TabBar | Taro TabBar | 框架配置 | 原型不实现 | +``` + +来源列使用 `@components/` 前缀,LLM 可直接 `import`。"需新建"标记让 LLM 知道这个需要从头写。 + +#### 组件映射推断规则(H4 修复) + +组件映射通过 DOM 结构特征推断,规则定义在 skill 中: + +| 原型 DOM 特征 | 推断组件 | 匹配依据 | +|--------------|---------|---------| +| 容器 + borderRadius + boxShadow + 内含标题+正文 | ContentCard | 卡片结构 | +| 容器 + 左侧竖线装饰 + 标题文本 + 可选右侧链接 | SectionTitle | 区块标题结构 | +| 按钮 + 主色背景 + 白色文字 + 高度 44-56px | PrimaryButton | CTA 按钮特征 | +| 按钮 + 边框 + 透明背景 | SecondaryButton | 次按钮特征 | +| 页面最外层容器 + padding + 滚动 | PageShell | 页面壳结构 | +| 圆形小容器 + 内含数字/文字 + 配色背景 | StatusTag | 标签/徽章 | +| 列表行 + 左图标 + 中间双行文字 + 右箭头 | ListItem | 列表项结构 | +| 未匹配上述任何规则 | — (需新建) | 标记为"需新建" | + +推断失败时默认标记为"需新建",不强制匹配错误组件。 + +### 3.6 交互规格章节 + +五列表格,覆盖触发 → 反馈 → 生命周期: + +```markdown +## 四、交互规格 + +| 元素 | 交互 | 触发 | 反馈 | 备注 | +|------|------|------|------|------| +| 轮播 | 自动播放+滑动 | 页面显示 | 3s 切换 | useDidShow 启动,useDidHide 暂停 | +| 文章卡片 | 点击跳转 | onPress | activeFeedback="bg" | 跳转文章详情 | +| CTA 按钮 | 点击登录 | onPress | loading 态 | 跳转登录页 | +| 功能卡片 | 点击跳转 | onPress | opacity 反馈 | 各卡跳不同服务页 | +| 资讯区域 | 下拉刷新 | onPullDownRefresh | — | 无数据时显示功能引导 | +``` + +### 3.7 状态变体章节 + +覆盖边界场景,避免实现时遗漏: + +```markdown +## 五、状态变体 + +- **无数据**: 资讯为空 → 显示功能引导卡片(3 个固定入口) +- **加载中**: LoadingCard layout="card" count=2 +- **已登录**: 自动跳转 HomeDashboard(不由本页处理) +- **网络错误**: ErrorState + onRetry 重新加载 +``` + +### 3.8 样式清单章节 + +Token 维度的汇总,便于 LLM 快速查表: + +```markdown +## 六、样式清单 + +间距: 页面 padding 20px (--tk-gap-lg), 卡片 padding 16px (--tk-gap-md) +字号: 标题 18px (--tk-font-nav), 正文 15px (硬编码), 摘要 13px (--tk-font-cap) +圆角: 卡片 16px (--tk-card-radius), 按钮 14px (--tk-radius-lg) +阴影: 卡片 (--tk-shadow-sm), 按钮 (--tk-shadow-btn) +``` + +--- + +## 4. Token 映射系统 + +### 4.1 全局配置文件 + +项目级配置 `.design/tokens.yml`,所有原型共享。**首次运行自动生成**,从项目代码库扫描: + +- `styles/tokens.scss` → CSS 运行时 Token(`--tk-*`) +- `styles/variables.scss` → SCSS 编译时变量(`$pri`, `$r` 等) + +生成后可手动微调,后续运行直接读取。 + +配置文件结构分为五个 Token 类别 + 一个别名映射区: + +```yaml +# .design/tokens.yml + +version: 1 +updated: 2026-05-17 + +# 色彩 Token — 从 tokens.scss 中 --tk-pri 等提取 +colors: + - token: --tk-pri + value: "#C4623A" + - token: --tk-pri-l + value: "#F0DDD4" + # ... + +# 字号 Token — 从 tokens.scss 中 --tk-font-* 提取 +typography: + - token: --tk-font-h1 + value: "28px" + note: 页面标题 serif bold + # ... + +# 间距 Token — 从 tokens.scss 中 --tk-gap-* 提取 +spacing: + - token: --tk-gap-md + value: "16px" + # ... + +# 圆角 Token — 从 tokens.scss 中 --tk-card-radius 等提取 +radius: + - token: --tk-card-radius + value: "16px" + # ... + +# 阴影 Token — 从 tokens.scss 中 --tk-shadow-* 提取 +shadow: + - token: --tk-shadow-sm + value: "0 1px 4px rgba(45,42,38,0.04)" + # ... + +# 原型 Token 别名 — 手动或自动积累的映射 +aliases: + prototype_keys: + T.pri: --tk-pri + T.priL: --tk-pri-l + T.bg: --tk-bg-base + T.card: --tk-bg-card + T.r: --tk-card-radius + T.rSm: --tk-radius-sm + # ... +``` + +### 4.2 三层匹配算法 + +从原型 HTML 中提取的每个 Token 引用,按以下优先级匹配: + +``` +优先级 1: 别名直查 + 原型中 T.pri → 查 aliases.prototype_keys → --tk-pri ✅ + 预估覆盖率: 最高(已有 aliases 的常用 Token) + 特点: 零歧义,直接确认 + +优先级 2: 值精确匹配(按 CSS 属性上下文消歧) + 原型中 #C4623A → 仅在 colors 类别中查找 → --tk-pri ✅ + 原型中 borderRadius: 16px → 仅在 radius 类别中查找 → --tk-card-radius ✅ + 原型中 padding: 16px → 仅在 spacing 类别中查找 → --tk-gap-md ✅ + 预估覆盖率: 中等(值恰好一致的 Token) + 特点: 按 CSS 属性类别过滤,避免同值多 Token 歧义 + +优先级 3: 色彩模糊匹配(仅颜色类) + 原型中 #C4623B → 与 colors 中各值算色差 ΔE + ΔE < 3 → 视为近似色 → ⚠️ pending 待确认 + 预估覆盖率: 较低(设计迭代中微调的色值) + 特点: 仅限色彩,必须人工确认 + +未匹配: ❌ unmatched + 无任何匹配结果 + 需人工决定: 新建 Token / 硬编码 / 忽略 +``` + +**值归一化规则(M4)**: +- `"16px"` 和 `"16"` 视为相同(去除 px 后缀比较数值) +- `"#C4623A"` 和 `"c4623a"` 视为相同(大小写不敏感) +- 不支持 `rem`/`em` 转换,遇此单位标记为 pending + +### 4.3 首次运行流程 + +``` +/design-handoff docs/design/mp-00-visitor.html + +Step 0: 格式校验(C3 弹性检查) + - 检查文件是否为合法 HTML + - 检查是否包含 React/Babel 脚本标签(huashu-design 特征) + - 检查是否包含 T 对象定义(const T = { ... }) + - 任何检查失败 → 输出清晰错误信息并终止 + - 缺少 T 对象: "未检测到设计 Token 对象 (T),请确认是否为 huashu-design 产物" + - 缺少 React: "未检测到 React 渲染环境,无法解析原型" + +Step 1: 检查 .design/tokens.yml + → 不存在: 自动扫描项目 SCSS 文件生成 + - 解析 tokens.scss → 提取所有 --tk-* 变量及值 + - 解析 variables.scss → 提取所有 $ 变量及值 + - 生成 .design/tokens.yml + → 存在: 直接使用 + +Step 2: 解析 HTML 中的 Token(C2 提取方式) + 方式: 静态解析 T 对象定义 + 内联 style 值 + - 用正则匹配 "const T = { ... }" 提取完整对象定义 + - 解析 T 对象的每个 key-value 对(如 T.pri: '#C4623A') + - 扫描内联 style 中的硬编码值: fontSize, padding, borderRadius 等 + - 不依赖 React 运行时,纯文本解析 + - 值归一化规则: "16px" 和 "16" 视为相同; 不支持 rem/em 转换 + +Step 3: 三层匹配 + - 匹配时按 CSS 属性上下文消歧(H1 修复) + - borderRadius / radius 相关属性 → 只匹配 radius 类别 Token + - padding / margin / gap → 只匹配 spacing 类别 Token + - fontSize / fontWeight → 只匹配 typography 类别 Token + - color / background → 匹配 colors 类别 Token + - 如果同值多 Token 且无属性上下文 → 标记为 ⚠️ pending + - 产出 tokens.json + +Step 4: 处理未确认项 + - pending + unmatched 项在终端列表展示 + - 用户确认后更新 aliases + - 后续原型自动复用已确认的映射 +``` + +### 4.3.1 .design/ 目录管理 + +- `.design/` 目录**提交到 git**(tokens.yml 是团队共享的项目级配置) +- `.design/config.yml`(个人偏好配置,如 auto_handoff)可加入 `.gitignore` +- 首次生成 tokens.yml 后建议立即提交,作为团队基线 + +### 4.4 产出文件 `tokens.json` + +```json +{ + "source": "mp-00-visitor.html", + "platform": "miniprogram", + "generated": "2026-05-17T10:30:00Z", + "tokens": { + "T.pri": { + "value": "#C4623A", + "mapped": "var(--tk-pri)", + "match": "alias", + "status": "confirmed" + }, + "fontSize:32": { + "value": "32px", + "closest": "var(--tk-font-h1)", + "closestValue": "28px", + "match": "fuzzy", + "status": "pending", + "note": "原型 32px vs 最近 Token 28px (--tk-font-h1),差 4px" + }, + "T.serif": { + "value": "Georgia, 'Times New Roman', serif", + "mapped": null, + "match": "none", + "status": "unmatched", + "note": "项目无字体族 Token,需硬编码或新建" + } + }, + "summary": { + "total": 18, + "confirmed": 13, + "pending": 2, + "unmatched": 3 + } +} +``` + +**字段说明**: + +| 字段 | 说明 | +|------|------| +| `value` | 原型中的原始值 | +| `mapped` | 匹配到的项目 Token(null 表示未匹配) | +| `match` | 匹配方式:alias / exact / fuzzy / none | +| `status` | 状态:confirmed / pending / unmatched | +| `note` | 补充说明(模糊匹配的差异原因等) | + +### 4.5 映射积累效应 + +每次运行 `/design-handoff` 并确认 pending 项后,映射自动写入 `aliases`: + +``` +第 1 次运行: 13 confirmed + 2 pending + 3 unmatched + → 确认 2 个 pending → aliases 新增 2 条 +第 2 次运行: 15 confirmed + 1 pending + 2 unmatched + → 确认 1 个 pending → aliases 新增 1 条 +第 N 次运行: 18 confirmed + 0 pending + 0 unmatched + → 全部自动匹配,无需人工干预 +``` + +随着项目推进,手动确认越来越少,最终趋近全自动。 + +--- + +## 5. 截图提取与交互推断 + +### 5.1 截图提取 + +#### 5.1.1 提取流程 + +使用 Playwright(huashu-design 已内置 `verify.py` 依赖 Playwright): + +``` +1. Playwright 启动浏览器,打开 HTML 原型文件 +2. 等待 React + Babel 渲染完成(检测 #root 内有子元素) +3. 遍历所有 IosFrame 实例,逐个截图 +4. 裁剪设备框(12px padding + 54px 状态栏 + 34px 底部指示器) +5. 输出到 screenshots/ 目录 +``` + +#### 5.1.2 IosFrame 定位规则 + +原型 HTML 中 `screen-label` 和 `IosFrame` 是**兄弟节点**,不是父子关系: + +```html +
+ 访客首页 — 完整页 + + + +
+``` + +定位方式: + +``` +1. 查找所有 class="screen-wrap" 的容器 +2. 每个容器内: + - 的文本 → 截图文件名 + - 实例 → 截图目标 +3. 如果容器无 screen-label → 使用序号命名 (screen-1.png, screen-2.png) +``` + +如果原型未使用 screen-wrap + IosFrame 结构,进入降级方案(见 5.1.4)。 + +#### 5.1.3 文件命名规则 + +| 规则 | 示例 | +|------|------| +| 使用 screen-label 文本 | `访客首页 — 完整页` → `home.png` | +| 轮播编号保留 | `访客首页 — 轮播 1` → `home-slide-1.png` | +| 中文翻译为英文简写 | `我的` → `profile`, `登录` → `login` | +| 同名页面加序号 | `home-2.png`, `home-3.png` | + +翻译映射表内置在 skill 中(覆盖项目已有的 20 个原型中出现的所有中文标签)。 + +#### 5.1.4 降级方案 + +当原型未使用 IosFrame 时(如早期部分原型或 Web 端原型): + +1. **整页截图**:截取完整渲染页面 +2. **手动标注**:用户通过 `--crop "x,y,w,h"` 参数指定裁剪区域 +3. **CSS 选择器**:通过 `--selector ".screen-content"` 指定截图目标 + +### 5.2 交互推断 + +#### 5.2.1 推断策略 + +从原型 HTML 的 DOM 结构推断交互行为。不做运行时分析,纯静态代码模式匹配。 + +推断规则存储在 `rules/interaction-rules.yml` 中,按 DOM 模式匹配。 + +#### 5.2.2 内置规则表 + +```yaml +# rules/interaction-rules.yml + +rules: + - id: swiper-autoplay + name: 自动轮播 + 手动滑动 + patterns: + - "连续多个子元素含渐变背景 + 底部指示点(宽窄交替)" + - "Swiper / swiper / carousel 关键词" + infer: + interaction: "自动播放 + 手动滑动" + trigger: "页面显示" + feedback: "3s 间隔自动切换" + lifecycle: "useDidShow 启动,useDidHide 暂停" + confidence: high + + - id: card-tap + name: 卡片点击跳转 + patterns: + - "卡片容器含标题+摘要+配图" + - "onClick / onPress 事件" + infer: + interaction: "点击跳转" + trigger: "onPress" + feedback: "activeFeedback 按组件类型自动选择" + confidence: high + + - id: form-submit + name: 表单提交 + patterns: + - "输入框 + 按钮在同一容器内" + - "input / textarea + button 组合" + infer: + interaction: "表单提交" + trigger: "onPress(按钮)" + feedback: "loading 态 + 禁用重复提交 + 错误提示" + confidence: high + + - id: list-scroll + name: 列表滚动加载 + patterns: + - "列表容器含多个重复结构子元素" + - "overflow: auto / scroll" + - "3 个以上同类卡片连续排列" + infer: + interaction: "下拉刷新 + 上拉加载" + trigger: "onPullDownRefresh / onReachBottom" + feedback: "分页加载" + confidence: medium + + - id: tab-switch + name: 标签页切换 + patterns: + - "多个平行区域 + 选中态样式差异" + - "tab / segment / filter 关键词" + - "一组按钮样元素,其中一个高亮" + infer: + interaction: "标签页切换" + trigger: "onPress(tab 项)" + feedback: "选中态样式切换 + 内容区切换" + confidence: high + + - id: static-decoration + name: 静态装饰 + patterns: + - "渐变背景 + 装饰圆/波浪/几何图形" + - "无事件绑定" + - "position: absolute + opacity < 1" + infer: + interaction: "无(纯装饰)" + trigger: "—" + feedback: "—" + confidence: high + + - id: login-cta + name: 登录/注册 CTA + patterns: + - "按钮含 '登录' / '注册' / '立即' 文案" + - "主色按钮 + 阴影" + infer: + interaction: "点击触发登录流程" + trigger: "onPress" + feedback: "PrimaryButton loading 态" + guard: "GuestGuard 拦截未登录态" + confidence: high + + - id: empty-fallback + name: 空数据降级 + patterns: + - "条件渲染: data.length > 0 ? ... : ..." + - "占位符文字 '暂无' / '空'" + - "硬编码的固定内容(非 API 数据)" + infer: + interaction: "无数据时显示替代内容" + trigger: "数据加载完成且为空" + feedback: "EmptyState 组件或固定引导卡片" + confidence: medium +``` + +#### 5.2.3 置信度分级 + +| 级别 | 阈值 | 处理方式 | +|------|------|---------| +| **high** (≥80%) | 结构明确,模式唯一 | 直接写入 SPEC.md 交互规格表 | +| **medium** (50-80%) | 结构合理但有歧义 | 写入交互规格表,标注 `⚠️ 待确认` | +| **low** (<50%) | 模式模糊,可能是静态 | 不写入交互规格,末尾单独列出 `## 待人工补充的交互` | + +#### 5.2.4 推断结果示例 + +对 `mp-00-visitor.html` 的推断输出: + +| 元素 | 推断交互 | 置信度 | 来源规则 | +|------|---------|--------|---------| +| 轮播区域 | 自动播放 3s + 手动滑动 | high | swiper-autoplay | +| 文章卡片 ×2 | 点击跳转详情 | high | card-tap | +| "更多›" 链接 | 点击跳转列表页 | high | card-tap | +| 功能卡片 ×3 | 点击跳转各服务页 | high | card-tap | +| CTA"立即登录" | 点击触发登录 | high | login-cta | +| TabBar 占位 | 无(小程序原生处理) | high | static-decoration | +| 资讯为空降级 | 显示功能引导卡片 | medium | empty-fallback | + +### 5.3 截图与规格的关联机制 + +SPEC.md 中每个页面结构块都以对应截图开头,形成视觉-规格一一对应: + +```markdown +### 2.1 访客首页 + +![首页全貌](./screenshots/home.png) ← LLM 读到此处时看到截图 + +布局层级(从上到下): ← 对照截图理解每层 +1. 轮播区域 (height: 280px) ... ← 截图中顶部渐变区域 +2. 健康资讯 (padding: 20px) ... ← 截图中卡片列表区域 +3. 登录引导 ... ← 截图中底部 CTA 按钮 +``` + +LLM 无需在多个文件间跳转。**一个 SPEC.md = 视觉 + 结构 + 映射 + 交互的全景图**。 + +--- + +## 6. 多平台支持与集成 + +### 6.1 多平台 Token 映射 + +`tokens.yml` 中的每个 Token 支持按平台指定不同变量名和引用方式: + +```yaml +# 单平台通用(大多数 Token,值和引用方式相同) +- token: --tk-pri + value: "#C4623A" + # 无 platforms 字段 → 所有平台统一用 var(--tk-pri) + +# 多平台差异映射(值相同,引用方式不同) +- token: --tk-pri + value: "#C4623A" + platforms: + miniprogram: "var(--tk-pri)" + web: "token('color.primary')" # Ant Design Token 系统 + h5: "var(--color-primary)" # H5 独立 CSS 变量体系 +``` + +运行时按 `--platform` 参数展开对应平台的映射: + +```bash +# 默认展开 miniprogram 映射 +/design-handoff mp-00-visitor.html + +# 展开 web 映射 +/design-handoff mp-00-visitor.html --platform web +``` + +SPEC.md 和 tokens.json 中仅包含目标平台的映射,不混淆。 + +### 6.2 多平台组件映射 + +可选配置文件 `.design/components.yml`,定义各平台的组件对应关系: + +```yaml +# .design/components.yml — 自动扫描生成,可手动调整 + +components: + ContentCard: + miniprogram: + import: "@components/ui/ContentCard" + props: "variant, padding, margin, activeFeedback, onPress, className" + web: + import: "@app/components/ContentCard" + props: "variant, padding, margin, onClick, className" + + PrimaryButton: + miniprogram: + import: "@components/ui/PrimaryButton" + props: "children, onClick, disabled, loading, size" + web: + import: "antd/es/button" + props: "children, onClick, disabled, loading, type='primary'" + + PageShell: + miniprogram: + import: "@components/ui/PageShell" + props: "padding, safeBottom, scroll, className" + web: + import: "@app/layouts/PageLayout" + props: "padding, children, className" + + Swiper: + miniprogram: + import: "@tarojs/components" + tag: "Swiper" + web: + import: "antd/es/carousel" + tag: "Carousel" + + TabBar: + miniprogram: + type: "framework-config" + note: "小程序原生 TabBar,在 app.config.ts 配置" + web: + import: "@app/components/AppTabBar" + note: "Web 端自定义 TabBar 组件" +``` + +**扫描生成规则**: +- `miniprogram` → 扫描 `apps/miniprogram/src/components/ui/` 目录 +- `web` → 扫描 `apps/desktop/src/components/` 或对应 Web 目录 +- 只在对应平台目录存在时才生成映射 + +### 6.3 huashu-design 协作方式 + +两种集成方式,**不修改 huashu-design 本身**: + +#### 方式 A:串行调用(默认) + +``` +会话 1: /huashu-design → 原型 HTML(设计阶段) +会话 2: /design-handoff mp-00-visitor.html → 交付包(规格化) +会话 3: "按 SPEC.md 实现" → Taro/React 代码(实施阶段) +``` + +三个阶段各自独立,松耦合。用户按需调用。 + +#### 方式 B:自动追加(可选配置) + +在 `.design/config.yml` 中开启: + +```yaml +# .design/config.yml +auto_handoff: true +``` + +开启后,huashu-design 完成原型写入时,skill 检测到 `auto_handoff: true`,自动提示: + +``` +原型已生成: docs/design/mp-00-visitor.html +检测到 auto_handoff 开启。是否自动生成设计交付包? + → 是: 立即运行 /design-handoff + → 否: 跳过,稍后手动调用 +``` + +### 6.4 实施 Prompt 模板 + +交付包生成完毕后,skill 在终端输出推荐 prompt,用户直接复制到新会话: + +``` +📋 实施时复制以下 prompt 到新会话: + +请按设计规格实现「访客首页」页面。 + +设计规格: docs/design/mp-00-visitor/SPEC.md +Token 映射: docs/design/mp-00-visitor/tokens.json +目标平台: miniprogram (Taro + React) +组件库: apps/miniprogram/src/components/ui/ +样式 Token: apps/miniprogram/src/styles/tokens.scss + +要求: +1. 先阅读 SPEC.md 中的截图和规格 +2. 按组件映射表使用现有组件,不要用 div 堆砌 +3. 按 Token 映射表使用 CSS 变量,不要硬编码颜色值 +4. 按交互规格表实现所有交互行为 +5. 覆盖所有状态变体(加载/空数据/错误) +``` + +注意:SPEC.md 中的截图路径为相对路径(`./screenshots/home.png`),LLM 应使用项目根目录的绝对路径读取(如 `docs/design/mp-00-visitor/screenshots/home.png`)。 + +#### 批处理索引(M3 修复) + +批量运行时(`/design-handoff docs/design/mp-*.html`),完成后自动在 `docs/design/` 下生成 `INDEX.md`: + +```markdown +# 设计交付包索引 + +> 生成时间: 2026-05-17 | 原型数: 20 + +| 原型 | 交付包 | 页面数 | 未匹配 Token | +|------|--------|--------|-------------| +| mp-00-visitor | [SPEC](mp-00-visitor/SPEC.md) | 2 | 2 | +| mp-01-login | [SPEC](mp-01-login/SPEC.md) | 2 | 1 | +| ... | ... | ... | ... | +``` + +LLM 可通过 `docs/design/INDEX.md` 定位所需的交付包。 + +### 6.5 META.yml 元数据 + +每个交付包的元数据文件: + +```yaml +# docs/design/mp-00-visitor/META.yml + +source: mp-00-visitor.html +platform: miniprogram +generated: 2026-05-17T10:30:00Z +generator: design-handoff v1 + +pages: + - name: 访客首页 + screenshot: screenshots/home.png + route: pages/index/index + - name: 访客"我的" + screenshot: screenshots/profile.png + route: pages/profile/index + +tokens: + total: 18 + confirmed: 13 + pending: 2 + unmatched: 3 + +components: + reused: 5 # 已有组件 + new: 1 # 需新建组件 + +interactions: + total: 7 + high_confidence: 6 + medium_confidence: 1 +``` + +--- + +## 附录 A. 交互推断规则表 + +完整内置规则清单(见 §5.2.2),共 8 条: + +| 规则 ID | 名称 | 置信度 | 匹配模式 | +|---------|------|--------|---------| +| swiper-autoplay | 自动轮播 + 手动滑动 | high | 渐变背景 + 指示点宽窄交替 | +| card-tap | 卡片点击跳转 | high | 卡片容器 + 标题摘要配图 | +| form-submit | 表单提交 | high | 输入框 + 按钮同容器 | +| list-scroll | 列表滚动加载 | medium | 3+ 同类卡片连续排列 | +| tab-switch | 标签页切换 | high | 平行区域 + 选中态差异 | +| static-decoration | 静态装饰 | high | 渐变 + 装饰圆 + 无事件 | +| login-cta | 登录/注册 CTA | high | 按钮含登录文案 + 主色 | +| empty-fallback | 空数据降级 | medium | 条件渲染 / 占位符文字 | + +扩展方式:在 `rules/interaction-rules.yml` 中追加新规则即可。 + +## 附录 B. Token 配置文件完整模板 + +见 §4.1 的 `.design/tokens.yml` 结构。首次运行时自动从以下文件扫描生成: + +| 源文件 | 提取内容 | +|--------|---------| +| `styles/tokens.scss` | 所有 `--tk-*` CSS 变量 | +| `styles/variables.scss` | 所有 `$` SCSS 变量 | +| `styles/mixins.scss` | mixin 名称和用途(用于注释) | + +## 附录 C. SPEC.md 完整示例(mp-00-visitor) + +见 §3.2 - §3.8 的完整格式定义。以 mp-00-visitor 为实际案例,涵盖: + +- 5 张截图(首页全貌 + 3 张轮播 + 我的页) +- 15+ Token 映射(11 confirmed + 2 pending + 2 unmatched) +- 7 个组件映射(5 复用 + 1 新建 + 1 框架配置) +- 7 条交互规格(6 high + 1 medium) +- 4 个状态变体(加载 / 空数据 / 已登录 / 错误)