# H5 原型 → 微信小程序迁移桥接规范（主规范 v3.0）

> **版本**：v3.0  
> **适用对象**：人工开发、AI 批量迁移、代码审查、验收回归  
> **适用范围**：本项目 `docs/h5_ui/` 原型页面到微信原生小程序的迁移  
> **目标**：实现页面与交互的高还原迁移，样式、层级、动效、状态切换、页面流转 1:1 对齐 H5 验收原型

---

## 📖 文档使用指南

### 本文档定位

这是**执行规程**，不是知识笔记。使用本规范时，AI 或开发者必须做到：

1. 能从单个 H5 页面产出对应的小程序页面文件
2. 能明确哪些能力可直接迁移，哪些必须降级，哪些禁止直迁
3. 能在不使用 DOM API 的前提下，把 H5 交互改造成小程序的数据驱动实现
4. 能输出可交付物，而不是停留在"分析建议"

### 文档结构

- **第一部分（§1-4）**：执行规程 - 输入输出、强制决策、能力分级
- **第二部分（§5-9）**：技术换算 - 尺寸、字号、颜色、布局、装饰
- **第三部分（§10-12）**：交互改造 - WXML 映射、数据驱动、API 替换
- **第四部分（§13）**：执行流程 - 10 步标准流程
- **附录引用（§14）**：4 个附录字典的索引

### 配套文档

- **附录 A**：Spacing 与尺寸字典
- **附录 B**：字体与文本字典（v3.1，含 line-height 规则）
- **附录 C**：颜色字典
- **附录 D**：布局类映射字典
- **附录索引**：`docs/miniprogram-dev/New/h5_to_wechat_miniprogram_migration_bridge_appendix_index_v3.md`

---

## 第一部分：执行规程

## 1. 输入输出契约

### 1.1 输入物

单页迁移时，输入物必须至少包含：

- 对应 H5 页面 HTML 文件
- 该页面引用的 CSS（页面内 `<style>`、独立 css 文件、Tailwind utility）
- 该页面引用的 JS
- 该页面依赖的图片 / 图标 / SVG / JSON Mock
- 页面跳转关系和入口参数

### 1.2 输出物

每个页面迁移后，必须至少产出：

- `pages/<route>/<route>.wxml`
- `pages/<route>/<route>.wxss`
- `pages/<route>/<route>.js`
- `pages/<route>/<route>.json`
- 该页面使用到的 mock 数据文件或页面内 mock 常量
- 迁移说明（至少包含：状态变量、事件、待确认项、降级项）

### 1.3 不允许的输出

以下结果视为未完成：

- 只给"迁移思路"，不产出页面文件
- 只把 Tailwind class 翻译成 CSS，不改 WXML / 交互
- 继续依赖 `document.*`、`window.*`、`history.*`、`localStorage` 等浏览器 API
- 以"兼容性未知"为由保留 H5 写法不处理
- 把高风险 CSS 特性原样搬过去，且不写降级方案

---

## 2. 项目级强制决策

### 2.1 框架与运行时

1. **必须使用微信原生小程序页面体系**：WXML + WXSS + JS + JSON
2. **禁止继续在小程序端运行 Tailwind CDN / 浏览器脚本**
3. **禁止使用 DOM 驱动交互**，页面必须改为数据驱动
4. **不引入 Taro / uni-app / WebView 兜底**，本规范面向原生小程序页面迁移

### 2.2 验收基准

1. H5 原型的主要验收基准宽度为 **412 CSS px**
2. 小程序以 412 宽设备的视觉效果为主验收目标
3. 其他常见宽度设备允许比例差异，但不得出现：文本溢出、遮挡、错层、关键按钮点击区域异常、吸顶/弹层错位

### 2.3 单位策略

采用 **`rpx` 为主、`px` 为辅** 的混合单位方案。

**优先 `rpx`**：
- 页面宽度、横向布局、卡片尺寸
- 主容器 padding / margin、列表间距、栅格
- 吸顶区高度、块级间距

**优先 `px`**：
- 1px 发丝线、阴影 blur/spread
- 小图标、绝对定位微调、小控件尺寸
- 状态栏 / 安全区补偿、细描边

**按效果决定**：
- 字号、圆角、局部 padding、某些 transform 位移

**换算基准**：
```
rpx = H5_CSS_px × (750 / 412)
    ≈ H5_CSS_px × 1.8204
```

**取整规则**：
- 普通布局值：四舍五入到整数 rpx
- 高频 token：沉淀成统一变量表（见附录 A），保持全项目一致
- 视觉敏感值：以真机截图比对微调，不强行套公式

### 2.4 页面滚动策略

1. 默认使用 **页面自然滚动**
2. 只有在必须做局部滚动区域时，才使用 `scroll-view`
3. 吸顶、滚动联动、section 感知优先围绕页面滚动实现；不要无必要把整页包进 `scroll-view`

### 2.5 样式组织策略

1. 全局 token、复用组件样式放 `app.wxss` 或公共样式文件
2. 页面私有样式放页面同名 `.wxss`
3. 样式优先使用 class，动态值用内联 style 绑定
4. 不以 CSS 变量作为唯一运行前提；优先输出静态色值 / 静态 token

