Files
Neo-ZQYY/_DEL/MIGRATION-PLAYBOOK.md
2026-03-15 10:15:02 +08:00

87 KiB
Raw Blame History

H5 → 微信小程序批量迁移执行手册

本手册是 SPEC 执行的唯一权威文档,整合了原 apps/miniprogram/doc/ 下全部迁移文档。 目标:将 docs/h5_ui/pages/ 下 17 个 HTML 原型页面迁移为原生微信小程序页面。 创建日期2026-03-08 原始文档来源migration-guide.md、migration-method-full-path.md、h5-to-miniprogram-pitfalls.md、 shared-component-specs.md、h5-input-material-guide.md、howtodo.md、auth-integration-guide.md、 migration-tracker.md、prd.md — 已全部整合至此,原文件不再维护。


一、迁移范围

排除页面4 个,用户指定不迁移)

  • login、no-permission、reviewing、apply

排除页面补充2 个,材料不完整,本次不处理)

  • home-settings无交互说明
  • ai-icon-demo无截图、无交互说明

全量迁移清单17 个页面,全部需要走完整流程)

board-coach、board-customer 虽有历史实现board-finance 正在迁移中, 但本次 SPEC 统一纳入,全部按标准流程重新迁移/验收。

批次 页面 复杂度 交互说明 截图 历史状态 备注
A-看板 board-finance (4张) 🔧 迁移中 6 板块,含筛选下拉+指标弹窗+目录面板
A-看板 board-coach (8张) 有历史实现 4 维度卡片 + 3 种筛选下拉
A-看板 board-customer (10张) 有历史实现 8 维度卡片 + heart-icon + 最专一表格
B-核心 task-list (3张) 待迁移 TabBar 主页,含长按菜单+备注弹窗
B-核心 my-profile 待迁移 TabBar 主页
C-任务 task-detail (3张) 待迁移 含放弃弹窗+备注弹窗
C-任务 task-detail-callback 待迁移 task-detail 主题色变体(仅 banner 背景+按钮配色),数据结构同 task-detail
C-任务 task-detail-priority 待迁移 task-detail 主题色变体(仅 banner 背景+按钮配色),数据结构同 task-detail
C-任务 task-detail-relationship 待迁移 task-detail 主题色变体(仅 banner 背景+按钮配色),数据结构同 task-detail
D-详情 coach-detail (2张) 待迁移 含备注弹窗
D-详情 customer-detail 待迁移 客户详情
D-详情 customer-service-records 待迁移 客户服务记录
E-绩效 performance 待迁移 业绩总览
E-绩效 performance-records 待迁移 业绩明细
F-对话 chat 待迁移 AI 对话
F-对话 chat-history 待迁移 对话历史
G-其他 notes 待迁移 备忘录(有历史实现,需重写)

有历史实现的页面处理方式

  • board-coach、board-customer、board-finance、notes已有小程序代码本次按标准流程重新审计 → 对比 H5 原型 → 差异修复 → 像素级验收
  • 其余 13 页:从零开始全流程迁移

二、每页迁移标准流程7 步)

Step 1输入物冻结加载顺序严格执行

1. 规则层:
   - 本文档(转换规范 + 避坑指南 + 组件规范,全部在此)

2. 全局资源层:
   - docs/h5_ui/design-tokens.json颜色/间距/字号/圆角/阴影)
   - docs/h5_ui/icon-mapping.md图标处理方案

3. 页面源码层:
   - docs/h5_ui/pages/<page>.htmlH5 源码 — 唯一结构真相)
   - docs/h5_ui/css/<page>.css自定义 CSS如有
   - docs/h5_ui/computed-styles.json 中 <page> key精确 px 值,如有)

4. 行为层:
   - docs/h5_ui/interactions/<page>.md状态变量 + 操作响应 + 状态枚举)

5. 视觉校验层:
   - docs/h5_ui/screenshots/<page>.png默认态截图
   - docs/h5_ui/screenshots/<page>--*.png交互态截图

缺一不可。 缺失材料时先标记,不猜测。

Step 2迁移审计先输出报告不写代码

输出《迁移审计报告》,包含 7 项:

审计项 内容
A. 页面结构 主要区域划分header/list/card/footer组件化边界建议
B. CSS 风险点 不支持的 CSS 特性清单 + 替代方案(含原因/影响/验收方式)
C. 关键样式映射 Tailwind 类 → computed 值 → WXSS按第五章七维度逐项核对取 computed-styles 精确值)
D. 图标处理 每个 SVG 的处理决策TDesign / 导出 SVG / Emoji
E. 交互映射 H5 DOM 操作 → setData + 事件绑定映射表
F. 外部依赖 CDN 资源Tailwind/Google Fonts本地化方案
G. 缺失信息 需要用户补充的材料清单

Step 3规则化转换详见第三章

按第三章的标签映射、样式转换、事件转换、路由规则执行。

Step 4编译验证

检查项 合格标准
WXML 编译 无编译错误(特别注意 .toFixed() 等 JS 方法不能在 WXML 中使用)
WXSS 编译 无警告(检查不支持的选择器)
控制台 无 JS 运行时错误
图片加载 无 404/500 错误(所有 /assets/ 引用的文件必须存在)
组件注册 无 "component not found" 警告
路由跳转 无 "navigateTo:fail" 错误
TS 类型 Page<IData>() 类型定义完整data 中所有字段有初始值

Step 5结构还原验证必须在像素对比之前完成

⚠️ 这是最关键的一步。未迁移页面的结构与 H5 原型差异巨大, 如果跳过结构验证直接做像素对比,差异率会高达 20%~80%,完全没有参考价值。 结构没对齐之前,禁止进入像素级对比。

逐项对照 H5 原型截图和交互说明,确认以下结构要素:

# 检查项 对照源 合格标准 常见问题
1 区域划分 H5 截图 header/content/footer/tabbar 区域与 H5 一致 缺少区域、区域顺序错误
2 元素层级 H5 源码 嵌套层级与 H5 DOM 结构对应 多余嵌套或缺少容器
3 列表/卡片数量 H5 截图 Mock 数据条数与 H5 一致(便于截图对比) 数据条数不同导致高度差异
4 文本内容 H5 源码 标题、标签、按钮文案与 H5 完全一致 文案遗漏或写错
5 图标完整性 icon-mapping.md 所有图标位置有对应实现TDesign/SVG/Emoji 图标缺失显示空白
6 导航结构 interactions/*.md 自定义导航栏/返回按钮/TabBar 行为正确 导航栏缺失或返回失效
7 弹窗/浮层 interactions/*.md 所有弹窗/下拉/浮层能正常触发和关闭 弹窗不弹出或无法关闭
8 滚动行为 H5 截图 长页面可滚动,吸顶元素正确固定 内容溢出不可滚动
9 三态占位 PRD 规范 loading/empty/error 三种状态有对应 UI 结构 缺少状态处理

通过标准: 以上 9 项全部通过后,才可进入 Step 6 像素级对比。 未通过处理: 记录问题 → 修复 → 重新验证,循环直到全部通过。

Step 6像素级视觉对比详见第五章

前置条件Step 5 结构还原验证全部通过。 结构未对齐时的像素对比结果无参考价值,禁止跳过 Step 5。

Step 7验收签收

验收项 怎么看 合格标准 常见失败表现
布局结构 对比截图整体布局 区域划分、层级关系一致 元素错位、层级混乱
间距系统 对比元素间距 与 H5 截图一致±4rpx 容差) 间距过大/过小
字体系统 对比字号、字重、行高 与 design-tokens 一致 字号偏差、行高不对
颜色 对比背景色、文字色、边框色 与 design-tokens 一致 颜色偏差
圆角 对比卡片、按钮圆角 与 design-tokens 一致 圆角过大/过小
阴影 对比卡片阴影 有阴影且不突兀 无阴影或阴影过重
图标 对比图标位置、大小、颜色 TDesign 图标正确显示 图标缺失或错位
交互完整性 按交互说明逐项操作 所有操作有正确响应 点击无反应、状态不切换
三态处理 切换 loading/empty/error 三种状态均有对应 UI 缺少空状态或加载态
安全区 刘海屏设备检查 内容不被刘海遮挡 顶部内容被裁切

未通过返工流程:

验收未通过时,按问题严重度回退到对应步骤修复:

问题类型 回退到 说明
结构错误(区域缺失/层级错乱/元素遗漏) Step 5 结构还原验证 结构问题必须重新走结构验证
样式偏差(间距/字号/颜色/圆角/阴影) Step 6 像素级对比 用 diff 图定位具体偏差点,补丁式修复
交互缺陷(点击无响应/状态不切换/弹窗异常) Step 3 规则化转换 对照 interactions/*.md 补全事件绑定
编译报错(修复过程中引入新错误) Step 4 编译验证 修复后必须重新过编译检查
真机差异(模拟器正常但真机异常) Step 6 像素级对比 真机截图对比,针对性修复

每次返工修复后,必须从回退步骤开始重新往下走完所有后续步骤,不可跳步。 例如:回退到 Step 5 修复结构问题后,需依次重新通过 Step 5 → Step 6 → Step 7。


三、转换规则大全

3.1 核心缩放公式(强制)

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

来源iPhone 15 Pro Max 430px 宽 → 小程序 750rpx 基准87.5% = 750/430/2。 先后尝试了非统一缩放、80%、87.5% 三种方案87.5% 在 iPhone 15 Pro Max 上与 H5 原型视觉一致度最高。

3.2 标签映射(硬性规则)

HTML WXML 说明
<div> <view> 容器
<span> / <p> <text> 文本必须用 <text> 包裹;<text> 内只能嵌套 <text>
<img> <image mode=""> 必须指定 mode 和宽高,默认 320×240 会变形
<svg> 内联 <image src="xx.svg"><t-icon> 不支持内联 SVG
<button> <t-button> TDesign 优先
<input> <t-input> TDesign 优先,事件是 bind:change 而非 bindinput
<textarea> <t-textarea> 同上
<select> <t-picker> 完全不同的交互
<a> bindtap + wx.navigateTo 无超链接
<ul>/<li> <view wx:for> 无列表语义标签,必须加 wx:key
<h1>~<h6> <text> + 样式类 无语义标题标签
<table> <view> 手动布局 无表格标签
<section> <view> 语义标签统一用 view
<label for="id"> <label for="id"> 支持,但 for 只能绑定 checkbox/radio/switch
<iframe> <web-view> 需配置业务域名白名单
scroll 容器 <scroll-view> 必须设固定高度,否则不滚动
<block> <block> 只是逻辑包裹,不产生真实 DOM需要样式时改用 <view>

严禁在 WXML 中使用 HTML 标签。

3.3 事件转换

H5 小程序 说明
onclick="fn()" bindtap="fn" 不能传参
onclick="fn(id)" data-id="{{id}}" bindtap="fn" dataset 传参
addEventListener 不支持 只能声明式绑定
event.target.value e.detail.value 取值路径不同
event.target.dataset e.currentTarget.dataset 注意 currentTarget
event.preventDefault() catchtap catch 前缀阻止冒泡
classList.toggle('active') setData({ active: !this.data.active }) + class="{{active ? 'on' : ''}}"
innerHTML = '...' setData({ content: '...' }) + WXML 数据绑定
history.back() wx.navigateBack()
window.location.href wx.navigateTo({ url: '...' })
localStorage.setItem wx.setStorageSync 上限 10MB
alert() / confirm() wx.showToast() / wx.showModal()

dataset 命名坑data- 属性名自动转换 — 连字符转驼峰(data-user-iddataset.userId),大写转小写(data-userIddataset.userid)。

3.4 路由规则

场景 小程序 API 说明
普通页面跳转 wx.navigateTo 保留当前页,页面栈 +1最多 10 层)
TabBar 页面跳转 wx.switchTab 必须用 switchTabnavigateTo 会报错
替换当前页 wx.redirectTo 关闭当前页
清空页面栈 wx.reLaunch 登录/登出场景
返回上一页 wx.navigateBack 页面栈 -1

TabBar 页面task-list、board-finance、my-profile 路径必须以 / 开头,且不带 .wxml 后缀。

3.5 SVG 导出规则

  1. 扫描目标页面 H5 源码中的所有 <svg> 标签
  2. TDesign 有语义等价图标(如返回箭头 → chevron-left)→ 用 <t-icon>,不导出
  3. 品牌/自定义图标 → 导出为 apps/miniprogram/miniprogram/assets/icons/<name>.svg
  4. 命名规则:icon-<用途>.svg(如 icon-wechat.svgLogo 类用 logo-<名称>.svg
  5. 导出时保留原始 viewBoxfillpath
  6. 小程序引用:<image src="/assets/icons/<name>.svg" mode="aspectFit" />,必须指定宽高
  7. TDesign <t-button icon="xxx">icon 属性只支持内置图标名,品牌图标传入无效名称会不显示

已导出清单

文件名 来源/用途 说明
logo-billiard.svg login 台球 Logo
icon-wechat.svg login 微信品牌图标
icon-clock-circle.svg reviewing 时钟主图标
icon-forbidden.svg no-permission 禁止符号主图标
ai-robot.svg ai-float-button AI 助手机器人图标
arrow-left.svg 通用 返回箭头
chart.svg board 系列 图表/数据图标
chat.svg / chat-gray.svg chat 对话图标(彩色/灰色)
check-bold.svg / check-circle.svg 通用 勾选图标
clock.svg 通用 时钟图标
forbidden.svg 通用 禁止图标
help-circle.svg board-finance 帮助/指标说明图标
info-circle.svg / info-error.svg / info-warning.svg 通用 信息/错误/警告图标
logout.svg my-profile 登出图标
task.svg task 系列 任务图标
wechat.svg 通用 微信图标
tab-*-nav.svg / tab-*-nav-active.svg board-tab-bar 底部导航栏 SVG 图标6 个)
tab-*.png / tab-*-active.png TabBar 系统 TabBar PNG 图标6 个)
icon-ai-float.png / icon-ai-inline.png AI 组件 AI 悬浮按钮/内联图标

完整文件列表见 apps/miniprogram/miniprogram/assets/icons/ 目录。 每次迁移新页面时,将新导出的图标追加到此清单。

3.6 转换执行顺序

1. 创建 4 文件骨架(.wxml / .wxss / .ts / .json
2. .json 注册 usingComponentsTDesign + 自定义组件)
3. 转换 WXML标签映射保持层级一致
4. 转换 WXSSTailwind → 手写 WXSS87.5% 缩放)
5. 转换 TSDOM → setData事件 → bindtap
6. Mock 数据(贴近真实 API 格式,标记 TODO
7. 三态处理loading / empty / normal / error

3.7 状态栏适配(所有 custom 导航页面强制)

// TS onLoad
const sysInfo = wx.getSystemInfoSync()
this.setData({ statusBarHeight: sysInfo.statusBarHeight || 20 })
<!-- WXML -->
<view class="page" style="padding-top: {{statusBarHeight}}px;">
/* WXSS */
.page { box-sizing: border-box; }

