Files

20 KiB
Raw Permalink Blame History

技术设计文档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/<page>/
├── <page>.wxml      # 视图模板
├── <page>.wxss      # 样式
├── <page>.ts        # 逻辑TypeScript
└── <page>.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<image> 引用(需求 2 第 6 条)
  • 复杂 Icon → 导出为 SVG<image> 引用(需求 2 第 7 条)

4. 样式转换系统设计

4.1 缩放公式

rpx = H5 px × 2 × 0.875
结果取偶数(向最近偶数取整)

特例:borderRadius 使用简单 ×2A/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 变量:

// 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 + 条件样式:

// 页面 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,
    });
  },
});
<!-- WXML 中使用 -->
<view class="ai-inline-icon {{aiColorClass}}">
  <image src="/assets/icons/ai-robot-sm.svg" mode="aspectFit" />
</view>

<view class="ai-title-badge {{aiColorClass}}">
  <view class="ai-title-badge-icon">
    <image src="/assets/icons/ai-robot.svg" mode="aspectFit" />
  </view>
  <text>AI 推荐</text>
</view>

5.3 两个系列的 WXSS 实现

ai-inline-icon行首小图标28rpx

  • 渐变背景 + 白色机器人 SVG
  • 微光扫过动画12s 周期 ai-shimmer
  • 尺寸28rpx × 28rpxH5 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 文件中注册所需组件:

{
  "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 种状态:

<!-- 通用三态模板 -->
<view wx:if="{{pageState === 'loading'}}">
  <t-loading text="加载中..." />
</view>
<view wx:elif="{{pageState === 'error'}}">
  <view class="error-state">
    <text>加载失败,请点击重试</text>
    <t-button bindtap="onRetry">重试</t-button>
  </view>
</view>
<view wx:elif="{{pageState === 'empty'}}">
  <text class="empty-text">{{emptyText}}</text>
</view>
<view wx:else>
  <!-- 正常内容 -->
</view>

各页面空状态文案见需求 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

# 提取 H5 锚点 + 截图
python scripts/ops/anchor_compare.py extract-h5 <page>

# 生成 MP 截图指令
python scripts/ops/anchor_compare.py mp-inst <page>

# 执行 MP 截图(通过微信开发者工具 MCP

# 逐段配对 + 对比
python scripts/ops/anchor_compare.py compare <page>

10.3 scroll-view 页面截图

使用 scroll-into-view 模式:

  1. page.setData({ scrollIntoView: '' }) — 清空
  2. page.setData({ scrollIntoView: '<section-id>' }) — 设目标
  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 检查登录态:

onLoad() {
  const token = wx.getStorageSync('token');
  if (!token) {
    wx.redirectTo({ url: '/pages/login/login' });
    return;
  }
  // 正常加载逻辑
}

12.2 开发联调

  • utils/request.tsBASE_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(复杂场景) 额外 <view> 模拟

直接支持的特性无需替代CSS 变量 var()linear-gradientanimation/@keyframestransition

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 原型截图(输入物,已有)
│   ├── <page>.png                      # 默认态截图
│   └── <page>--<state>.png             # 交互态截图
├── mp-screenshots/                     # 🆕 小程序截图(迁移过程生成)
│   ├── <page>/                         # 按页面分子目录
│   │   ├── <page>.png                  # 默认态全屏截图
│   │   ├── <page>--<state>.png         # 交互态截图
│   │   └── seg-<N>-<section>.png       # 逐段截图anchor_compare 生成)
│   └── ...
├── diffs/                              # 🆕 像素对比结果(迁移过程生成)
│   ├── <page>/                         # 按页面分子目录
│   │   ├── diff-<page>.png             # 全屏 diff 图
│   │   ├── diff-seg-<N>-<section>.png  # 逐段 diff 图
│   │   └── report.md                   # 该页面的对比结果摘要(差异率、问题区域)
│   └── ...
├── h5-segments/                        # 🆕 H5 逐段截图anchor_compare 生成)
│   ├── <page>/
│   │   └── seg-<N>-<section>.png
│   └── ...
└── ...

15.2 归档规则

生成物类型 目标目录 命名规则 说明
H5 原型截图 docs/h5_ui/screenshots/ <page>.png / <page>--<state>.png 输入物,已有,不动
MP 全屏截图 docs/h5_ui/mp-screenshots/<page>/ <page>.png / <page>--<state>.png 每轮对比更新覆盖
MP 逐段截图 docs/h5_ui/mp-screenshots/<page>/ seg-<N>-<section>.png anchor_compare 生成
H5 逐段截图 docs/h5_ui/h5-segments/<page>/ seg-<N>-<section>.png anchor_compare 生成
全屏 diff 图 docs/h5_ui/diffs/<page>/ diff-<page>.png pixelmatch 输出
逐段 diff 图 docs/h5_ui/diffs/<page>/ diff-seg-<N>-<section>.png pixelmatch 输出
对比报告 docs/h5_ui/diffs/<page>/ report.md 差异率 + 问题区域摘要
新导出 SVG assets/icons/ icon-<用途>.svg / logo-<名称>.svg 小程序工程内
图标映射更新 docs/h5_ui/icon-mapping.md 追加新条目
小程序页面代码 apps/miniprogram/miniprogram/pages/<page>/ 四文件组合 最终交付物

15.3 管理规则

  1. 按页面分子目录MP 截图、H5 逐段截图、diff 图均按 <page>/ 分目录,避免数百张图片平铺
  2. 每轮覆盖更新像素精调循环中每轮新截图覆盖上一轮同名文件不保留历史版本git 有历史)
  3. 逐段截图编号连续:seg-0seg-1seg-2...,与 anchor_compare.py 输出一致
  4. report.md 格式统一:每个页面的 diffs/<page>/report.md 记录最终差异率和遗留问题,作为验收依据
  5. .gitignore 不排除:这些中间文件需要入库,便于团队复查和回溯
  6. H5 原型截图目录只读:docs/h5_ui/screenshots/ 是输入物,迁移过程中不往里写 MP 截图或 diff 图