# 技术设计文档:H5 → 微信小程序批量迁移 ## 1. 概述 本设计文档基于 33 条需求(`requirements.md`),为 17 个 H5 原型页面迁移到微信小程序提供技术实现方案。权威参考:`docs/prd/MIGRATION-PLAYBOOK.md`。 核心约束: - 纯前端迁移,不涉及后端 API 或数据库变更 - 输入物分两批:第一批(结构迁移 Step 1-5)、第二批(像素精调 Step 6-7) - 迁移粒度:以"屏/交互态"为最小单位,非整页 ## 2. 架构总览 ### 2.1 目录结构(现有 + 新增) ``` apps/miniprogram/miniprogram/ ├── app.json / app.ts / app.wxss # 全局配置与样式 ├── assets/icons/ # SVG 图标(已有 + 新导出) ├── components/ # 共享组件 │ ├── ai-float-button/ # ✅ 已有 │ ├── board-tab-bar/ # ✅ 已有 │ ├── filter-dropdown/ # ✅ 已有 │ ├── heart-icon/ # ✅ 已有 │ ├── star-rating/ # ✅ 已有 │ ├── note-modal/ # ✅ 已有 │ ├── metric-card/ # ✅ 已有 │ ├── hobby-tag/ # ✅ 已有 │ ├── banner/ # ✅ 已有 │ └── dev-fab/ # ✅ 已有 ├── pages/ # 页面目录(17 个迁移目标) │ ├── board-finance/ # A 批次 - 看板 │ ├── board-coach/ │ ├── board-customer/ │ ├── task-list/ # B 批次 - 核心 │ ├── my-profile/ │ ├── task-detail/ # C 批次 - 任务详情 │ ├── task-detail-callback/ │ ├── task-detail-priority/ │ ├── task-detail-relationship/ │ ├── coach-detail/ # D 批次 - 详情 │ ├── customer-detail/ │ ├── customer-service-records/ │ ├── performance/ # E 批次 - 绩效 │ ├── performance-records/ │ ├── chat/ # F 批次 - 对话 │ ├── chat-history/ │ └── notes/ # G 批次 - 其他 └── utils/ # 工具模块 ├── ai-color.ts # 🆕 AI 图标随机配色 ├── format.wxs # ✅ 已有 WXS 格式化 ├── request.ts # ✅ 已有 网络请求 ├── router.ts # ✅ 已有 路由工具 └── ... # 其他已有工具 ``` ### 2.2 页面四文件结构 每个页面输出标准四文件: ``` pages// ├── .wxml # 视图模板 ├── .wxss # 样式 ├── .ts # 逻辑(TypeScript) └── .json # 页面配置(usingComponents) ``` ## 3. 单页迁移工作流设计 ### 3.1 工作流总览(7 步 + 屏级粒度) 迁移以"屏/交互态"为最小工作单位,而非整页。每个页面的迁移流程: ``` Step 0: 页面分析(确认屏数、子页面、变种、工作量) ↓ Step 1: 输入物冻结(第一批:结构材料) ↓ Step 2: 迁移审计报告(7 项审计,不写代码) ↓ Step 3: 规则化转换(按屏逐个开发) ↓ Step 4: 编译验证(7 项检查) ↓ Step 5: 结构还原验证(9 项核对,按屏逐个验证) ↓ ── 第二批输入物补充(截图 + computed-styles)── ↓ Step 6: 像素级对比(按屏逐段对比 + 微调循环) ↓ Step 7: 验收签收(12 项清单) ``` ### 3.2 Step 0:页面分析(新增步骤) 在正式迁移前,先对目标页面做结构分析: 1. 打开 H5 原型截图 + 交互说明文档 2. 确认页面总长度(几个屏?) 3. 识别子页面/变种(如 task-detail 的 3 个主题色变体) 4. 列出所有交互态(弹窗、筛选展开、空状态等) 5. 输出工作量估算表: ``` | 单位 | 类型 | 描述 | 预估复杂度 | |------|------|------|-----------| | 屏-1 | 默认态首屏 | Banner + 筛选栏 + 第一板块 | 中 | | 屏-2 | 默认态第二屏 | 第二~三板块 | 中 | | 交互-1 | 筛选下拉 | 时间筛选 + 区域筛选 | 低 | | 交互-2 | 指标弹窗 | 长按指标卡片 | 低 | | ... | ... | ... | ... | ``` ### 3.3 Step 3:按屏逐个开发 规则化转换不是一次性写完整页,而是按屏/交互态逐个推进: 1. 先完成首屏结构 → 编译通过 → 截图粗看 2. 再完成第二屏 → 编译通过 → 截图粗看 3. 所有屏完成后 → 处理交互态(弹窗、筛选等) 4. 最后处理三态(loading/empty/error) ### 3.4 Step 5:按屏逐个验证 结构还原验证同样按屏进行: 1. 截取小程序当前屏 → 与 H5 原型截图粗略比对 2. 9 项核对清单逐项确认 3. 通过 → 下一屏;未通过 → 修复后重新验证 4. 所有屏 + 所有交互态全部通过 → 进入 Step 6 ### 3.5 差异率过大的处理策略 当 Step 6 像素对比差异率 > 15% 且多轮微调无法收敛时: - 放弃修补,从零重写该页面(需求 2 第 5 条) - 复杂 Banner 背景 → 导出为 SVG,`` 引用(需求 2 第 6 条) - 复杂 Icon → 导出为 SVG,`` 引用(需求 2 第 7 条) ## 4. 样式转换系统设计 ### 4.1 缩放公式 ``` rpx = H5 px × 2 × 0.875 结果取偶数(向最近偶数取整) ``` 特例:`borderRadius` 使用简单 ×2(A/B 对比验证差异 < 0.02%)。 ### 4.2 design-tokens 映射 全局设计令牌来自 `docs/h5_ui/design-tokens.json`,直接映射到 WXSS 变量: | Token | 值 | 用途 | |-------|-----|------| | fontSize.xs | 22rpx | 辅助文字 | | fontSize.sm | 24rpx | 次要文字 | | fontSize.base | 28rpx | 正文 | | fontSize.lg | 32rpx | 小标题 | | fontSize.xl | 36rpx | 标题 | | fontSize.2xl | 42rpx | 大标题 | | borderRadius.sm | 8rpx | 小圆角 | | borderRadius.md | 16rpx | 中圆角 | | borderRadius.lg | 24rpx | 大圆角 | | borderRadius.xl | 32rpx | 特大圆角 | | borderRadius.3xl | 48rpx | 全圆角 | 颜色使用 `colors` 中定义的灰阶(gray-1 ~ gray-13),禁止使用 `#333`、`#666`、`#999` 等非标准灰色。 ### 4.3 两阶段样式数据源 **结构迁移阶段(Step 3)**: 1. H5 源码 Tailwind 类名 → 查速查表换算 2. design-tokens.json Token 值 3. 目测估算(必须标注 `/* 目测值,待校准 */`) **像素精调阶段(Step 6)**: 1. computed-styles.json 精确 px 值(最高优先级) 2. H5 源码 Tailwind 类名 3. design-tokens.json 4. H5 截图目测(最低) ### 4.4 七维度核对 每个可见元素写 WXSS 时逐项确认: 1. font-size 2. font-weight 3. color 4. line-height(必须显式写出) 5. padding 6. margin / gap 7. border / border-radius ## 5. AI 图标配色系统设计 ### 5.1 配色方案定义 6 种配色,每种 4 个 CSS 变量: ```typescript // utils/ai-color.ts const AI_COLOR_SCHEMES = { red: { from: '#e74c3c', to: '#f39c9c', fromDeep: '#c0392b', toDeep: '#e74c3c' }, orange: { from: '#e67e22', to: '#f5c77e', fromDeep: '#ca6c17', toDeep: '#e67e22' }, yellow: { from: '#d4a017', to: '#f7dc6f', fromDeep: '#b8860b', toDeep: '#d4a017' }, blue: { from: '#2980b9', to: '#7ec8e3', fromDeep: '#1a5276', toDeep: '#2980b9' }, indigo: { from: '#667eea', to: '#a78bfa', fromDeep: '#4a5fc7', toDeep: '#667eea' }, purple: { from: '#764ba2', to: '#c084fc', fromDeep: '#5b3080', toDeep: '#764ba2' }, }; ``` ### 5.2 小程序实现方案 H5 通过 DOM `querySelectorAll` + `classList.add` 实现随机配色。小程序无 DOM API,改用 `setData` + 条件样式: ```typescript // 页面 onLoad 中调用 import { getRandomAiColor } from '../../utils/ai-color'; Page({ data: { aiColorClass: '', // 'ai-color-red' | 'ai-color-orange' | ... aiColorVars: {}, // CSS 变量值对象 }, onLoad() { const color = getRandomAiColor(); this.setData({ aiColorClass: color.className, aiColorVars: color.vars, }); }, }); ``` ```xml AI 推荐 ``` ### 5.3 两个系列的 WXSS 实现 **ai-inline-icon**(行首小图标,28rpx): - 渐变背景 + 白色机器人 SVG - 微光扫过动画(12s 周期 `ai-shimmer`) - 尺寸:28rpx × 28rpx(H5 16px × 2 × 0.875 ≈ 28) **ai-title-badge**(标题行右侧标识): - 浅色背景 + 主题色文字 + 主题色边框 - 呼吸脉冲动画(3s 周期 `ai-pulse`) - 高光扫过动画(14s 周期 `ai-shimmer`) ### 5.4 ai-float-button 排除 `ai-float-button` 组件已有固定渐变动画(`#667eea → #764ba2 → #f093fb → #f5576c`),不参与页面级随机配色。无需修改。 ### 5.5 机器人 SVG 复用 - 大系列(ai-title-badge):复用已有 `assets/icons/ai-robot.svg` - 小系列(ai-inline-icon):从 H5 源码导出白色填充版本,保存为 `assets/icons/ai-robot-sm.svg` ## 6. 共享组件设计 ### 6.1 已有组件(直接复用) | 组件 | 路径 | 用途 | 使用页面 | |------|------|------|---------| | ai-float-button | components/ai-float-button/ | AI 悬浮按钮 | 所有业务页面 | | board-tab-bar | components/board-tab-bar/ | 自定义底部导航 | board-coach, board-customer | | filter-dropdown | components/filter-dropdown/ | 筛选下拉面板 | board-finance/coach/customer | | heart-icon | components/heart-icon/ | 心形评分 | board-customer | | star-rating | components/star-rating/ | 星级评价 | notes | | note-modal | components/note-modal/ | 备注弹窗 | task-list/detail, coach-detail | | metric-card | components/metric-card/ | 指标卡片 | board-finance, performance | | hobby-tag | components/hobby-tag/ | 爱好标签 | board-customer, customer-detail | | banner | components/banner/ | 顶部 Banner | task-list, performance | | dev-fab | components/dev-fab/ | 开发调试按钮 | 所有页面(开发环境) | ### 6.2 组件注册规范 每个页面的 `.json` 文件中注册所需组件: ```json { "usingComponents": { "t-button": "tdesign-miniprogram/button/button", "t-icon": "tdesign-miniprogram/icon/icon", "t-loading": "tdesign-miniprogram/loading/loading", "ai-float-button": "/components/ai-float-button/ai-float-button", "filter-dropdown": "/components/filter-dropdown/filter-dropdown" } } ``` ## 7. 事件与路由转换设计 ### 7.1 事件映射表 | H5 | 小程序 | 说明 | |----|--------|------| | `onclick="fn()"` | `bindtap="fn"` | 基础点击 | | `onclick="fn(id)"` | `data-id="{{id}}" bindtap="fn"` | dataset 传参 | | `event.target.value` | `e.detail.value` | 表单取值 | | `event.target.dataset` | `e.currentTarget.dataset` | dataset 取值 | | `event.preventDefault()` | `catchtap` | 阻止冒泡 | | `classList.toggle` | `setData` + 条件 class | 样式切换 | | `innerHTML` | `setData` + WXML 绑定 | 视图更新 | | `history.back()` | `wx.navigateBack()` | 返回 | | `localStorage` | `wx.setStorageSync` | 本地存储 | | `alert()/confirm()` | `wx.showToast()/wx.showModal()` | 弹窗 | | `longpress` | `bindlongpress` | 长按 | ### 7.2 路由规则 | 目标页面类型 | API | 示例 | |-------------|-----|------| | TabBar 页面 | `wx.switchTab` | task-list, board-finance, my-profile | | 普通页面 | `wx.navigateTo` | task-detail, coach-detail, chat | | 重定向 | `wx.redirectTo` | 登录后跳转 | | 返回 | `wx.navigateBack` | 详情页返回 | | 重启 | `wx.reLaunch` | 切换身份 | 路径规则:以 `/` 开头,不带 `.wxml` 后缀。 ## 8. 弹窗与 z-index 分层设计 ### 8.1 全局 z-index 分层 ``` 10-29 sticky 元素(Tab 栏 20, 筛选栏 15) 30 AI 悬浮按钮 50 底部固定操作栏 100 自定义底部导航栏(board-tab-bar) 999 遮罩层 1000 弹窗内容 9999 Toast / Loading ``` ### 8.2 弹窗实现模式 所有弹窗遵循统一模式: - 同一时刻只允许一个弹窗打开(互斥) - 遮罩 `bindtap` 关闭,内容区 `catchtap` 防穿透 - 背景滚动锁定:`catchtouchmove` 在遮罩层 - 底部弹出类添加 `padding-bottom: env(safe-area-inset-bottom)` - 动画统一 200-220ms + `ease` ## 9. 三态处理设计 每个页面统一处理 4 种状态: ```xml 加载失败,请点击重试 重试 {{emptyText}} ``` 各页面空状态文案见需求 14。 ## 10. 像素对比工具链设计 ### 10.1 工具链流程 ``` H5 截图(DPR=3, 1290px 宽) ↓ MP 截图(DPR=1.5, 645px)→ ×2 缩放 → 1290px ↓ pixelmatch 逐像素对比 ↓ 按 150px 条带分析差异密度 ↓ 定位差异区域 → WXSS 微调 → 循环 ``` ### 10.2 逐段对比(v2 方案) 长页面使用 `scripts/ops/anchor_compare.py`: ```bash # 提取 H5 锚点 + 截图 python scripts/ops/anchor_compare.py extract-h5 # 生成 MP 截图指令 python scripts/ops/anchor_compare.py mp-inst # 执行 MP 截图(通过微信开发者工具 MCP) # 逐段配对 + 对比 python scripts/ops/anchor_compare.py compare ``` ### 10.3 scroll-view 页面截图 使用 `scroll-into-view` 模式: 1. `page.setData({ scrollIntoView: '' })` — 清空 2. `page.setData({ scrollIntoView: '' })` — 设目标 3. 等待 1000ms → 截图 ### 10.4 达标标准 - 前半屏差异率 < 5%:优秀 - 前半屏差异率 ≤ 10%:达标 - 前半屏差异率 > 15% 且无法收敛:触发重写 ## 11. task-detail 变体策略 ### 11.1 实现方式 1. 先完成 task-detail 主页面的完整迁移和验收 2. 复制 task-detail 四文件到变体目录 3. 替换主题色变量(banner 背景色、按钮配色) 4. 保持数据结构和布局完全一致 ### 11.2 变体清单 | 变体 | 差异点 | |------|--------| | task-detail-callback | banner 背景色 + 按钮配色(对照 H5 原型校准) | | task-detail-priority | banner 背景色 + 按钮配色(对照 H5 原型校准) | | task-detail-relationship | banner 背景色 + 按钮配色(对照 H5 原型校准) | ## 12. 认证与联调设计 ### 12.1 认证守卫 每个业务页面 `onLoad` 检查登录态: ```typescript onLoad() { const token = wx.getStorageSync('token'); if (!token) { wx.redirectTo({ url: '/pages/login/login' }); return; } // 正常加载逻辑 } ``` ### 12.2 开发联调 - `utils/request.ts` 中 `BASE_URL` 指向 `http://localhost:8000` - 后端 `WX_DEV_MODE=true` 支持 `/api/xcx/dev-login` Mock 登录 - Storage + header token 维持登录态 ## 13. 不支持的 CSS 特性替代方案 | H5 特性 | 小程序替代 | |---------|-----------| | `backdrop-filter: blur()` | `background: rgba(255,255,255,0.95)` | | `*` 通配符选择器 | 逐个元素设置 | | `filter: blur()` | `radial-gradient` 模拟 | | `url("data:image/svg+xml,...")` | CSS 渐变模拟或导出 PNG/base64 | | `::before/::after`(复杂场景) | 额外 `` 模拟 | 直接支持的特性(无需替代):CSS 变量 `var()`、`linear-gradient`、`animation`/`@keyframes`、`transition`。 ## 14. 批次执行顺序与依赖 ``` A-看板(board-finance → board-coach → board-customer) ↓ 共享组件验证完毕 B-核心(task-list → my-profile) ↓ C-任务(task-detail → 3 个变体) ↓ D-详情(coach-detail → customer-detail → customer-service-records) ↓ E-绩效(performance → performance-records) ↓ F-对话(chat → chat-history) ↓ G-其他(notes) ``` A 批次优先:验证共享组件(filter-dropdown、board-tab-bar、metric-card)在实际页面中的表现,为后续批次建立基线。 ## 15. 产出物与中间生成物归档 迁移过程中会产生大量截图、diff 图、逐段对比图等中间文件。所有生成物必须按类型分目录存放,禁止散放在项目根目录或临时位置。 ### 15.1 目录结构 ``` docs/h5_ui/ ├── screenshots/ # H5 原型截图(输入物,已有) │ ├── .png # 默认态截图 │ └── --.png # 交互态截图 ├── mp-screenshots/ # 🆕 小程序截图(迁移过程生成) │ ├── / # 按页面分子目录 │ │ ├── .png # 默认态全屏截图 │ │ ├── --.png # 交互态截图 │ │ └── seg--
.png # 逐段截图(anchor_compare 生成) │ └── ... ├── diffs/ # 🆕 像素对比结果(迁移过程生成) │ ├── / # 按页面分子目录 │ │ ├── diff-.png # 全屏 diff 图 │ │ ├── diff-seg--
.png # 逐段 diff 图 │ │ └── report.md # 该页面的对比结果摘要(差异率、问题区域) │ └── ... ├── h5-segments/ # 🆕 H5 逐段截图(anchor_compare 生成) │ ├── / │ │ └── seg--
.png │ └── ... └── ... ``` ### 15.2 归档规则 | 生成物类型 | 目标目录 | 命名规则 | 说明 | |-----------|---------|---------|------| | H5 原型截图 | `docs/h5_ui/screenshots/` | `.png` / `--.png` | 输入物,已有,不动 | | MP 全屏截图 | `docs/h5_ui/mp-screenshots//` | `.png` / `--.png` | 每轮对比更新覆盖 | | MP 逐段截图 | `docs/h5_ui/mp-screenshots//` | `seg--
.png` | anchor_compare 生成 | | H5 逐段截图 | `docs/h5_ui/h5-segments//` | `seg--
.png` | anchor_compare 生成 | | 全屏 diff 图 | `docs/h5_ui/diffs//` | `diff-.png` | pixelmatch 输出 | | 逐段 diff 图 | `docs/h5_ui/diffs//` | `diff-seg--
.png` | pixelmatch 输出 | | 对比报告 | `docs/h5_ui/diffs//` | `report.md` | 差异率 + 问题区域摘要 | | 新导出 SVG | `assets/icons/` | `icon-<用途>.svg` / `logo-<名称>.svg` | 小程序工程内 | | 图标映射更新 | `docs/h5_ui/icon-mapping.md` | — | 追加新条目 | | 小程序页面代码 | `apps/miniprogram/miniprogram/pages//` | 四文件组合 | 最终交付物 | ### 15.3 管理规则 1. 按页面分子目录:MP 截图、H5 逐段截图、diff 图均按 `/` 分目录,避免数百张图片平铺 2. 每轮覆盖更新:像素精调循环中,每轮新截图覆盖上一轮同名文件,不保留历史版本(git 有历史) 3. 逐段截图编号连续:`seg-0`、`seg-1`、`seg-2`...,与 anchor_compare.py 输出一致 4. report.md 格式统一:每个页面的 `diffs//report.md` 记录最终差异率和遗留问题,作为验收依据 5. .gitignore 不排除:这些中间文件需要入库,便于团队复查和回溯 6. H5 原型截图目录只读:`docs/h5_ui/screenshots/` 是输入物,迁移过程中不往里写 MP 截图或 diff 图