禁止使用 env(safe-area-inset-top)(部分机型不生效)。

3.8 一屏页面布局(不滚动的页面)

.page {
  height: 100vh;              /* 固定一屏高度,不用 min-height */
  display: flex;
  flex-direction: column;
  box-sizing: border-box;     /* padding-top 从 100vh 中扣除 */
  overflow: hidden;           /* 防止内容溢出产生滚动 */
}
.hero { flex: 1; }            /* 主内容区域占满剩余空间 */
.bottom-area { /* 固定高度,不参与 flex 伸缩 */ }

如果页面内容可能超过一屏(如表单页),改用 min-height: 100vh + 允许滚动。

3.9 长页面滚动与吸顶处理

详细规范滚动方案选择、sticky 层级、筛选栏隐显、板块吸顶、下拉刷新、结构验证补充检查项) 见第十四章「长页面迁移专题」。本节仅列出核心要点。

核心要点:

  • 页面级滚动优先,不要用 scroll-view 包裹整个页面
  • stickyscroll-view 内部不生效(最常见的坑)
  • sticky 元素必须设 background-color,否则内容穿透
  • 多层 sticky 叠加时 z-index 从上到下递减20 → 15 → 10
  • 底部固定栏用 position: fixed,内容区末尾加占位 <view> 防遮挡
  • 下拉刷新适用页面task-list、board-finance、board-coach、board-customer、notes、chat-history

3.10 交互子状态处理规范

详细规范弹窗类型与实现方案、z-index 分层策略、遮罩模式、长按菜单、星星评分、 AI 悬浮按钮、底部固定操作栏、三态处理、表单处理、交互态验证清单) 见第十五章「交互弹窗与状态管理专题」。本节仅列出核心要点。

核心要点:

  • 遮罩 bindtap 关闭弹窗,弹窗内容 catchtap="" 阻止穿透
  • 底部弹窗必须加 padding-bottom: env(safe-area-inset-bottom)
  • 弹窗打开时用 catchtouchmove 阻止背景滚动
  • 同一时刻只允许一个弹窗打开(互斥)
  • z-index 分层sticky 10-29 → 悬浮按钮 30 → 底部栏 50 → 导航栏 100 → 遮罩 999 → 弹窗 1000
  • 长按菜单用 bindlongpress,注意与 bindtap 的冲突处理
  • 每个页面必须处理 loading / empty / error / normal 四种状态

四、样式转换详解

4.1 Tailwind → WXSS 速查表87.5% 缩放)

Tailwind H5 px ×2 ×0.875 最终 rpx
text-xs (12px) 12 24 21 22
text-sm (14px) 14 28 24.5 24
text-base (16px) 16 32 28 28
text-lg (18px) 18 36 31.5 32
text-xl (20px) 20 40 35 36
text-2xl (24px) 24 48 42 42
gap-2 / p-2 (8px) 8 16 14 14
gap-3 / p-3 (12px) 12 24 21 22
gap-4 / p-4 (16px) 16 32 28 28
gap-5 / p-5 (20px) 20 40 35 36
gap-6 / p-6 (24px) 24 48 42 42
gap-8 / p-8 (32px) 32 64 56 56
rounded-xl (12px) 12 24 21 24
rounded-2xl (16px) 16 32 28 32
rounded-3xl (24px) 24 48 42 48

圆角例外A/B 视觉对比验证 ×2 与 ×0.875 差异 <0.02%,圆角统一采用简单 ×2不乘 0.875),数值更整洁且与 app.wxss CSS 变量一致。 | w-10 (40px) | 40 | 80 | 70 | 70 | | w-14 (56px) | 56 | 112 | 98 | 98 | | w-24 (96px) | 96 | 192 | 168 | 168 | | w-28 (112px) | 112 | 224 | 196 | 196 |

4.2 Tailwind 类 → WXSS 属性映射

Tailwind WXSS
p-4 padding: 28rpx;
px-4 padding-left: 28rpx; padding-right: 28rpx;
m-3 margin: 22rpx;
gap-3 gap: 22rpx;
space-y-3 子元素 margin-top: 22rpx;(首个除外,用 .child + .child 选择器)
flex display: flex;
flex-col flex-direction: column;
items-center align-items: center;
justify-between justify-content: space-between;
flex-1 flex: 1;
min-h-screen min-height: 100vh;
sticky top-0 position: sticky; top: 0;
z-10 z-index: 10;
font-medium font-weight: 500;
font-semibold font-weight: 600;
leading-relaxed line-height: 1.625;
shadow-sm box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
shadow-lg box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.06);
shadow-xl box-shadow: 0 16rpx 48rpx rgba(0,0,0,0.08);
bg-white background-color: #ffffff;
bg-gray-1 background-color: #f3f3f3;
bg-white/60 background: rgba(255,255,255,0.6);
text-gray-13 color: #242424;
text-gray-6 color: #a6a6a6;
border-b border-gray-2 border-bottom: 2rpx solid #eeeeee;
backdrop-blur-sm 不支持,改为 rgba() 半透明

4.3 颜色速查design-tokens.json

Token Hex 用途
primary #0052d9 主色、active 态
primary-light #ecf2fe 主色浅底
success #00a870 成功/助教标签
warning #ed7b2f 警告
error #e34d59 错误/跟 badge
gray-1 #f3f3f3 页面背景
gray-2 #eeeeee 分隔线
gray-3 #e7e7e7
gray-4 #dcdcdc 禁用态
gray-5 #c5c5c5
gray-6 #a6a6a6 次要文字
gray-7 #8b8b8b TabBar 默认色
gray-8 #777777
gray-9 #5e5e5e
gray-10 #4b4b4b
gray-11 #393939
gray-12 #2c2c2c
gray-13 #242424 主文字

4.4 不支持的 CSS 替代方案

