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
+## 页面索引
+
+| 页面 | 截图 | 路由 |
+|------|------|------|
+| 访客首页 |  | pages/index/index |
+| 访客"我的" |  | 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 访客首页
+
+
+
+布局层级(从上到下):
+
+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 访客首页
+
+ ← 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 个状态变体(加载 / 空数据 / 已登录 / 错误)