### 2.6 文本与行高策略 ⚠️

**关键发现**：微信小程序的 `<text>` 组件不能直接设置 `line-height`，必须通过外层 `<view>` 设置。

**全局设置（推荐）：**
```wxss
page {
  line-height: 1.5; /* Tailwind 默认行高 */
}

view {
  line-height: inherit; /* view 继承 page 的 line-height */
}

/* text 会自动继承外层 view 的 line-height，不需要额外设置 */
```

**局部覆盖：**
```wxss
.section-title {
  font-size: 26rpx;
  line-height: 36rpx; /* 在 view 的 class 上设置，text 会继承 */
  font-weight: 600;
}
```

```wxml
<view class="section-title">
  <text>标题文本</text>
</view>
```

**错误做法：**
```wxss
/* ❌ 直接在 text 上设置 line-height 无效 */
text {
  line-height: 36rpx; /* 不会生效 */
}
```

**执行规则**：
1. 所有文本类样式必须同时设置 `font-size` 和 `line-height`
2. `line-height` 必须在外层 `<view>` 的 class 上设置，不能在 `<text>` 上设置
3. 标准字号的 `line-height` 换算参考附录 B.3
4. 若发现 Y 轴高度与 H5 端不一致，优先检查 `line-height` 是否正确设置

### 2.7 SVG / 图标策略

1. **禁止把 H5 的内联 `<svg>...</svg>` 直接当 WXML DOM 搬运**
2. 资源层允许 `svg` 文件进入小程序项目包，但页面显示是否稳定，必须以目标设备实测为准
3. 默认执行顺序：
   - 简单静态图标：优先导出为独立资源文件
   - 若 `svg` 渲染不稳定：转为 `png`
   - 高频单色图标：允许改为 iconfont / 组件化图标
4. 动态换色的 SVG 不要临时拼接字符串；应改为多份资源、图标组件或样式控制的等价实现

---

## 3. 能力分级（A/B/C 类）

### 3.1 A 类：可直接使用

以下能力在当前项目中视为稳定迁移能力：

- `app.wxss` / 页面 `wxss` / `@import`
- `rpx`
- `.class`、`#id`、元素选择器、`::before`、`::after`
- `flex`、`position: relative/absolute/fixed`
- `box-shadow`、`border-radius`、`linear-gradient`
- `opacity`、常规 `transform`
- `transition`、`animation`
- 动态内联样式绑定
- `onPageScroll`
- `wx.createAnimation()`
- `wx.createSelectorQuery()`
- `wx.createIntersectionObserver()`
- `navigator` / `wx.navigateTo` / `wx.redirectTo` / `wx.switchTab` / `wx.navigateBack`
- `view` / `navigator` 的 `hover-class`

### 3.2 B 类：有条件使用，必须带回退或验证

以下能力允许使用，但不能把它们当成项目硬依赖：

- `position: sticky`
- `grid`
- `filter: blur()`
- `backdrop-filter`
- `clip-path`
- `vh`
- `line-clamp`
- 复杂组合选择器
- 资源型 `svg`
- CSS 变量

**执行规则**：
1. 使用前必须确认该页面已有回退方案
2. 若页面对视觉或交互依赖度高，则必须做真机验证
3. 若 B 类能力失效，不得阻塞交付，必须切换为本规范定义的降级实现

### 3.3 C 类：禁止直迁

以下能力不得原样从 H5 搬到小程序：

- `document.getElementById/querySelector/querySelectorAll`
- `classList.add/remove/toggle`
- 直接改 `element.style.xxx`
- `innerHTML` / `textContent` 方式渲染视图
- `window.scrollY` / `window.scrollTo`
- `history.back`
- `localStorage` / `sessionStorage`
- `alert` / `confirm`
- CSS `env(safe-area-inset-*)`
- `:hover` / `group-hover:*`
- 依赖浏览器焦点模型的 `:focus` / `:active` 交互
- 依赖 `:first-child/:last-child/:nth-child` 才能成立的视觉规则

---

## 4. Tailwind CSS 在本项目中的行为

### 4.1 Tailwind CSS 渲染机制

本项目 H5 原型使用 `<script src="https://cdn.tailwindcss.com"></script>`（Tailwind CSS v3 CDN Play 模式）。

Tailwind v3 默认主题中，大量 spacing、font-size、border-radius token 主要使用 `rem` 体系。在浏览器默认根字号 `1rem = 16px` 且页面未主动改写 `html { font-size }` 的前提下，这些 rem 体系的 utility 会计算成固定 CSS px 值。

**关键事实**：Tailwind 默认 rem 体系的 utility class 在任何设备上都会计算为固定的 CSS px 值，与设备布局宽度无关。

### 4.2 微信 rpx 的精确定义

来源：[微信官方 WXSS 文档](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html)

> rpx（responsive pixel）：可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。

换算公式：
```
1rpx = 屏幕宽度px / 750
1px  = 750 / 屏幕宽度px × rpx
```