CSS 特性 小程序支持 替代方案
backdrop-filter: blur() background: rgba(255,255,255,0.95);
* 通配符选择器 逐个元素设置Tailwind 的 * reset 全部失效)
filter: blur() radial-gradient 模拟扩散效果
::before / ::after ⚠️ 部分支持 复杂场景用额外 <view> 元素模拟
远程 @font-face ⚠️ wx.loadFontFace() 或系统字体
clip-path 改为图片或 CSS 渐变近似
CSS 变量 var() TDesign 大量使用,可直接迁移
linear-gradient 正常使用
animation / @keyframes 正常使用
transition 正常使用
env(safe-area-inset-*) 刘海屏适配可用(但 top 建议用 JS
url("data:image/svg+xml,...") 用 CSS 渐变模拟或导出为 PNG/base64
position: fixed ⚠️ 部分场景异常 position: sticky 或组件自带吸顶
gap (flexbox) ⚠️ 基础库 2.30+ 低版本用 margin

4.5 样式作用域

  • app.wxss = 全局样式
  • 页面 .wxss = 仅当前页面生效(自动隔离)
  • 组件 .wxss = 默认隔离(styleIsolation: 'isolated'
  • H5 的全局 CSS reset* { box-sizing: border-box; })在小程序中无效,需逐个元素设置

五、样式精调执行规范(像素级对齐核心)

第四章提供了速查表和映射工具,本章定义的是"怎么用这些工具写出精确的 WXSS"。 AI 在写每个 WXML 元素的 WXSS 时,必须按本章清单逐项确认,不可凭感觉估值。

5.1 数据源优先级(强制)

取值时按以下优先级,高优先级覆盖低优先级:

1. computed-styles.json 中该元素的精确 px 值(最高优先级)
   → 直接用公式换算rpx = px × 2 × 0.875,取偶数

2. H5 源码中的 Tailwind 类名
   → 查 4.1 速查表换算

3. design-tokens.json 中的 Token 值
   → 颜色、圆角、阴影直接引用

4. H5 截图目测估算(最低优先级,仅在以上三者均缺失时使用)
   → 必须标注 /* 目测值,待校准 */

禁止:不查任何数据源,直接写一个"看起来差不多"的值。

5.2 七维度逐项核对清单

每个可见元素(文字、卡片、按钮、图标、分隔线等)写 WXSS 时,必须逐一确认:

# 维度 必须确认的属性 常见错误 正确做法
1 字号 font-size 凭感觉写 28rpx 查 computed-styles 或 Tailwind 类,用公式换算
2 字重 font-weight 遗漏 semibold/medium font-medium → 500font-semibold → 600font-bold → 700
3 文字颜色 color 用近似灰色 严格使用 design-tokens 色号(见下方色阶表)
4 行高 line-height 遗漏或用错单位 Tailwind 比例值 → 换算为具体值(见下方行高表)
5 内间距 padding(上右下左) 四个方向不分别确认 逐方向查值,上下和左右通常不同
6 外间距 margingap 兄弟元素间距遗漏 优先用父容器 gap,其次用 margin-top(首个除外)
7 边框与圆角 borderborder-radius 边框粗细或颜色错误 边框最小 2rpx1rpx 部分设备不显示),颜色查 tokens

5.3 文字颜色层级体系(强制绑定)

用途 Token Hex 典型场景
主文字 gray-13 #242424 标题、姓名、金额数值
次要文字 gray-9 #5e5e5e 副标题、说明文字
辅助文字 gray-6 #a6a6a6 时间戳、占位符、标签
禁用文字 gray-4 #dcdcdc 不可点击的按钮文字
强调文字 primary #0052d9 链接、active 态、关键操作
成功文字 success #00a870 正向指标、完成状态
警告文字 warning #ed7b2f 待处理、注意事项
错误文字 error #e34d59 错误提示、紧急标签

禁止:使用不在此表中的灰色值(如 #333#666#999)。所有灰色必须从 gray-1 到 gray-13 中选取。

5.4 行高换算规则

Tailwind 的 leading-* 是比例值,不是 px。换算方式line-height = font-size × 比例

Tailwind 类 比例值 搭配 text-sm (24rpx) 搭配 text-base (28rpx) 搭配 text-lg (32rpx)
leading-none 1.0 24rpx 28rpx 32rpx
leading-tight 1.25 30rpx 36rpx 40rpx
leading-snug 1.375 34rpx 38rpx 44rpx
leading-normal 1.5 36rpx 42rpx 48rpx
leading-relaxed 1.625 40rpx 46rpx 52rpx
leading-loose 2.0 48rpx 56rpx 64rpx

默认行高:如果 H5 源码没有显式指定 leading-*Tailwind 默认 leading-normal1.5)。小程序中必须显式写出 line-height,不能依赖浏览器默认值(小程序默认行高与浏览器不同)。

5.5 高频元素标准间距参考

以下是项目中高频出现的元素间距标准值(从已完成页面提取的实际值):

元素类型 属性 标准值 说明
页面容器 padding 0 28rpx 左右 28rpx上下由内容决定
卡片容器 padding 28rpx 四方向统一
卡片容器 border-radius 24rpx rounded-xl12px × 2
卡片容器 margin-bottom 24rpx 卡片间距12px × 2
卡片容器 box-shadow 0 2rpx 8rpx rgba(0,0,0,0.05) shadow-sm
列表项 padding 24rpx 28rpx 上下 24rpx左右 28rpx
列表项分隔线 border-bottom 2rpx solid #eeeeee gray-2
标题文字 font-size + font-weight 32rpx + 600 text-base + semibold
正文文字 font-size + font-weight 28rpx + 400 text-sm + normal
辅助文字 font-size + color 24rpx + #a6a6a6 text-xs + gray-6
金额数值 font-size + font-weight 36rpx + 600 text-lg + semibold
标签tag padding 4rpx 14rpx 紧凑内间距
标签tag font-size + border-radius 22rpx + 8rpx 小字 + 小圆角
按钮 height + border-radius 80rpx + 48rpx 大按钮rounded-3xl: 24px × 2
图标 width + height 36rpx × 36rpx 行内小图标
元素间纵向间距 gapmargin-top 14rpx(紧凑)/ 22rpx(标准)/ 28rpx(宽松) 三档间距

5.6 典型错误案例(踩坑记录)

# 错误描述 视觉表现 根因 正确做法
1 卡片 padding 写 24rpx 而非 28rpx 卡片内容偏挤,与 H5 对比左右差 4rpx 没查 computed-styles凭感觉写了 p-3 的值 查 H5 源码确认是 p-416px → 28rpx
2 文字颜色用 #666666 灰度偏深,与 H5 不一致 没用 design-tokens 色号 查色阶表,次要文字用 #5e5e5egray-9
3 行高遗漏 多行文字间距偏窄,整体显得拥挤 没写 line-height,小程序默认值比浏览器小 显式写 line-height: 42rpxtext-sm + leading-normal
4 字重写 bold 而非 600 文字过粗 bold = 700H5 用的是 font-semibold = 600 查 Tailwind 类确认字重等级
5 兄弟元素间距用 margin-bottom 最后一个元素多出底部空白 应该用 gap.item + .item { margin-top } 父容器 gap: 22rpx.item + .item 选择器
6 边框写 1rpx 部分安卓设备边框不显示 1rpx 在低 DPR 设备上被四舍五入为 0 边框最小用 2rpx
7 圆角写 16rpx 而非 24rpx 卡片圆角偏小 混淆了 rounded-lg8px→16rpxrounded-xl12px→24rpx 查 4.1 速查表确认 Tailwind 类对应值

5.7 每个元素的写码流程(强制)

写一个元素的 WXSS 时,按以下顺序执行:

1. 确认元素在 H5 中的 Tailwind 类名(或 computed-styles 值)
2. 逐一换算 7 个维度:
   □ font-size    → 查速查表或公式换算
   □ font-weight  → 查 Tailwind 类medium/semibold/bold
   □ color        → 查色阶表,禁止用非标准灰色
   □ line-height  → 查行高表,必须显式写出
   □ padding      → 四方向分别确认
   □ margin/gap   → 与兄弟元素的间距
   □ border/radius → 边框最小 2rpx圆角查速查表
3. 写入 WXSS
4. 自检:与 H5 源码逐属性对比,确认无遗漏

不适用此流程的元素:纯布局容器(只有 display: flex 等布局属性、无视觉表现的 view


六、像素级视觉对比工具链

核心思想computed-styles.json 中提取的精确 px 值,经 rpx = px × 2 × 0.875 换算后, 在浏览器和小程序中理论上应产生相同的视觉效果。但由于两个平台的渲染引擎差异 (字体 hinting、亚像素抗锯齿、flex 布局舍入策略、行高默认值等), 相同的数值会产生 1-3rpx 级别的渲染偏差。

当页面结构已完成迁移并通过 Step 5 验证后,像素级对比的唯一目的就是: 系统性地发现并消除这些"理论一致但实际不一致"的渲染偏差 将 gap 收敛到人眼不可察觉的程度(前半屏差异率 < 5%)。

换言之,结构对齐是前提,像素对比只解决最后一公里的精度问题。

6.1 前置准备

H5 截图(一次性)

  1. 用户启动 Go LiveVS Code 插件),端口 5500
  2. Playwright MCP 导航到 http://127.0.0.1:5500/docs/h5_ui/pages/<page>.html
  3. 设置视口 430×752(与 MP windowHeight 统一DPR=3
  4. 逐段截图 → docs/h5_ui/screenshots/h5-<page>--seg-<i>.png(每张 1290×2256
  5. 交互态截图(下拉、弹窗、面板等)→ <page>--<state>.png

微信开发者工具连接

# 用户手动启动自动化端口
& "C:\dev\WechatDevtools\cli.bat" auto --project "C:\NeoZQYY\apps\miniprogram" --auto-port 9420

连接规范:

  • 只能用 wsEndpoint 策略,ws://127.0.0.1:9420
  • 禁止 auto/launch/connect/discover 策略
  • 导航到 tabbar 页面必须用 relaunch,路径前加 /

6.2 DPR 换算关系

平台 视口(逻辑) DPR 截图尺寸(物理)
H5 截图 430×752 3 1290×2256
MP 截图 430×752 1.5 645×1128

统一对比尺寸1290×2256MP ×2 缩放)。 两端视口高度统一为 752pxMP windowHeight截图自然 1:1 对齐,无需裁剪。

6.3 对比流程

长页面和短页面使用不同的对比策略:

6.3.1 短页面(一屏内)— 全页面对比

Step 1: 截图
  mcp_weixin_devtools_mcp_relaunch → /pages/<page>/<page>
  mcp_weixin_devtools_mcp_waitFor → 2000ms
  mcp_weixin_devtools_mcp_screenshot → mp-<page>.png

Step 2: 尺寸统一
  python scripts/ops/resize_and_compare_v2.py
  → H5 保持 1290px 宽
  → MP 截图 ×2 缩放到 1290px
  → H5 裁剪到 MP 逻辑高度对应的物理高度
  → 输出 cmp-h5.png + cmp-mp.png同尺寸

Step 3: 像素对比
  mcp_image_compare_compare_images
  → image1: cmp-h5.png, image2: cmp-mp.png
  → threshold: 0.1
  → 输出 diff 图 + 差异百分比

Step 4: wxss 微调 → 回到 Step 1 循环

6.3.2 长页面 — v2 逐段截图对比anchor_compare.py

长页面(超过一屏)使用 scripts/ops/anchor_compare.py 进行逐段截图对比。

核心思路:两端都按 section 锚点逐段滚动 + 单屏截图。 两端视口高度统一为 430×752MP windowHeight截图自然 1:1 对齐。

DPR 换算:

  • H5: viewport 430×752, DPR=3 → 每张截图 1290×2256
  • MP: viewport 430×752, DPR=1.5 → 每张截图 645×1128 → ×2 缩放到 1290×2256

两端截图从顶部自然对齐:

  • H5: safe-area-top(~46px) + filterBar(70px) ≈ 116px sticky 区域
  • MP: board-tabs(45px) + filter-bar(70px) = 115px sticky 区域
  • 差异 ~1px 可忽略,无需裁剪顶部
Step 1: 提取 H5 锚点坐标 + 逐段截图
  python scripts/ops/anchor_compare.py extract-h5 <page>
  → 需要 Go Live 运行在 5500 端口
  → Playwright 视口 430×752, DPR=3, headless=True
  → 逐个锚点滚动scrollTo = anchor_top - stickyHeight+ 单屏截图
  → 输出 docs/h5_ui/anchors/<page>.json锚点坐标 + segments 数组)
  → 输出 docs/h5_ui/screenshots/h5-<page>--seg-<i>.png每张 1290×2256

Step 2: 生成 MP 截图指令
  python scripts/ops/anchor_compare.py mp-inst <page>
  → 输出 docs/h5_ui/anchors/<page>-mp-instructions.json
  → 包含每段的 scroll_into_view_id、等待时间、截图文件名

Step 3: AI 按指令执行 MP 截图(通过 MCP 工具手动执行)
  → scroll-view 页面使用 scroll_into_view 模式:
    1. evaluate_script: page.setData({ scrollIntoView: '' })     // 先清空
    2. evaluate_script: page.setData({ scrollIntoView: '<id>' }) // 设目标
    3. waitFor: delay 1000ms
    4. screenshot: mp-<page>--seg-<i>.png645×1128
  → page_scroll 页面使用 wx.pageScrollTo({scrollTop})

Step 4: 逐段对比
  python scripts/ops/anchor_compare.py compare <page>
  → H5 截图直接使用(已是 1290×2256
  → MP 截图 ×2 缩放到 1290×2256
  → 输出 seg-h5-<page>-<i>.png + seg-mp-<page>-<i>.png

Step 5: 像素对比
  mcp_image_compare_compare_images
  → 逐段对比 seg-h5 vs seg-mp
  → 输出 diff-<page>-<i>.png + 差异百分比

Step 6: wxss 微调 → 回到 Step 3 循环

已知限制:

  • sys.stdout = io.TextIOWrapper(...) 与 Playwright asyncio 冲突,已修复为延迟到 main() 入口调用
  • MP 截图需通过 MCP 工具手动执行,无法全自动化

6.4 差异阈值

差异% 评价 行动
< 5% 优秀 字体渲染级差异,可接受
5-10% 良好 检查是否有结构性差异,字号字体边距行距等
10-15% 需调整 定位差异区域,微调间距,字号字体边距行距等
> 15% 较大 可能有布局错误,需逐元素排查,字号字体边距行距等

注意底部区域MP 只截一屏)的差异是结构性的,不算样式问题。评估时应关注前半屏的差异。

6.5 交互态验证

交互态 触发方式 截图命名
筛选下拉 点击 filter-dropdown <page>--filter-dropdown.png
指标弹窗 点击 help-icon <page>--tip-modal.png
目录导航 点击 toc-btn <page>--toc-panel.png

交互态对比更多是视觉检查(弹窗位置、遮罩透明度),不需要像默认态那样精确到像素。

6.6 每轮修复原则

  • 只输出需要改动的文件和改动段落diff 风格),不整文件重贴
  • 优先使用 flex/盒模型的确定性方案,不用"碰运气"的魔法数
  • rpx 换算统一,严禁同一类间距混用 rpx 和 px
  • 每轮控制 2-5 处修改,避免一次改太多难以定位效果
  • 每次修复后重新编译验证,防止修 A 坏 B

6.7 差异收敛规律

  • 第一轮调整通常能降 3-5 个百分点(修复明显的间距错误)
  • 第二轮再降 2-3 个百分点(精细间距对齐)
  • 低于 10% 后继续调整收益递减(剩余差异多为字体渲染和平台差异)
  • 前半屏 < 5% 即可视为达标

6.8 工具脚本

脚本 路径 功能
anchor_compare.py scripts/ops/ 锚点分区对齐的长页面像素级对比(推荐)
resize_and_compare_v2.py scripts/ops/ 短页面全页面统一尺寸 + 裁剪
analyze_diff.py scripts/ops/ 按条带分析差异分布
screenshot_h5_pages.py scripts/ops/ H5 页面全页面截图Playwright

anchor_compare.py 子命令:

子命令 说明
extract-h5 <page> 提取 H5 锚点坐标 + 逐段截图(视口 430×752, DPR=3输出 docs/h5_ui/anchors/<page>.json
mp-inst <page> 生成 MP 截图指令,输出 docs/h5_ui/anchors/<page>-mp-instructions.json
compare <page> MP ×2 缩放 + 逐段配对,输出 seg-h5-<page>-<i>.png + seg-mp-<page>-<i>.png
full <page> 一键执行 extract-h5 + mp-inst + compareMP 截图仍需手动)
list 列出所有已配置锚点的页面

七、高频踩坑完整清单

7.1 结构层

# 说明 解决方案
1 内联 SVG 不支持 WXML 不能写 <svg> 标签 提取为 .svg 文件,用 <image><t-icon>
2 没有 DOM API document.getElementById 等全部不可用 this.setData() 驱动视图更新
3 <text> 内只能嵌套 <text> 不能在 <text> 内放 <view> 需要块级布局时外层用 <view>
4 checked="false" 是 true 字符串 "false" 是 truthy 必须写 checked="{{false}}"
5 wx:key 必须提供 列表渲染不加 key 会警告且性能差 wx:key="id"wx:key="*this"
6 <block> 不渲染 DOM 只是逻辑包裹,不产生真实节点 需要样式时改用 <view>

7.2 样式层

# 说明 解决方案
7 * 选择器无效 全局 reset 失效 逐个元素设置 box-sizing
8 backdrop-filter 不支持 毛玻璃效果无法实现 用半透明背景色 rgba() 近似
9 image 默认 320×240 不设宽高会变形 始终指定 width/height + mode
10 rpx 小数精度 1rpx 在某些设备上不显示 边框最小用 2rpx
11 组件样式隔离 页面样式穿不进自定义组件 用外部样式类或 styleIsolation: 'shared'
12 !important 滥用 TDesign 组件内部样式优先级高 优先用 CSS 变量覆盖

7.3 逻辑层

# 说明 解决方案
13 setData 性能 大数据量传输卡顿 只传变化字段:'list[2].name': 'new'
14 没有 Cookie 登录态不能靠 Cookie Storage + header token
15 eval() 不可用 动态代码执行被禁止 预编译逻辑
16 页面栈 10 层限制 navigateTo 超过 10 层静默失败 合理使用 redirectTo / reLaunch
17 alert() 不存在 没有浏览器弹窗 API wx.showToast() / wx.showModal()
18 window / document 不存在 所有 Web API 不可用 wx.* API 替代

7.4 TDesign 相关

# 说明 解决方案
19 style: v2 冲突 app.json 中的 "style": "v2" 导致样式错乱 删除该配置
20 npm 构建遗忘 安装新包后忘记构建 npm 每次 npm install 后在开发者工具中"构建 npm"
21 事件名差异 TDesign 用 bind:change,原生用 bindinput 查阅组件文档确认事件名
22 外部样式类命名 t-class / t-class-input 等各组件不同 查阅组件文档的 External Classes

7.5 实战踩坑记录(按发现时间倒序)

# 触发页面 解决方案
P1 WXML 中不能调用 JS 方法(.toFixed() 等) 所有页面 创建 WXS 模块 utils/format.wxs
P2 TabBar 页面不能用 navigateTo task-list 等 跳转前判断目标是否 TabBar 页面
P3 图片 500 错误(资源文件不存在) 所有引用图片的页面 CSS 渐变/emoji/t-icon 替代不存在的图片
P4 env(safe-area-inset-top) 部分机型不生效 所有 custom 导航页面 JS 获取 statusBarHeight
P5 statusBarHeight padding 导致一屏页面底部溢出 login 等一屏页面 height: 100vh + box-sizing: border-box
P6 TDesign Button icon 不支持自定义图标 login 原生 view + image 组合按钮
P7 TDesign Button 默认样式覆盖自定义 WXSS login 高度定制时用原生 view
P8 WXSS 不支持 ::before/::after 伪元素 reviewing 用实际 <view> 替代
P9 WXSS 不支持 url("data:image/svg+xml,...") reviewing repeating-linear-gradient 模拟
P10 不支持 filter: blur() reviewing 用更大尺寸 radial-gradient 模拟
P11 max-w-sm 机械换算后过宽 reviewing 实测调整,不机械换算
P12 Tailwind 颜色变体需逐一核对 reviewing 查 Tailwind 官方色板取精确 hex

八、共享组件规范

8.1 AI 悬浮按钮ai-float-button

  • 组件路径:components/ai-float-button/
  • 机器人 SVG icon + 渐变流动动画背景
  • 默认 bottom 220rpx在自定义底部导航栏上方

8.2 自定义底部导航栏board-tab-bar

  • 组件路径:components/board-tab-bar/
  • 用于非 TabBar 的看板子页面board-coach、board-customer
  • SVG icon 从 H5 原型提取,路径 /assets/icons/tab-*-nav*.svg
属性
高度 100rpx
背景 #ffffff
边框 1rpx solid #eeeeee
icon 尺寸 44rpx × 44rpx
label 字号 20rpx
label 颜色 #8b8b8b默认/ #0052d9active
active 字重 500
gapicon↔label 4rpx
safe-area padding-bottom: env(safe-area-inset-bottom)

8.3 筛选下拉组件filter-dropdown

  • 组件路径:components/filter-dropdown/
  • 全屏宽度面板 + 半透明遮罩 + 动态 top 计算

触发按钮样式:

属性
padding 16rpx 20rpx
背景 #ffffff
边框 2rpx solid var(--color-gray-1)
圆角 var(--radius-md)
label 字号 24rpx
label 字重 600
active 边框色 var(--color-primary)
active 背景 var(--color-primary-light)

下拉面板样式:

属性
定位 fixed, left:0, right:0
最大高度 60vh
圆角 0 0 28rpx 28rpx
阴影 0 16rpx 48rpx rgba(0,0,0,0.15)
选项 padding 34rpx 32rpx
选项字号 28rpx
分隔线 1rpx solid rgba(0,0,0,0.03)
active 颜色 var(--color-primary)
active 字重 500
遮罩背景 rgba(0,0,0,0.5)
z-index 999遮罩/ 1000面板

8.4 顶部看板 Tab 栏

  • 直接写在页面 wxml 中(非独立组件)
  • sticky top: 0, z-index: 20
属性
背景 #ffffff
边框 2rpx solid #eeeeee
tab padding 24rpx 0
tab 字号 26rpx
tab 字重 500默认/ 600active
tab 颜色 #8b8b8b默认/ #0052d9active
下划线宽 42rpx
下划线高 5rpx
下划线渐变 linear-gradient(90deg, #0052d9, #5b9cf8)

8.5 筛选栏容器

  • sticky top: 70rpx, z-index: 15
  • 滚动隐藏/显示220ms ease 过渡)