官方示例：
| 设备 | 屏幕宽度 | 1rpx = ?px | 1px = ?rpx |
|------|----------|-----------|-----------|
| iPhone 5 | 320px | 0.42px | 2.34rpx |
| iPhone 6 | 375px | 0.5px | 2rpx |
| iPhone 6 Plus | 414px | 0.552px | 1.81rpx |

### 4.3 本项目的换算链路

验收基准设备：等效宽度 412px

完整链路：`Tailwind class → CSS 计算值(px) → rpx`

```
rpx = CSS_px × (750 / 412)
    = CSS_px × 1.82039...
```

---

**（待续：第二部分将包含详细的技术换算表）**

---

## 📚 快速导航

- **执行规程**：§1-4（当前部分）
- **技术换算**：§5-9（见下文）
- **交互改造**：§10-12（见下文）
- **执行流程**：§13（见下文）
- **附录索引**：§14（见下文）

---

## 版本历史

- **v3.0**（2026-03-14）：合并 v1.0 技术换算基础和 v2.0 执行规范，补充 line-height 关键发现
- **v2.0**：执行规范，强化输入输出契约和能力分级
- **v1.0**：技术换算基础，建立完整的映射表
# H5 原型 → 微信小程序迁移桥接规范（主规范 v3.0 - 第二部分）

> 接续第一部分，本部分包含：§5-9 技术换算基础

---

## 第二部分：技术换算基础

## 5. Tailwind Spacing → rpx 完整换算表

Tailwind v3 的 spacing scale 基于 `0.25rem = 4px` 步进。以下是本项目原型中实际使用的所有 spacing 值的精确换算。这些 rpx 值已在已迁移页面中使用，作为项目统一 token，后续页面应保持一致。

### 5.1 Tailwind spacing scale（默认）

| Tailwind 值 | rem | CSS px | rpx（项目 token） | 常见用途 |
|-------------|-----|--------|---------------------|---------|
| `0.5` | 0.125rem | 2px | 4rpx | gap-0.5, p-0.5, mt-0.5 |
| `px` | 1px | 1px | 2rpx | py-px, w-px |
| `1` | 0.25rem | 4px | 8rpx | gap-1, p-1, mt-1, mb-1 |
| `1.5` | 0.375rem | 6px | 12rpx | gap-1.5, p-1.5, mt-1.5 |
| `2` | 0.5rem | 8px | 14rpx | gap-2, p-2, mt-2, mb-2 |
| `2.5` | 0.625rem | 10px | 18rpx | gap-2.5, p-2.5, mb-2.5 |
| `3` | 0.75rem | 12px | 22rpx | gap-3, p-3, mb-3, pl-3 |
| `3.5` | 0.875rem | 14px | 26rpx | p-3.5, h-3.5, w-3.5 |
| `4` | 1rem | 16px | 30rpx | gap-4, p-4, px-4, mb-4 |
| `5` | 1.25rem | 20px | 36rpx | p-5, px-5, mb-5, pb-5 |
| `6` | 1.5rem | 24px | 44rpx | gap-6, p-6, px-6, mb-6 |
| `7` | 1.75rem | 28px | 52rpx | h-7, w-7 |
| `8` | 2rem | 32px | 58rpx | px-8, mb-8, pb-8, h-8 |
| `10` | 2.5rem | 40px | 72rpx | h-10, w-10, pb-10 |
| `12` | 3rem | 48px | 88rpx | h-12, w-12, pb-12 |
| `16` | 4rem | 64px | 116rpx | h-16, w-16 |
| `20` | 5rem | 80px | 146rpx | h-20, w-20, top-20 |
| `24` | 6rem | 96px | 174rpx | h-24, w-24 |
| `32` | 8rem | 128px | 232rpx | h-32, w-32 |
| `40` | 10rem | 160px | 292rpx | bottom-40, top-40 |

**详细完整表格**：参见附录 A

---

## 6. Tailwind 字号 → WXSS font-size + line-height 完整换算表

### 6.1 核心规则

Tailwind v3 的字号类（`text-xs`、`text-sm` 等）同时设置 `font-size` 和 `line-height`。

例如 `text-sm` 生成的 CSS 是：
```css
font-size: 0.875rem;    /* 14px */
line-height: 1.25rem;   /* 20px */
```

**强制规则**：每个 Tailwind 字号类转 WXSS 时，必须同时写 `font-size` 和 `line-height`。

### 6.2 微信小程序 text 组件的 line-height 限制 ⚠️

**关键发现**：微信小程序的 `<text>` 组件不能直接设置 `line-height`，必须通过外层 `<view>` 设置。

**正确做法**：参见 §2.6

**原因**：微信小程序的 `<text>` 是特殊的内联组件，直接设置 `line-height` 不生效，必须在外层 `<view>` 上设置。

**验证方法**：在开发者工具的 Computed 面板中，text 元素不会显示 `line-height` 属性，但外层 view 的 `height` 值会包含行高效果。

### 6.3 标准字号类换算表