属性
背景 #f3f3f3
padding 14rpx 28rpx
内框背景 #ffffff
内框圆角 14rpx
内框 padding 10rpx
内框 gap 14rpx
内框边框 2rpx solid #eeeeee
第一个筛选项 flex: 1.8
其他筛选项 flex: 1

8.6 heart-icon 组件

  • 组件路径:components/heart-icon/
  • 用 TS observers 监听 score 属性变化,计算对应 emoji 字符串
  • WXS 不支持 emoji surrogate pair必须用 TS 计算
  • 样式:font-size: 22rpx; line-height: 1; position: relative; top: -4rpx

8.7 其他组件

组件 路径 说明
hobby-tag components/hobby-tag/ 客户爱好标签board-customer 使用
metric-card components/metric-card/ 指标卡片board 系列页面使用
note-modal components/note-modal/ 备注弹窗task-detail/coach-detail 等使用
star-rating components/star-rating/ 星星评分board-coach 使用
dev-fab components/dev-fab/ 开发调试悬浮按钮,所有页面(全局注册于 app.json

九、TDesign 组件使用规范

9.1 常见替代关系

H5 原型元素 TDesign 组件 注意事项
<button> <t-button> 用 CSS 变量定制样式
<input> <t-input> 事件是 bind:change 而非 bindinput
<textarea> <t-textarea> 同上
<select> <t-picker> 完全不同的交互
<checkbox> <t-checkbox> 或手动实现
<radio> <t-radio> / <t-radio-group> bind:change 取值
SVG 图标 <t-icon name="xxx"> TDesign 内置图标库
加载动画 <t-loading> 替代 CSS spinner
弹窗 <t-dialog> / <t-toast> 替代 alert() / confirm()

9.2 样式覆盖 4 种方式(优先级从高到低)

  1. CSS 变量(推荐):--td-button-large-height: 96rpx
  2. 外部样式类:t-class="my-class" + .my-class { ... !important }
  3. 解除隔离TDesign 已开启 addGlobalClass,页面样式可直接覆盖
  4. style 属性:style="background: #f5f5f5;"

原则TDesign 组件适合"接近默认样式"的场景;与原型差异大时,原生 view 实现更可控。


十、认证联调指南

10.1 环境准备

后端:

# .env.local
WX_DEV_MODE=true          # 开启开发模式,跳过真实微信 code2Session
JWT_SECRET_KEY=dev-secret
cd apps/backend && uvicorn app.main:app --reload  # 监听 http://localhost:8000

小程序开发者工具设置:

  • 勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」
  • utils/request.tsBASE_URL 指向 http://localhost:8000

10.2 Mock 登录

# 默认创建 pending 状态用户
curl -X POST http://localhost:8000/api/xcx/dev-login \
  -H "Content-Type: application/json" \
  -d '{"openid": "test_user_001"}'

# 指定用户状态
curl -X POST http://localhost:8000/api/xcx/dev-login \
  -H "Content-Type: application/json" \
  -d '{"openid": "test_user_001", "status": "approved"}'

10.3 API 端点汇总

小程序端:

端点 方法 说明
/api/xcx/login POST 微信登录
/api/xcx/dev-login POST Mock 登录(仅 WX_DEV_MODE=true
/api/xcx/apply POST 提交申请
/api/xcx/me GET 查询用户状态
/api/xcx/me/sites GET 查询关联店铺
/api/xcx/switch-site POST 切换店铺
/api/xcx/refresh POST 刷新令牌

管理端:

端点 方法 说明
/api/admin/applications GET 查询申请列表
/api/admin/applications/{id} GET 申请详情 + 候选匹配
/api/admin/applications/{id}/approve POST 批准申请
/api/admin/applications/{id}/reject POST 拒绝申请

10.4 常见问题

错误 原因 排查
401 token 过期或无效 检查 Storage 中 access_token用 dev-login 重新获取
403 用户 disabled 或权限不足 GET /api/xcx/me 检查 status
409 重复提交申请 查看现有申请状态
422 表单格式错误 site_code 格式 2字母+3数字phone 11位
网络失败 后端未启动或域名校验 访问 http://localhost:8000/docs 确认

十一、迁移进度追踪

数据来源2026-03-08 像素级对比结果。 对比方法H5 截图DPR=3, 1290px 宽vs MP 截图DPR=1.5, 645px → ×2 缩放至 1290px裁剪到相同高度后 pixelmatch 对比。 截图目录:docs/h5_ui/screenshots/

11.1 页面进度总览

已有历史实现(需重新走完整流程验收)

页面 小程序路径 历史状态 默认态差异% 备注
board-coach pages/board-coach/ 已完成 7.04% 4 维度卡片 + 3 种筛选下拉
board-customer pages/board-customer/ 已完成 7.51% 8 维度卡片 + heart-icon + 最专一表格
board-finance pages/board-finance/ 🔧 迁移中 9.04% 6 板块完整重写

待迁移14 页)

页面 小程序路径 H5 原型 交互说明 优先级 默认态差异%
task-list pages/task-list/ 43.78%
my-profile pages/my-profile/ 3.61%
task-detail pages/task-detail/ 20.66%
task-detail-callback pages/task-detail-callback/ 23.69%
task-detail-priority pages/task-detail-priority/ 23.05%
task-detail-relationship pages/task-detail-relationship/ 20.67%
coach-detail pages/coach-detail/ 34.25%
customer-detail pages/customer-detail/ 38.48%
customer-service-records pages/customer-service-records/ 25.33%
performance pages/performance/ 38.70%
performance-records pages/performance-records/ 28.42%
chat pages/chat/ 25.35%
chat-history pages/chat-history/ 8.98%
notes pages/notes/ 10.85%

待迁移页面差异率偏高属预期H5 mock 数据 vs MP 真实数据、字体渲染差异等)。迁移后差异率应降至 ≤10%。

11.2 已迁移页面交互态对比

页面 交互态 差异% diff 文件
board-coach perf-high 7.05% diff-board-coach--perf-high.png
board-coach salary-high 10.55% diff-board-coach--salary-high.png
board-coach storage-value 10.28% diff-board-coach--storage-value.png
board-coach task-complete 6.36% diff-board-coach--task-complete.png
board-coach sort-dropdown 23.90% diff-board-coach--sort-dropdown.png
board-coach skill-dropdown 23.30% diff-board-coach--skill-dropdown.png
board-coach time-dropdown 24.10% diff-board-coach--time-dropdown.png
board-customer recall 7.46% diff-board-customer--recall.png
board-customer potential 7.35% diff-board-customer--potential.png
board-customer balance 7.30% diff-board-customer--balance.png
board-customer recharge 7.46% diff-board-customer--recharge.png
board-customer recent 6.89% diff-board-customer--recent.png
board-customer spend60 6.85% diff-board-customer--spend60.png
board-customer freq60 11.43% diff-board-customer--freq60.png
board-customer loyal 10.03% diff-board-customer--loyal.png
board-customer type-dropdown 24.82% diff-board-customer--type-dropdown.png
board-customer project-dropdown 23.03% diff-board-customer--project-dropdown.png
board-finance filter-dropdown 14.29% diff-board-finance--filter-dropdown.png
board-finance tip-modal 24.84% diff-board-finance--tip-modal.png
board-finance toc-panel 10.90% diff-board-finance--toc-panel.png

下拉菜单类交互态差异率偏高20%+)属预期——弹出层遮罩和动画导致像素差异大,重点关注内容区域还原度。

11.3 无需 H5 迁移的页面

页面 小程序路径 说明
mvp pages/mvp/ 临时入口/路由分发页
index pages/index/ 框架默认页
logs pages/logs/ 框架默认日志页
dev-tools pages/dev-tools/ 开发调试工具页

十二、AI 工作流提示词模板

以下 6 阶段提示词模板用于指导 AI 执行迁移。每个阶段有明确的输入/输出要求。 实际使用时,将 <page-name> 替换为具体页面名,将 <<<...>>> 替换为实际内容。

阶段 1迁移前审计与准备

你是一名资深微信小程序前端架构师+CSS布局专家。我要把一套 Web 页面HTML/CSS/少量JS
迁移为"原生微信小程序"WXML/WXSS/JS/JSON要求结构与样式细节还原度尽可能高。

输入(我将分段提供):
- 页面HTML可含多页面
- 全量CSS含reset/公共样式)
- 资源清单(图片/字体/图标svg等路径
- 若有:目标效果截图/设计稿

请先不要写最终代码,先做《迁移审计与准备报告》,输出必须包含:

A. 页面结构清单:主要区域及建议组件化边界
B. CSS复杂度审计高风险点sticky、复杂选择器、伪元素、滤镜等+ 替代策略
C. HTML→WXML映射规则语义标签映射、事件绑定映射、表单控件映射、wx:for 策略
D. CSS→WXSS映射规则单位策略px→rpx、选择器扁平化、样式隔离
E. 资源处理:图片/字体/图标方案、包体与分包建议
F. 产物目录规划pages/、components/、styles/、assets/ 目录树
G. 最小补充信息清单:高还原必须补充的信息

输出要求:表格+要点并存;每个高风险点给出"原因 + 影响 + 处理方案 + 验收方式"。

阶段 2生成第一版代码

基于《迁移审计与准备报告》,生成可运行的小程序第一版代码。

硬性要求:
A. 输出完整目录树(含 pages、components、styles、assets
B. 按文件逐个输出:.wxml / .wxss / .js / .json
C. 公共样式抽到 styles/common.wxss页面只放差异
D. 尽量避免第三方库;如必须引入,说明理由和替代方案
E. 对"Web有但小程序不完全支持"的效果:写清替代实现与预期差异

我将提供:
- <PAGE_NAME> 的HTML<<<...>>>
- 全量CSS<<<...>>>
- 截图/设计稿要点:<<<...>>>

请输出:
I. 目录树
II. 逐文件代码(从 app.json/app.wxss/app.js 开始)
III. 编译与运行说明
IV. 第一版"已知差异清单"P0/P1/P2

阶段 3高保真样式对齐

进入"高保真样式对齐"阶段。目标:不破坏现有结构,视觉细节贴近 Web/设计稿。

我会提供:
- 当前小程序代码片段wxml/wxss
- Web 参考HTML/CSS片段或截图差异描述
- 具体差异点列表

你的任务:
A. 每个差异点:根因判断 + 最小修改方案 + 修改后风险
B. 输出"补丁式修改"diff 风格),不要整文件重贴
C. 验收步骤:开发者工具与真机分别怎么验证

约束:
- 优先 flex/盒模型确定性方案,减少魔法数
- px→rpx 换算统一,禁止同一类间距混用单位

阶段 4结构还原与组件化质量提升

对当前小程序实现做"结构与可维护性重构",不改变 UI 效果。

输出:
A. 现状问题清单(重复样式、选择器过深、耦合、命名不一致)
B. 组件拆分方案组件名、props、slot、事件、数据流
C. 样式治理方案BEM/命名规范、公共变量、设计token化
D. 重构后目录树
E. 逐文件补丁diff风格说明每个改动为何不影响UI

约束不引第三方UI库不改变页面路由与业务数据接口。

阶段 5像素级对比与验收清单

输出《像素级对比与验收清单》,用于逐项对照 Web 版本与小程序版本。

要求:
- 按页面输出(每页一个小节)
- 每页包含:布局结构、间距系统、字体系统、颜色、边框/圆角、阴影、
  图片裁切、交互态、滚动与吸顶、列表与空状态
- 每条验收项:怎么看 + 合格标准 + 常见失败表现
- 额外输出《差异追踪表》:
  差异描述|截图/位置严重度P0/P1/P2可能原因建议修复方案修复代价回归点

输出用表格为主,可直接复制当验收用例。

阶段 6编译报错/真机差异快速修复

你是微信小程序调试与兼容性专家。我会贴出:
- 微信开发者工具编译报错/警告日志
- 真机预览与模拟器表现差异描述
- 相关代码片段

请你:
A. 定位根因(按概率排序列出 1~3 个最可能原因)
B. 最小修复补丁diff风格
C. 回归测试点防止修A坏B
D. 如果是基础库兼容问题,说明最低基础库版本或降级方案

十三、输入素材准备指南

每次迁移一个页面前,需按本章要求准备输入素材,提高 AI 转换还原度。

13.1 HTML 文件规范

当前 H5 原型是 Tailwind CDN + 内联 SVG + 原生 JS 的单文件结构,存在以下转换障碍:

  • Tailwind 工具类需逐个展开为 WXSS容易遗漏或换算错误
  • 内联 SVG 在小程序中完全不可用
  • JS 交互逻辑对小程序没有参考价值

推荐做法:

  1. 提供渲染后的最终 DOM浏览器中 Copy outerHTML保存为 docs/h5_ui/rendered/<page-name>.html
  2. 去掉所有 <script> 标签,交互逻辑单独用结构化文本描述
  3. 内联关键样式(二选一):
    • 方式 A手动在 Chrome DevTools → Computed 面板记录关键属性计算值
    • 方式 B推荐在 Chrome Console 执行批量导出脚本

批量导出脚本:

function exportStyles() {
  const props = [
    'width','height','padding','margin','gap',
    'fontSize','fontWeight','lineHeight','color',
    'background','backgroundColor','borderRadius',
    'boxShadow','display','flexDirection','alignItems',
    'justifyContent','position','opacity'
  ];
  const elements = document.querySelectorAll('[class]');
  const result = [];
  elements.forEach((el, i) => {
    const cs = getComputedStyle(el);
    const styles = {};
    props.forEach(p => {
      const v = cs[p];
      if (v && v !== 'none' && v !== 'normal' && v !== '0px' && v !== 'auto') {
        styles[p] = v;
      }
    });
    if (Object.keys(styles).length > 0) {
      result.push({
        tag: el.tagName.toLowerCase(),
        classes: el.className,
        text: el.textContent?.trim().slice(0, 30),
        styles
      });
    }
  });
  console.log(JSON.stringify(result, null, 2));
}
exportStyles();

输出追加到 docs/h5_ui/computed-styles.json(按页面名分 key 存放)。

13.2 设计 Token全局做一次

从 Tailwind 自定义主题提取,保存为 docs/h5_ui/design-tokens.json

{
  "colors": {
    "primary": "#0052d9", "primary-light": "#ecf2fe",
    "success": "#00a870", "warning": "#ed7b2f", "error": "#e34d59",
    "gray-1": "#f3f3f3", "gray-6": "#a6a6a6", "gray-9": "#5e5e5e", "gray-13": "#242424"
  },
  "spacing": { "comment": "Tailwind 1 unit = 4px = 8rpx750rpx 基准)", "base": 8, "unit": "rpx" },
  "borderRadius": { "sm": "8rpx", "md": "16rpx", "lg": "24rpx", "xl": "32rpx" },
  "fontSize": { "xs": "24rpx", "sm": "28rpx", "base": "32rpx", "lg": "36rpx", "xl": "40rpx" },
  "shadows": { "lg": "0 8rpx 32rpx rgba(0,0,0,0.06)", "xl": "0 16rpx 48rpx rgba(0,0,0,0.08)" }
}

13.3 自定义 CSS 特性处理

CSS 特性 小程序支持 处理方式
@keyframes + animation 直接迁移
transition 直接迁移
linear-gradient 直接迁移
backdrop-filter: blur() 改为 rgba() 半透明背景
blur-xlTailwind 去掉或改为纯色
CSS 变量 var() 直接迁移TDesign 大量使用)

13.4 交互描述规范

每个页面提供一份交互说明,保存为 docs/h5_ui/interactions/<page-name>.md,格式:

# 页面名:<page-name>

## 状态变量
| 变量名 | 类型 | 初始值 | 说明 |

## 用户操作 → 响应
| 操作 | 触发条件 | 响应行为 | 目标状态 |

## 页面状态枚举
| 状态名 | 视觉表现 | 触发条件 |

每个页面必须覆盖的状态:正常态(有数据)、空数据态、加载中态、错误态、登录态差异。

13.5 视觉截图规范

  • 分辨率iPhone 6/7/8375×667小程序 750rpx 设计基准的 1:1 对应
  • 命名:<page-name>--<state>.png(如 board-customer--filter-open.png
  • 存放:docs/h5_ui/screenshots/
  • 长页面Chrome DevTools → Ctrl+Shift+P → "Capture full size screenshot"
  • 每页至少截:默认态、关键交互态、空数据态(如适用)、弹窗/浮层

13.6 图标处理

优先级TDesign 内置图标 > 提取独立 SVG > PNG/JPG 图片

图标映射表(全局维护 docs/h5_ui/icon-mapping.md

H5 中的图标描述 处理方式 小程序引用
Logo 台球图标 自定义SVG /assets/icons/icon-billiard.svg
任务管理图标 TDesign <t-icon name="task" />
返回箭头 TDesign <t-icon name="chevron-left" />

13.7 每页准备流程

  1. 在 Chrome 中打开 H5 页面,切换 iPhone 6/7/8 设备模式
  2. 运行 exportStyles() 脚本,输出追加到 computed-styles.json
  3. 截图各状态,放入 screenshots/
  4. 写交互说明,放入 interactions/
  5. 检查图标,补充映射表

13.8 最低限度清单

优先级 材料 作用 频率
P0 截图默认态375px 宽) 校验还原度的唯一视觉参考 每页
P0 交互说明(状态变量 + 操作响应表) 避免逻辑错误 每页
P0 设计 Token JSON 避免颜色/间距换算错误 一次
P1 计算样式 JSON 显著提高还原度 每页(可选)
P1 图标映射表 避免图标处理失误 一次 + 增量
P2 渲染后 DOM 复杂页面时有帮助 按需

十四、长页面迁移专题

17 个迁移页面中task-detail 系列、coach-detail、customer-detail、performance、board-finance 等均为长页面(内容超过一屏)。 本章统一规范长页面的滚动方案、sticky 元素处理、底部固定栏实现、截图对比流程。

14.1 滚动方案选择

场景 方案 说明
整页纵向滚动(绝大多数页面) 页面自然滚动 不需要 scroll-view页面本身就是滚动容器。WXSS 用 min-height: 100vh,不设 overflow: hidden
局部区域独立滚动(如对话消息列表) <scroll-view scroll-y> 必须设固定高度(calc(100vh - 顶部高度 - 底部高度)),否则不滚动
横向滚动列表(如标签栏溢出) <scroll-view scroll-x> white-space: nowrap,子元素 display: inline-block
看板页面board-finance 等) <scroll-view scroll-into-view> 页面主体用 scroll-view 包裹,配合 scroll-into-view 属性实现锚点定位

决策规则:优先用页面自然滚动。只有当页面中某个区域需要独立于页面滚动时(如 chat 页的消息区),才用 scroll-view

scroll-view 页面的识别与处理

部分页面(如 board-finance使用 <scroll-view scroll-into-view="{{scrollIntoView}}"> 作为主滚动容器,而非页面自然滚动。这类页面有以下特征:

  • WXML 中存在 <scroll-view scroll-y scroll-into-view="{{scrollIntoView}}">
  • wx.pageScrollTo() 无效(因为页面本身不滚动,滚动发生在 scroll-view 内部)
  • position: sticky 在 scroll-view 内部不生效

识别方法:在微信开发者工具中执行 wx.pageScrollTo({scrollTop: 100}),如果页面不动,说明是 scroll-view 页面。

对于 scroll-view 页面的像素级对比MP 截图时需要使用 setData({scrollIntoView: '<section-id>'}) 而非 wx.pageScrollTo()。详见第六章 6.3.2 锚点分区对比流程。

14.2 sticky 元素处理

长页面中常见的 sticky 元素:顶部 Tab 栏、筛选栏、分组标题。

/* 顶部 Tab 栏 — 紧贴状态栏下方 */
.tab-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: #ffffff;
}

/* 筛选栏 — 紧贴 Tab 栏下方 */
.filter-bar {
  position: sticky;
  top: 70rpx;  /* = Tab 栏高度 */
  z-index: 15;
  background: #f3f3f3;
}

注意事项:

  • position: sticky 在小程序中正常工作,但父容器不能有 overflow: hidden
  • sticky 元素的 top 值需要考虑状态栏高度(通过 JS wx.getSystemInfoSync().statusBarHeight 获取)
  • 多个 sticky 元素叠加时,z-index 从上到下递减20 → 15 → 10
  • 如果页面使用 navigationStyle: 'custom'sticky top 需要加上 statusBarHeight + 导航栏高度

14.3 底部固定栏实现

task-detail、coach-detail、customer-detail 等页面底部有固定操作栏(问问助手 + 备注)。

<!-- WXML 结构 -->
<view class="page">
  <!-- 可滚动内容区 -->
  <view class="content">
    <!-- 页面主体内容 -->
    ...
    <!-- 底部占位,防止内容被固定栏遮挡 -->
    <view class="bottom-placeholder"></view>
  </view>

  <!-- 底部固定栏 -->
  <view class="bottom-bar">
    <view class="btn-ask">问问助手</view>
    <view class="btn-note">备注</view>
  </view>
</view>
/* WXSS */
.page {
  min-height: 100vh;
  padding-bottom: 0;  /* 不在 page 上加 padding */
}
.bottom-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 50;
  background: #ffffff;
  padding: 16rpx 28rpx;
  padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
  border-top: 2rpx solid #eeeeee;
  display: flex;
  gap: 20rpx;
}
.bottom-placeholder {
  height: 120rpx;  /* ≥ 底部固定栏高度 + safe-area */
}