| Tailwind class | font-size (px) | line-height (px) | WXSS font-size | WXSS line-height |
|---------------|---------------|-----------------|----------------|-----------------|
| `text-xs` | 12px | 16px | 22rpx | 29rpx |
| `text-sm` | 14px | 20px | 26rpx | 36rpx |
| `text-base` | 16px | 24px | 30rpx | 44rpx |
| `text-lg` | 18px | 28px | 33rpx | 51rpx |
| `text-xl` | 20px | 28px | 36rpx | 51rpx |
| `text-2xl` | 24px | 32px | 44rpx | 58rpx |
| `text-3xl` | 30px | 36px | 54rpx | 66rpx |

**换算公式**：
```
font-size(rpx) = font-size(px) × 1.8204
line-height(rpx) = line-height(px) × 1.8204
```

**详细完整表格**：参见附录 B

### 6.4 font-weight 映射

| Tailwind class | CSS font-weight | WXSS |
|---------------|----------------|------|
| `font-normal` | 400 | `font-weight: 400` |
| `font-medium` | 500 | `font-weight: 500` |
| `font-semibold` | 600 | `font-weight: 600` |
| `font-bold` | 700 | `font-weight: 700` |

**注意**：`font-medium` 是 500 不是 600。这是常见错误。

### 6.5 其他文本属性

| Tailwind class | WXSS |
|---------------|------|
| `text-center` | `text-align: center` |
| `text-right` | `text-align: right` |
| `text-left` | `text-align: left` |
| `truncate` | `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` |
| `line-clamp-2` | `display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden` |
| `whitespace-nowrap` | `white-space: nowrap` |
| `underline` | `text-decoration: underline` |
| `line-through` | `text-decoration: line-through` |

---

## 7. Tailwind 颜色 → WXSS 颜色完整映射

### 7.1 项目自定义颜色

每个 HTML 页面的 `<script>` 中定义了相同的自定义颜色扩展：

| Tailwind 名称 | 色值 | WXSS 变量 |
|--------------|------|----------|
| `primary` | `#0052d9` | `var(--color-primary)` |
| `primary-light` | `#ecf2fe` | `var(--color-primary-light)` |
| `success` | `#00a870` | `var(--color-success)` |
| `warning` | `#ed7b2f` | `var(--color-warning)` |
| `error` | `#e34d59` | `var(--color-error)` |
| `gray-1` | `#f3f3f3` | `var(--color-gray-1)` |
| `gray-7` | `#8b8b8b` | `var(--color-gray-7)` |
| `gray-9` | `#5e5e5e` | `var(--color-gray-9)` |
| `gray-13` | `#242424` | `var(--color-gray-13)` |

**详细完整表格**：参见附录 C

### 7.2 透明度变体

原型大量使用 Tailwind 的透明度修饰符（`/N`），如 `bg-primary/10`、`text-white/60`。

WXSS 映射方式：使用 `rgba()` 或直接写带透明度的色值。

| 透明度后缀 | alpha 值 | 示例 |
|-----------|---------|------|
| `/10` | 0.10 | `bg-primary/10` → `rgba(0, 82, 217, 0.10)` |
| `/20` | 0.20 | `bg-white/20` → `rgba(255, 255, 255, 0.20)` |
| `/50` | 0.50 | `bg-black/50` → `rgba(0, 0, 0, 0.50)` |
| `/80` | 0.80 | `bg-white/80` → `rgba(255, 255, 255, 0.80)` |

---

## 8. Tailwind 布局类 → WXSS 完整映射

### 8.1 Flexbox

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `flex` | `display: flex` | `display: flex` |
| `flex-col` | `flex-direction: column` | `flex-direction: column` |
| `flex-1` | `flex: 1 1 0%` | `flex: 1` |
| `flex-shrink-0` | `flex-shrink: 0` | `flex-shrink: 0` |
| `flex-wrap` | `flex-wrap: wrap` | `flex-wrap: wrap` |
| `items-center` | `align-items: center` | `align-items: center` |
| `items-start` | `align-items: flex-start` | `align-items: flex-start` |
| `justify-between` | `justify-content: space-between` | `justify-content: space-between` |
| `justify-center` | `justify-content: center` | `justify-content: center` |

### 8.2 Grid

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `grid` | `display: grid` | `display: grid` |
| `grid-cols-2` | `grid-template-columns: repeat(2, minmax(0, 1fr))` | `grid-template-columns: repeat(2, minmax(0, 1fr))` |
| `grid-cols-3` | `grid-template-columns: repeat(3, minmax(0, 1fr))` | `grid-template-columns: repeat(3, minmax(0, 1fr))` |
| `grid-cols-4` | `grid-template-columns: repeat(4, minmax(0, 1fr))` | `grid-template-columns: repeat(4, minmax(0, 1fr))` |

### 8.3 Gap（间距）

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `gap-1` | `gap: 4px` | `gap: 8rpx` |
| `gap-2` | `gap: 8px` | `gap: 14rpx` |
| `gap-3` | `gap: 12px` | `gap: 22rpx` |
| `gap-4` | `gap: 16px` | `gap: 30rpx` |
| `gap-x-3` | `column-gap: 12px` | `column-gap: 22rpx` |
| `gap-y-2` | `row-gap: 8px` | `row-gap: 14rpx` |