关键点:

  • position: fixed 而非 sticky(底部栏需要始终可见)
  • 必须加 env(safe-area-inset-bottom) 适配刘海屏底部
  • 必须在内容区末尾加占位 <view>,高度 ≥ 底部栏高度,防止最后一段内容被遮挡
  • 底部栏 z-index: 50,高于所有 sticky 元素但低于弹窗遮罩

14.4 长页面截图对比流程

长页面对比已升级为 v2 逐段截图方案(anchor_compare.py),替代旧的长截图裁剪方式。 完整流程见第六章 6.3.2。本节补充长页面特有的注意事项和实测经验。

v2 逐段截图方案(推荐)

# 提取 H5 锚点坐标 + 逐段截图(需要 Go Live 运行在 5500 端口)
python scripts/ops/anchor_compare.py extract-h5 board-finance

# 生成 MP 截图指令
python scripts/ops/anchor_compare.py mp-inst board-finance

# AI 按指令通过 MCP 工具执行 MP 截图(手动步骤)
# scroll-view 页面使用 scroll-into-view 模式:
#   page.setData({ scrollIntoView: '' })        // 先清空
#   page.setData({ scrollIntoView: '<id>' })    // 设目标
#   等待 1000ms → 截图

# 逐段配对 + 对比
python scripts/ops/anchor_compare.py compare board-finance

board-finance 实测数据2026-03-08 验证)

区域 H5 scrollY 差异率 说明
seg-0 经营一览 16 11.77% 首屏,数据内容差异
seg-1 预收资产 668 8.79% 良好
seg-2 应计收入确认 1393 8.62% 良好
seg-3 现金流入 2757 15.04% 数据行数差异较大
seg-4 现金流出 3233 8.46% 良好
seg-5 助教分析 4042 5.46% 优秀

差异来源分析:

  • 数据内容差异H5 是静态 mock 数据MP 是真实 API 数据,金额/文本不同
  • 字体渲染差异Chromium vs 微信 WebView 的字体 hinting 和亚像素抗锯齿不同
  • 非对齐问题所有段截图尺寸完全一致1290×2256对齐精度 ~1px

scroll-view 页面的 MP 截图要点

board-finance 等使用 <scroll-view scroll-into-view> 的页面MP 截图时:

  1. 先清空 scrollIntoViewpage.setData({ scrollIntoView: '' })
  2. 设置目标:page.setData({ scrollIntoView: 'section-xxx' })
  3. 等待 1000ms 让滚动和渲染完成
  4. 截图645×1128 物理像素 = 430×752 逻辑像素)

两端截图从顶部自然对齐,无需裁剪。

filter-bar 高度统一约束

所有带 filter-bar 的看板页面board-finance、board-coach、board-customerfilter-bar 统一高度为 70 逻辑像素。这确保两端 sticky 区域高度一致(~116px截图自然对齐。

编辑小程序前端页面时须 check 并修正不符合此高度的 filter-bar见需求 11.8)。

短页面对比

短页面或无锚点配置的页面仍可使用全页面对比:

python scripts/ops/resize_and_compare_v2.py

14.5 长页面性能注意事项

风险 触发条件 解决方案
setData 数据量过大 列表 > 50 条 只传变化字段:'list[2].name': 'new'
首屏渲染慢 页面 DOM 节点 > 500 非首屏内容用 wx:if 延迟渲染
滚动卡顿 大量图片 + 复杂布局 <image lazy-load> + 简化嵌套层级
内存溢出 无限滚动列表 本项目列表数据量有限(< 200 条),暂不需要虚拟列表

当前 17 个页面中,数据量最大的是 board-coach 和 board-customer助教/客户卡片列表),单店通常 < 100 条,不需要虚拟列表。如果未来数据量增长,优先考虑分页加载(onReachBottom 触发加载更多)。


十五、交互弹窗与状态管理专题

17 个迁移页面中涉及大量交互弹窗:筛选下拉、长按菜单、备注弹窗、放弃弹窗、指标弹窗、目录面板等。 本章统一规范弹窗实现模式、z-index 分层、动画过渡、三态处理。

15.1 弹窗类型与实现方案

弹窗类型 涉及页面 实现方案 说明
筛选下拉面板 board-finance/coach/customer 自定义组件 filter-dropdown 全屏宽面板 + 遮罩,已有组件规范(第八章 8.3
长按浮层菜单 task-list、board-finance 自定义 view + 遮罩 黑底圆角菜单,定位在长按元素附近
备注弹窗 task-list/detail、coach-detail <t-popup> + 自定义内容 底部弹出,含 textarea + 提交按钮
确认弹窗 task-detail放弃任务 <t-dialog> 标准确认/取消双按钮
指标说明弹窗 board-finance 自定义 view + 遮罩 居中卡片,展示指标解释文本
目录导航面板 board-finance 自定义 view + 遮罩 右侧滑出面板
Toast 提示 所有页面 <t-toast>wx.showToast 操作成功/失败的轻量反馈

决策规则:

  • 标准确认/取消 → <t-dialog>
  • 底部弹出表单/选择器 → <t-popup position="bottom">
  • 需要高度定制样式的浮层(筛选下拉、长按菜单、目录面板)→ 自定义 view + 遮罩
  • 轻量提示 → <t-toast>wx.showToast

15.2 z-index 分层策略(全局统一)

z-index 层级分配(从低到高):

10-29   页面内 sticky 元素
  20    顶部 Tab 栏
  15    筛选栏容器

30-49   页面内浮动元素
  30    AI 悬浮按钮
  50    底部固定操作栏

100-199 自定义底部导航栏
  100   board-tab-bar

900-999 弹窗遮罩层
  999   遮罩背景rgba(0,0,0,0.5)

1000+   弹窗内容层
  1000  筛选下拉面板
  1000  长按浮层菜单
  1000  备注弹窗
  1000  指标说明弹窗
  1000  目录导航面板

9999    Toast / Loading

规则:

  • 同一时刻只允许一个弹窗打开(互斥),因此弹窗内容层统一用 1000
  • 如果未来需要弹窗叠加(如弹窗中再弹确认框),内层用 1100
  • 遮罩和内容必须成对出现,遮罩 z-index = 内容 z-index - 1

15.3 遮罩 + 弹窗通用实现模式

<!-- WXML 通用模式 -->
<!-- 遮罩层 -->
<view
  wx:if="{{showPopup}}"
  class="overlay"
  bindtap="onClosePopup"
  catchtouchmove="preventScroll"
></view>

<!-- 弹窗内容 -->
<view
  wx:if="{{showPopup}}"
  class="popup popup--bottom"
  catchtouchmove="preventScroll"
>
  <!-- 弹窗内容 -->
</view>
/* WXSS 通用样式 */
.overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 999;
}

/* 底部弹出 */
.popup--bottom {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 1000;
  background: #ffffff;
  border-radius: 28rpx 28rpx 0 0;
  padding-bottom: env(safe-area-inset-bottom);
  animation: slideUp 220ms ease;
}

/* 居中弹窗 */
.popup--center {
  position: fixed;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1000;
  background: #ffffff;
  border-radius: 28rpx;
  animation: fadeIn 200ms ease;
}

/* 右侧滑出 */
.popup--right {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 560rpx;
  z-index: 1000;
  background: #ffffff;
  animation: slideLeft 220ms ease;
}

@keyframes slideUp {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}
@keyframes fadeIn {
  from { opacity: 0; transform: translate(-50%, -48%); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes slideLeft {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
// TS 通用逻辑
Page({
  data: {
    showPopup: false,
  },
  onOpenPopup() {
    this.setData({ showPopup: true });
  },
  onClosePopup() {
    this.setData({ showPopup: false });
  },
  // 阻止遮罩层下方页面滚动
  preventScroll() {},
});

关键点:

  • catchtouchmove="preventScroll" 阻止弹窗打开时背景页面滚动空函数即可catch 会阻止事件冒泡)
  • 遮罩 bindtap 关闭弹窗,弹窗内容用 catchtap 防止点击穿透
  • 动画时长统一 200-220ms缓动函数 ease
  • 底部弹窗必须加 env(safe-area-inset-bottom)

15.4 长按菜单实现

task-list 的长按卡片菜单是特殊交互,小程序中用 bindlongpress 实现:

<!-- WXML -->
<view
  wx:for="{{taskList}}"
  wx:key="id"
  class="task-card"
  bindtap="onTaskTap"
  bindlongpress="onTaskLongPress"
  data-id="{{item.id}}"
  data-index="{{index}}"
>
  <!-- 卡片内容 -->
</view>

<!-- 长按菜单(定位在长按位置附近) -->
<view wx:if="{{longPressMenu.visible}}" class="overlay" bindtap="closeLongPressMenu"></view>
<view
  wx:if="{{longPressMenu.visible}}"
  class="longpress-menu"
  style="top: {{longPressMenu.y}}px; left: {{longPressMenu.x}}px;"
>
  <view class="menu-item" bindtap="onMenuAction" data-action="pin_bottom">任务置底</view>
  <view class="menu-item" bindtap="onMenuAction" data-action="ask_ai">问问助手</view>
  <view class="menu-item" bindtap="onMenuAction" data-action="add_note">备注</view>
</view>
.longpress-menu {
  position: fixed;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.85);
  border-radius: 16rpx;
  padding: 8rpx 0;
  min-width: 200rpx;
}
.menu-item {
  padding: 20rpx 32rpx;
  color: #ffffff;
  font-size: 28rpx;
}
.menu-item + .menu-item {
  border-top: 1rpx solid rgba(255, 255, 255, 0.1);
}
onTaskLongPress(e: WechatMiniprogram.TouchEvent) {
  const { id } = e.currentTarget.dataset;
  // 使用触摸点坐标定位菜单
  const touch = e.touches[0];
  this.setData({
    'longPressMenu.visible': true,
    'longPressMenu.taskId': id,
    'longPressMenu.x': touch.clientX,
    'longPressMenu.y': touch.clientY,
  });
},

注意:bindlongpress 触发后会同时触发 bindtap,需要在 onTaskTap 中判断:

onTaskTap(e: WechatMiniprogram.TouchEvent) {
  // 如果长按菜单正在显示,不处理 tap
  if (this.data.longPressMenu.visible) return;
  // 正常跳转逻辑
  const { id } = e.currentTarget.dataset;
  wx.navigateTo({ url: `/pages/task-detail/task-detail?id=${id}` });
},

15.5 三态处理标准模板

每个页面必须处理 loading / empty / error / normal 四种状态。统一模式:

// TS — data 定义
data: {
  pageState: 'loading' as 'loading' | 'empty' | 'error' | 'normal',
  errorMessage: '',
  // ... 业务数据
},

async onLoad() {
  await this.loadData();
},

async loadData() {
  this.setData({ pageState: 'loading' });
  try {
    const data = await api.fetchXxx();
    if (!data || data.length === 0) {
      this.setData({ pageState: 'empty' });
    } else {
      this.setData({ pageState: 'normal', /* 业务数据 */ });
    }
  } catch (err) {
    this.setData({ pageState: 'error', errorMessage: '加载失败,请点击重试' });
  }
},

onRetry() {
  this.loadData();
},
<!-- WXML — 三态切换 -->
<!-- 加载态 -->
<view wx:if="{{pageState === 'loading'}}" class="state-container">
  <t-loading theme="circular" size="40rpx" />
  <text class="state-text">加载中...</text>
</view>

<!-- 空数据态 -->
<view wx:elif="{{pageState === 'empty'}}" class="state-container">
  <text class="state-text">暂无数据</text>
</view>

<!-- 错误态 -->
<view wx:elif="{{pageState === 'error'}}" class="state-container">
  <text class="state-text">{{errorMessage}}</text>
  <t-button theme="primary" size="small" bindtap="onRetry">重试</t-button>
</view>

<!-- 正常态 -->
<view wx:else>
  <!-- 页面主体内容 -->
</view>
/* WXSS — 三态容器 */
.state-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-top: 200rpx;
  gap: 24rpx;
}
.state-text {
  font-size: 28rpx;
  color: #a6a6a6;
}