### 8.4 定位

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `relative` | `position: relative` | `position: relative` |
| `absolute` | `position: absolute` | `position: absolute` |
| `fixed` | `position: fixed` | `position: fixed` |
| `sticky` | `position: sticky` | 见 §3.2 兼容性说明 |
| `inset-0` | `inset: 0` | `top: 0; right: 0; bottom: 0; left: 0` |
| `top-0` | `top: 0` | `top: 0` |
| `z-10` | `z-index: 10` | `z-index: 10` |
| `z-50` | `z-index: 50` | `z-index: 50` |

### 8.5 尺寸

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `w-full` | `width: 100%` | `width: 100%` |
| `h-full` | `height: 100%` | `height: 100%` |
| `w-1/2` | `width: 50%` | `width: 50%` |
| `min-h-screen` | `min-height: 100vh` | `min-height: 100vh` |

**详细完整表格**：参见附录 D

---

## 9. Tailwind 装饰类 → WXSS 完整映射

### 9.1 圆角

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `rounded` | `border-radius: 4px` | `border-radius: 8rpx` |
| `rounded-lg` | `border-radius: 8px` | `border-radius: 14rpx` |
| `rounded-xl` | `border-radius: 12px` | `border-radius: 22rpx` |
| `rounded-2xl` | `border-radius: 16px` | `border-radius: 30rpx` |
| `rounded-full` | `border-radius: 9999px` | `border-radius: 50%` 或 `border-radius: 999rpx` |

### 9.2 阴影

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `shadow-sm` | `box-shadow: 0 1px 2px 0 rgba(0,0,0,0.05)` | `box-shadow: 0 2rpx 4rpx 0 rgba(0,0,0,0.05)` |
| `shadow-lg` | `box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)` | `box-shadow: 0 18rpx 28rpx -6rpx rgba(0,0,0,0.1), 0 8rpx 12rpx -8rpx rgba(0,0,0,0.1)` |

### 9.3 边框

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `border` | `border: 1px solid` | `border: 1px solid` |
| `border-2` | `border: 2px solid` | `border: 2px solid` |
| `border-b` | `border-bottom: 1px solid` | `border-bottom: 1px solid` |
| `border-t` | `border-top: 1px solid` | `border-top: 1px solid` |

### 9.4 渐变

| Tailwind class | CSS | WXSS |
|---------------|-----|------|
| `bg-gradient-to-r` | `background-image: linear-gradient(to right, ...)` | `background: linear-gradient(to right, ...)` |
| `bg-gradient-to-br` | `background-image: linear-gradient(to bottom right, ...)` | `background: linear-gradient(to bottom right, ...)` |
| `from-X` | 渐变起始色 | 渐变第一个色值 |
| `to-X` | 渐变结束色 | 渐变最后一个色值 |

示例：`bg-gradient-to-br from-blue-400 to-blue-500`  
→ WXSS: `background: linear-gradient(to bottom right, #60a5fa, #3b82f6)`

### 9.5 透明度与模糊

| Tailwind class | CSS | WXSS | 小程序兼容性 |
|---------------|-----|------|-------------|
| `opacity-30` | `opacity: 0.3` | `opacity: 0.3` | ✓ |
| `opacity-60` | `opacity: 0.6` | `opacity: 0.6` | ✓ |
| `backdrop-blur-md` | `backdrop-filter: blur(12px)` | 见 §3.2 | ⚠️ 部分支持 |
| `blur-xl` | `filter: blur(24px)` | `filter: blur(24px)` | ✓ |

---

**（待续：第三部分将包含交互改造）**

---

## 📚 快速导航

- **执行规程**：§1-4（见第一部分）
- **技术换算**：§5-9（当前部分）
- **交互改造**：§10-12（见第三部分）
- **执行流程**：§13（见第三部分）
- **附录索引**：§14（见第三部分）
# H5 原型 → 微信小程序迁移桥接规范（主规范 v3.0 - 第三部分）

> 接续第二部分，本部分包含：§10-12 交互改造、§13 执行流程、§14 附录索引

---

## 第三部分：交互改造

## 10. HTML 标签 → WXML 组件映射

### 10.1 基础标签映射

| H5 标签 | WXML 组件 | 说明 |
|--------|----------|------|
| `<div>` | `<view>` | 通用容器 |
| `<span>` | `<text>` | 行内文本。注意：`<text>` 内只能嵌套 `<text>`，不能嵌套 `<view>` |
| `<p>` | `<view>` 或 `<text>` | 段落 |
| `<img>` | `<image>` | 图片。必须指定 `mode` 属性 |
| `<a>` | `<navigator>` 或 `<view bindtap>` | 链接/跳转 |
| `<button>` | `<button>` 或 `<view bindtap>` | 按钮 |
| `<input>` | `<input>` | 输入框 |
| `<textarea>` | `<textarea>` | 多行输入 |
| `<svg>` | `<image src="xxx.svg">` | 小程序不支持内联 SVG |
| `<ul>` / `<ol>` / `<li>` | `<view>` | 列表容器 |

### 10.2 image 组件的 mode 属性

| H5 CSS | image mode |
|--------|-----------|
| `object-fit: cover` | `mode="aspectFill"` |
| `object-fit: contain` | `mode="aspectFit"` |
| 无特殊设置 | `mode="widthFix"`（宽度撑满，高度自适应） |

### 10.3 事件属性映射

| H5 事件 | WXML 事件 | 说明 |
|--------|----------|------|
| `onclick` | `bindtap` | 点击 |
| `onchange` | `bindchange` | 值变化 |
| `oninput` | `bindinput` | 输入 |
| `onscroll` | `bindscroll`（scroll-view）或 `onPageScroll`（页面） | 滚动 |
| `ontouchstart` | `bindtouchstart` | 触摸开始 |
| `ontouchend` | `bindtouchend` | 触摸结束 |
| `oncontextmenu` | `bindlongpress` | 长按 |

**注意**：
- `bind` 前缀事件会冒泡
- `catch` 前缀事件会阻止冒泡（等同于 `e.stopPropagation()`）
- `catchtouchmove`：阻止触摸移动冒泡（常用于遮罩层阻止背景滚动）

---

## 11. JS DOM 操作 → 小程序数据驱动完整映射

### 11.1 总体原则

**H5 模式**：`用户操作 → JS 查询 DOM → 修改 DOM 属性/样式/内容 → 视觉变化`

**小程序模式**：`用户操作 → 事件处理函数 → setData 更新数据 → WXML 自动重渲染 → 视觉变化`

**禁止在小程序中使用的 H5 API**：
- `document.getElementById()`
- `document.querySelector()` / `querySelectorAll()`
- `element.classList.add/remove/toggle/contains`
- `element.style.xxx = yyy`
- `element.innerHTML` / `textContent`
- `document.createElement()` / `appendChild()`

### 11.2 模式 A：显示/隐藏切换

**H5 写法**：
```javascript
document.getElementById('modal').classList.remove('hidden')
document.getElementById('modal').classList.add('flex')
```

**小程序写法**：
```javascript
// JS
Page({
  data: { showModal: false },
  openModal() { this.setData({ showModal: true }) },
  closeModal() { this.setData({ showModal: false }) }
})
```

```xml
<!-- WXML -->
<view wx:if="{{showModal}}" class="modal-overlay" catchtouchmove bindtap="closeModal">
  <view class="modal-content" catchtap>
    <!-- 弹窗内容 -->
  </view>
</view>
```

### 11.3 模式 B：Tab/状态切换

**H5 写法**：
```javascript
function switchTab(tabName) {
  document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'))
  document.getElementById('tab-' + tabName).classList.add('active')
}
```

**小程序写法**：
```javascript
Page({
  data: { activeTab: 'basic' },
  onTabChange(e) {
    this.setData({ activeTab: e.currentTarget.dataset.tab })
  }
})
```

```xml
<view class="tab {{activeTab === 'basic' ? 'tab--active' : ''}}"
      data-tab="basic" bindtap="onTabChange">基础课</view>
<view class="tab {{activeTab === 'vip' ? 'tab--active' : ''}}"
      data-tab="vip" bindtap="onTabChange">VIP课</view>

<view wx:if="{{activeTab === 'basic'}}">基础课内容</view>
<view wx:if="{{activeTab === 'vip'}}">VIP课内容</view>
```

### 11.4 模式 C：展开/收起

**H5 写法**：
```javascript
function toggleExpand(id) {
  const el = document.getElementById(id)
  el.classList.toggle('hidden')
}
```

**小程序写法**：
```javascript
Page({
  data: { expandedSections: {} },
  toggleSection(e) {
    const key = e.currentTarget.dataset.key
    this.setData({ [`expandedSections.${key}`]: !this.data.expandedSections[key] })
  }
})
```

```xml
<view bindtap="toggleSection" data-key="detail">
  <text>{{expandedSections.detail ? '收起' : '展开更多'}}</text>
</view>
<view wx:if="{{expandedSections.detail}}">
  <!-- 展开内容 -->
</view>
```

### 11.5 模式 D：内容动态更新

**H5 写法**：
```javascript
document.getElementById('total').textContent = '¥12,345'
document.getElementById('list').innerHTML = items.map(i => `<div>${i.name}</div>`).join('')
```

**小程序写法**：
```javascript
Page({
  data: { total: '¥12,345', items: [] },
  loadData() {
    this.setData({
      total: '¥12,345',
      items: [{ name: '台桌' }, { name: '酒水' }]
    })
  }
})
```

```xml
<text>{{total}}</text>
<view wx:for="{{items}}" wx:key="name">
  <text>{{item.name}}</text>
</view>
```

### 11.6 模式 E：滚动联动

**H5 写法**：
```javascript
window.addEventListener('scroll', () => {
  const currentY = window.scrollY
  if (currentY > 100) {
    filterBar.classList.add('filter-bar-hidden')
  }
})
```