各页面空状态文案:

页面 空状态文案
task-list 暂无任务
board-coach 暂无助教数据
board-customer 暂无客户数据
board-finance 暂无财务数据
chat-history 暂无对话记录
notes 暂无备注记录
performance-records 暂无业绩明细
customer-service-records 暂无服务记录
其他详情页 暂无数据

15.6 弹窗内表单处理

备注弹窗task-list、task-detail、coach-detail的统一实现

<!-- 备注弹窗 -->
<view wx:if="{{showNotePopup}}" class="overlay" bindtap="closeNotePopup"></view>
<view wx:if="{{showNotePopup}}" class="popup popup--bottom" catchtap>
  <view class="popup-header">
    <text class="popup-title">添加备注</text>
    <view class="popup-close" bindtap="closeNotePopup">
      <t-icon name="close" size="40rpx" color="#a6a6a6" />
    </view>
  </view>
  <t-textarea
    value="{{noteContent}}"
    placeholder="请输入备注内容"
    maxlength="500"
    bind:change="onNoteInput"
    autosize="{{ { minRows: 3, maxRows: 6 } }}"
    t-class="note-textarea"
  />
  <view class="popup-footer">
    <t-button
      theme="primary"
      block
      disabled="{{!noteContent.trim()}}"
      loading="{{noteSubmitting}}"
      bindtap="onSubmitNote"
    >提交</t-button>
  </view>
</view>

关键点:

  • 弹窗内容区用 catchtap(无处理函数)防止点击穿透到遮罩
  • textarea 用 TDesign <t-textarea>,事件是 bind:change
  • 提交按钮在内容为空时 disabled,提交中显示 loading
  • 提交成功后 wx.showToast({ title: '备注已添加', icon: 'success' }) + 关闭弹窗

15.7 交互态验证清单

每个页面迁移完成后,按此清单逐项验证交互态:

# 验证项 验证方法 合格标准
1 弹窗打开/关闭 点击触发元素 → 弹窗出现;点击遮罩/关闭按钮 → 弹窗消失 动画流畅,无闪烁
2 背景滚动锁定 弹窗打开时尝试滑动背景 背景不滚动
3 点击穿透 点击弹窗内容区域 不触发遮罩关闭
4 长按菜单定位 长按列表不同位置的卡片 菜单出现在触摸点附近,不超出屏幕
5 长按 vs 点击 短按卡片 → 跳转;长按卡片 → 菜单 两种手势互不干扰
6 三态切换 模拟 loading/empty/error 三种状态 UI 正确显示
7 错误重试 点击重试按钮 重新加载数据
8 表单提交 填写内容 → 提交 loading 态 → 成功 toast → 弹窗关闭
9 键盘适配 弹窗内 textarea 获取焦点 键盘弹起不遮挡输入区
10 safe-area 底部弹窗在刘海屏设备 底部内容不被遮挡

十六、产品需求摘要PRD

⚠️ 本章仅为迁移时的速查索引,详细度约为原 spec 的 15-20%。 完整需求验收标准、状态变量、操作→响应映射、API 依赖、样式细节、边界条件等) 必须参考原 PRD spec 文件:

  • 任务模块:docs/prd/specs/P6-miniapp-fe-tasks.md
  • 业绩模块:docs/prd/specs/P7-miniapp-fe-performance.md
  • 看板模块:docs/prd/specs/P8-miniapp-fe-boards.md
  • 详情页模块:docs/prd/specs/P9-miniapp-fe-details.md

每个页面的交互子状态(弹窗、下拉、长按菜单、星星评分等)详见: docs/h5_ui/interactions/<page>.md21 个文件,覆盖全部页面)

PRD spec 原文件未被删除,完整保留在 docs/prd/specs/ 目录中。

16.1 全局设计规范

  • 语言:简体中文;金额元取整,万元保留两位小数
  • 底部 TabBar任务 / 看板 / 我的
  • 二级/详情页隐藏原生导航栏,使用自定义头部(左上角返回图标)
  • 悬浮助手按钮:所有业务页面右下角显示,点击进入助手对话页(详见第八章 8.1 节)
  • 错误态:加载失败,请点击重试 + 重试按钮
  • 空数据态:暂无数据 / 暂无任务(纯文字,无插画)
  • 加载态:加载中...(纯文字,无骨架屏)

16.2 页面级需求

task-list任务列表页默认首页

  • 顶部自定义导航栏(标题"任务",无返回按钮)
  • Banner 区:用户名+身份+业绩概览+预计收入,整块可点击跳转业绩详情页
  • 任务列表:单列,按紧急程度排序(红→橙→粉→蓝),不分组
  • 单条卡片:类型标签(带颜色)+ 客户姓名 + 右箭头 + 补充信息行
  • 长按卡片:黑底浮层菜单(任务置底 / 问问助手 / 备注)
  • 空状态:暂无任务

task-detail任务详情页

  • 模块:客户基本信息 → 消费习惯 → 与我的关系(等级+说明)→ 任务建议
  • 底部固定栏:问问助手 + 备注

task-detail-callback / task-detail-priority / task-detail-relationship

  • task-detail 的主题色变体,数据结构和页面布局完全以 task-detail 为准
  • 变体仅差异banner 背景色、按钮配色、页面主题色(对照 H5 原型截图校准色值即可)
  • 迁移策略:先完成 task-detail再复制并替换主题色变量

performance业绩详情页

  • 顶部 Banner用户名+身份+本月业绩进度+预计收入
  • 下方:多组指标,两列卡片网格(收入构成/台球助教业绩/充值业绩/酒水业绩)
  • 指标卡片:名称+当前值+目标值+完成度%
  • 仅展示本月数据,无时间切换

performance-records业绩明细页

  • 业绩详情的明细列表

board-finance看板-财务视图)

  • 筛选时间月份9 选项+指定周期,最大 366 天)+ 区域(全部/大厅ABC/麻将房/团建房/具体台桌)
  • 财务汇总行:实际收入/支出/净利润(三列)
  • 四个板块:营业数据 / 收入构成 / 支出构成 / 利润构成,每行 3 个指标卡片
  • 长按指标卡片:启动助手对话

board-coach看板-助教视图)

  • 筛选排序维度7 选项)+ 擅长项目 + 时间月份
  • 助教卡片:姓名+等级+擅长项目 | 关系最好客户前三 | 排序维度数值
  • 点击进入助教详情页

board-customer看板-客户视图)

  • 筛选客户类型8 维度)+ 偏爱项目
  • 客户卡片:名称+等级+VIP标识 | 最喜欢助教前三 | 核心指标+最近到店
  • 助教身份默认过滤(后台行为,前端不显式展示)
  • 点击进入客户详情页

coach-detail助教详情页

  • 模块:基本信息 → 流水与业绩 → 工资与上课时长 → 前 10 客户指数列表
  • 底部固定栏:问问助手 + 备注

customer-detail客户详情页

  • 模块:基本信息 → 消费习惯(标签+文本)→ 与我的关系(等级+说明)
  • 底部固定栏:问问助手 + 备注

customer-service-records客户服务记录页

  • 客户的服务记录列表

my-profile我的首页

  • 顶部用户信息区 + 列表菜单(备注记录/助手对话记录/首页设置/退出账号)

chat助手对话页

  • 仿微信对话界面(左助手气泡/右用户气泡)
  • 引用内容:灰底小卡片(来源类型+标题+摘要)
  • 输入区:文本框 + 按住说话(语音转文字)+ 发送按钮
  • 会话管理:超 1 小时提示"新对话主题/继续对话"

chat-history助手对话记录页

  • 列表:对话标题+最近时间+消息条数,按更新时间倒序
  • 点击打开对应会话,滚动到最后一条

notes备注记录页

  • 列表按时间倒序平铺,每条:备注全文+关联对象+创建时间
  • 不支持编辑/删除

十七、单页收尾清单

每个页面迁移完成后,逐项检查:

# 检查项 合格标准 验证方式
1 编译零报错 开发者工具无 error/warning 编译面板
2 默认态像素对比 差异率 ≤ 10% scripts/ops/page_compare.py
3 关键交互态截图 弹窗/筛选/空状态均有截图 docs/h5_ui/screenshots/
4 87.5% 缩放一致 所有 rpx 值 = H5 px × 2 × 0.875 抽查 3 个关键元素
5 颜色 Token 一致 使用 design-tokens.json 中定义的颜色 全局搜索硬编码色值
6 TDesign 组件正确 按第九章规范使用 代码审查
7 共享组件复用 filter-bar / card 等按第八章规范 代码审查
8 交互逻辑完整 所有状态变量和操作→响应已实现 对照 interactions/*.md
9 空/错误/加载态 三种状态均已处理 手动触发验证
10 真机预览 iOS + Android 各一台无异常 扫码预览
11 导航正确 返回/跳转/TabBar 行为符合 PRD 手动操作
12 认证守卫 未登录自动跳转登录页 清除 token 后访问

附录:目录结构参考

迁移完成后,docs/h5_ui/ 目录结构应如下:

docs/h5_ui/
├── css/                          # H5 自定义 CSS
├── img/                          # H5 图片资源
├── js/                           # H5 JS 文件
├── pages/                        # H5 原型页面(迁移输入源)
├── rendered/                     # 渲染后 DOM可选
├── computed-styles.json          # 计算样式(按页面名分 key
├── screenshots/                  # 页面截图(必须)
│   ├── <page>--default.png
│   ├── <page>--<state>.png
│   └── ...
├── interactions/                 # 交互说明(必须)
│   ├── <page>.md
│   └── ...
├── design-tokens.json            # 设计 Token必须做一次
├── icon-mapping.md               # 图标映射表(必须,做一次)
└── index.html                    # 入口页

本文档整合自原 apps/miniprogram/doc/ 下 9 个文件,为 H5→小程序迁移的唯一权威参考。 原文件已废弃,不再维护。