**小程序写法**：
```javascript
Page({
  data: { filterBarVisible: true },
  _lastScrollTop: 0,
  _throttleTimer: null,

  onPageScroll(e) {
    // 节流：避免每帧 setData
    if (this._throttleTimer) return
    this._throttleTimer = setTimeout(() => {
      this._throttleTimer = null
    }, 100)

    const scrollTop = e.scrollTop
    const shouldHide = scrollTop > 100

    // 只在状态真正变化时 setData
    if (shouldHide !== !this.data.filterBarVisible) {
      this.setData({ filterBarVisible: !shouldHide })
    }
  }
})
```

**关键规则**：
1. 滚动事件处理函数中必须做节流（100ms 以上）
2. 只在状态真正变化时调用 `setData`，避免无意义的重渲染
3. 不要在滚动事件中更新大量数据或触发复杂计算

### 11.7 模式 F：页面导航

**H5 写法**：
```javascript
window.location.href = 'task-detail.html?id=123'
history.back()
```

**小程序写法**：
```javascript
// 普通页面跳转
wx.navigateTo({ url: '/pages/task-detail/task-detail?id=123' })

// 返回上一页
wx.navigateBack()

// TabBar 页面跳转
wx.switchTab({ url: '/pages/task-list/task-list' })

// 重定向（替换当前页）
wx.redirectTo({ url: '/pages/login/login' })
```

**关键规则**：TabBar 页面（在 `app.json` 的 `tabBar.list` 中定义的）必须用 `wx.switchTab`，用 `navigateTo` 会静默失败。

---

## 12. 浏览器 API → 微信小程序 API 完整映射

### 12.1 导航

| H5 API | 小程序 API | 说明 |
|--------|-----------|------|
| `window.location.href = url` | `wx.navigateTo({ url })` | 普通页面跳转 |
| `window.location.replace(url)` | `wx.redirectTo({ url })` | 替换当前页 |
| `history.back()` | `wx.navigateBack()` | 返回上一页 |
| — | `wx.switchTab({ url })` | TabBar 页面跳转（必须用这个） |

### 12.2 滚动

| H5 API | 小程序 API | 说明 |
|--------|-----------|------|
| `window.scrollY` | `onPageScroll(e)` 中 `e.scrollTop` | 页面滚动位置 |
| `window.scrollTo(0, y)` | `wx.pageScrollTo({ scrollTop: y })` | 滚动到指定位置 |
| `element.scrollIntoView()` | `scroll-view` 的 `scroll-into-view="{{id}}"` | 滚动到指定元素 |
| `element.getBoundingClientRect()` | `wx.createSelectorQuery().select('.cls').boundingClientRect()` | 获取元素位置（异步） |

### 12.3 存储

| H5 API | 小程序 API | 说明 |
|--------|-----------|------|
| `localStorage.setItem(k, v)` | `wx.setStorageSync(k, v)` | 同步写入 |
| `localStorage.getItem(k)` | `wx.getStorageSync(k)` | 同步读取 |
| `localStorage.removeItem(k)` | `wx.removeStorageSync(k)` | 同步删除 |
| `localStorage.clear()` | `wx.clearStorageSync()` | 清空全部 |

### 12.4 剪贴板

| H5 API | 小程序 API |
|--------|-----------|
| `navigator.clipboard.writeText(text)` | `wx.setClipboardData({ data: text })` |
| `navigator.clipboard.readText()` | `wx.getClipboardData()` |

### 12.5 对话框

| H5 API | 小程序 API |
|--------|-----------|
| `alert(msg)` | `wx.showModal({ title: '', content: msg, showCancel: false })` |
| `confirm(msg)` | `wx.showModal({ title: '提示', content: msg })` |
| — | `wx.showToast({ title: msg, icon: 'success' })` |

### 12.6 系统信息

| 需求 | 小程序 API |
|------|-----------|
| 状态栏高度 | `wx.getSystemInfoSync().statusBarHeight` |
| 屏幕宽度 | `wx.getSystemInfoSync().windowWidth` |
| 安全区域 | `wx.getSystemInfoSync().safeArea` |
| 底部安全区高度 | `screenHeight - safeArea.bottom` |

---

## 第四部分：执行流程

## 13. 页面转换的标准执行流程（10 步）

每迁移 1 个页面，都必须按以下顺序执行。

### 步骤 1：页面资产盘点

**输入**：HTML / CSS / JS / 图片资源

**动作**：
- 抽取页面标题、路由名、入口参数、返回路径
- 列出页面块：顶部、卡片区、列表区、底部操作区、弹层、浮层、Toast
- 列出页面状态：默认态、空态、加载态、错误态、选中态、展开态、禁用态
- 列出页面动效：淡入、位移、缩放、吸顶、滚动联动、长按、评分拖动

**输出**：页面迁移卡

### 步骤 2：结构改写为 WXML 语义树

**动作**：
- `div` → `view`
- 文本优先用 `text`
- 图片用 `image`
- 链接/跳转用 `navigator` 或 `bindtap`
- 表单元素替换为原生小程序组件
- 列表一律改为 `wx:for`
- 条件显示一律改为 `wx:if` / `hidden` / 条件 class

**禁止**：
- 保留 H5 DOM 层级只是把标签名替换一下
- 在 `text` 内嵌套 `view`
- 用字符串拼接 HTML 片段

### 步骤 3：Tailwind 与自定义 CSS 拆解

**动作**：
- 先按视觉功能拆成：布局、尺寸、文本、颜色、装饰、状态、动画
- 再判断每个值使用 `rpx` 还是 `px`
- 统一抽出公共 token，不允许每页各自发散命名

**要求**：
- 不保留"一长串 utility class 直接照搬"的中间态
- 对频繁出现的组合样式抽成语义类，如 `.card`、`.section-title`、`.tab--active`

### 步骤 4：交互从 DOM 驱动改为数据驱动

**动作**：
- 把所有 DOM 查询改成状态变量
- 把显隐、激活、展开、选中、校验、加载都改成 `data` 驱动
- 把滚动联动改成 `onPageScroll` + 节流 + 仅在状态变化时 `setData`

**输出**：状态表

状态表至少包含：
- 状态变量名
- 默认值
- 可选值
- 受哪个事件修改
- 影响哪些视图

### 步骤 5：页面级动效实现

**执行规则**：
- 纯显隐 / 透明度 / 位移 / 简单位移动画：优先 `transition` / `animation`
- 需要顺序编排、组合变换、受事件精确驱动：用 `wx.createAnimation()`
- 高频交互不要每帧 `setData`

### 步骤 6：导航与参数改造

**动作**：
- 普通页面：`wx.navigateTo`
- 替换当前页：`wx.redirectTo`
- tabBar 页面：`wx.switchTab`
- 返回：`wx.navigateBack`
- 页面参数：统一在 `onLoad(query)` 中读取

### 步骤 7：安全区与导航栏补偿

**动作**：
- 禁止使用 `env(safe-area-inset-*)`
- 通过系统信息计算状态栏与底部安全区
- 自定义导航栏页面必须统一实现，不得一页一套

### 步骤 8：Mock 数据接入

**动作**：
- 页面先用 mock 跑通
- 模板静态文案与后端数据字段分开
- 所有列表、徽标、状态标签都要有 mock

### 步骤 9：真机回归与像素微调

**动作**：
- 先看 412 宽主验收设备
- 再看至少一台 375 宽设备
- 出现不对齐时优先微调局部 `px/rpx`，不要推翻整体单位策略

### 步骤 10：记录迁移说明

每页必须输出迁移说明，至少包含：
- 页面依赖资源
- 迁移状态变量
- 高风险点
- 已做降级项

---

## 14. 附录文档索引

### 14.1 附录清单

位于 `docs/miniprogram-dev/New/`：

1. **附录索引**：`h5_to_wechat_miniprogram_migration_bridge_appendix_index_v3.md`（v3.1）
2. **附录 A**：`appendix_a_spacing_and_sizing_dictionary_v3.md` - Spacing 与尺寸字典
3. **附录 B**：`appendix_b_typography_and_text_dictionary_v3.md`（v3.1）- 字体与文本字典
4. **附录 C**：`appendix_c_color_dictionary_v3.md` - 颜色字典
5. **附录 D**：`appendix_d_layout_class_mapping_dictionary_v3.md` - 布局类映射字典

### 14.2 使用方式

**AI 使用顺序**：
1. 加载本主规范（v3.0）
2. 加载附录索引
3. 按需加载具体附录（A/B/C/D）
4. 执行迁移

**开发者使用顺序**：
1. 先读本主规范了解整体流程和规则
2. 迁移时查阅附录进行 token 映射
3. 遇到问题时回到主规范查找解决方案

### 14.3 附录更新记录

- **v3.1**（2026-03-14）：附录 B 补充 text 组件的 line-height 设置规则
- **v3.0**：初始版本，建立附录索引和四个附录字典

---

## 📚 完整导航

- **第一部分：执行规程**（§1-4）
  - 输入输出契约
  - 项目级强制决策
  - 能力分级（A/B/C 类）
  - Tailwind CSS 行为说明

- **第二部分：技术换算**（§5-9）
  - Spacing 换算表
  - 字号与行高换算
  - 颜色映射
  - 布局类映射
  - 装饰类映射

- **第三部分：交互改造**（§10-12）
  - HTML→WXML 映射
  - DOM 操作→数据驱动
  - 浏览器 API→小程序 API

- **第四部分：执行流程**（§13-14）
  - 10 步标准流程
  - 附录文档索引

---

## 版本历史

- **v3.0**（2026-03-14）：合并 v1.0 技术换算基础和 v2.0 执行规范，补充 line-height 关键发现
- **v2.0**：执行规范，强化输入输出契约和能力分级
- **v1.0**：技术换算基础，建立完整的映射表

---

## 📖 使用建议

### 对于 AI

1. **首次加载**：加载本主规范全文 + 附录索引
2. **执行迁移**：按 §13 的 10 步流程执行
3. **查表映射**：按需加载附录 A/B/C/D
4. **验证输出**：确保符合 §1.2 的输出物要求

### 对于开发者

1. **快速上手**：先读 §1-4 了解规则和决策
2. **技术细节**：需要时查阅 §5-12 的换算表
3. **执行迁移**：按 §13 的流程逐步完成
4. **问题排查**：回到对应章节查找解决方案

---

**文档完整，可以开始迁移！** 🚀
