- name
- html-to-miniprogram
- description
- 将 HTML/React/Vue 等前端 Demo 页面转换为微信小程序原生开发项目。重点是转换前端页面和简单交互(页面跳转、提醒等),不涉及业务逻辑,数据集中在 mock.js 中管理。
# HTML Demo 转微信小程序 Skill
将任意前端 Demo(HTML/React/Vue 单文件或多文件)转换为**微信小程序原生开发**项目,精准还原 UI 和简单交互。
> [!IMPORTANT]
> **转换范围**:仅转换前端页面 UI 和简单交互(页面跳转、Tab 切换、Toast 提醒、弹窗等),**不实现业务逻辑**(如网络请求、用户认证、数据持久化等)。所有页面用到的数据统一整合在 `utils/mock.js` 中管理。
> [!IMPORTANT]
> **交互语言**:与用户的所有对话、确认、提问、说明必须使用**中文**。包括但不限于:任务描述、设计决策询问、进度汇报、问题反馈等。代码中的变量名、文件路径等技术标识符保留英文。
> [!CAUTION]
> **连续执行**:用户确认设计决策(阶段 1)后,阶段 2 ~ 5(生成蓝图、初始化骨架、逐页转换、验证)必须**一口气连续完成**,中间不得暂停等待用户确认。不要在完成几个页面后就停下来汇报进度或请求继续——**所有页面必须连续完成后再进入验证阶段**。只有在遇到**无法自主决策的问题**时才暂停询问用户。
---
## 一、转换流程(按顺序执行)
### 阶段 1:分析源文件 → 确认设计决策
1. **通读 Demo 源码**,提取以下信息:
- **页面数量与路由结构**(识别所有"视图/路由/Tab")
- 组件层级关系
- 样式体系(CSS 变量、设计系统、色板)
- 图标方案(SVG / icon font / 图片),列出所有使用的图标名称和颜色
- 交互逻辑(点击事件、Tab 切换、页面跳转、弹窗提醒等简单交互)
- 数据模型(用于 Mock 数据的结构)
> [!CAUTION]
> **页面提取是最关键的步骤,遗漏页面会导致最终产物缺页。** 必须通过以下方式**交叉验证**,确保不遗漏任何页面:
>
> 1. **路由配置**:检查 Router 配置、hash 路由、Tab 定义等,提取所有注册的路由
> 2. **导航链接**:搜索源码中所有 `href`、`to`、`router.push`、`navigate` 等跳转目标
> 3. **JS 事件跳转**:搜索 `onClick`、`handleClick` 等事件处理函数中的页面跳转逻辑
> 4. **条件渲染的视图**:检查 `v-if`、`v-show`、`{condition && <Component>}` 等条件渲染,识别隐藏的子视图/页面
> 5. **HTML 页面结构**:如果是单 HTML 文件,搜索所有 `section`/`div` 中通过 CSS `display:none` 或 JS 切换显示的独立视图
>
> 分析完成后,**必须明确告知用户总页面数**(如"共发现 13 个页面:4 个 TabBar 页面 + 9 个子页面"),让用户确认是否有遗漏。
2. **在生成蓝图之前,必须先与用户确认以下设计决策**:
> [!CAUTION]
> 以下决策直接影响蓝图内容和后续实现方式,**必须在蓝图创建前完成确认**,避免蓝图与实际执行脱节。
- **页面完整性**:告知用户发现的页面总数和清单,确认是否有遗漏(使用固定模板)
- **TabBar 样式**:系统默认 or 自定义(浮动胶囊等特殊设计需自定义)
- **导航栏样式**:默认 or 自定义
- **图标方案**(向用户说明两种方案的优劣,让用户选择):
- **方案 A:Emoji 占位**(快速原型)— 无需额外资源,开发速度快,适合快速验证布局,后续可替换为真实图标
- **方案 B:SVG 转 PNG 图片** — 视觉效果精准,需要生成图标资源文件,适合对图标质量有要求的项目
页面完整性确认模板(必须原样输出结构):
```markdown
已识别页面总数:N(TabBar: X,子页面: Y)
TabBar 页面:
- pages/xxx/xxx
- pages/xxx/xxx
子页面:
- pages/xxx/xxx
- pages/xxx/xxx
疑似遗漏页面(若无则写“无”):
- ...
```
### 阶段 2:生成转换蓝图
根据阶段 1 的分析结果和用户确认的设计决策,**创建转换蓝图文件** `conversion-blueprint.md`,置于项目根目录。
> [!IMPORTANT]
> 蓝图是整个转换过程的**单一事实来源**。蓝图内容必须与用户确认的设计决策一致。
蓝图包含以下内容:
```markdown
# [项目名] 转换蓝图
## 一、页面清单
| 序号 | 页面名称 | 路径 | 类型 |
|------|---------|------|------|
| 1 | 首页 | pages/home/home | TabBar |
| 2 | 详情页 | pages/detail/detail | 子页面 |
| ... | ... | ... | ... |
## 二、路由结构
- TabBar 页面:[列表]
- 子页面:[列表]
- 页面间跳转关系:[描述]
## 三、组件层级
- 全局组件:[列表]
- 页面私有组件:[列表]
## 四、样式体系
- CSS 变量/设计 Token:[列出关键变量]
- 色板:[主色、辅色、背景色等]
- 字体:[字号体系]
## 五、图标方案:[Emoji / PNG]
<!-- 根据用户选择的方案填写不同内容 -->
### 如果选择 Emoji 方案:
| 原图标名称 | Emoji 字符 | 使用位置 |
|-----------|-----------|--------|
| chevron-left | ‹ | 所有子页面返回按钮 |
| home | 🏠 | TabBar-首页 |
| ... | ... | ... |
### 如果选择 PNG 方案:
| 图标名称 | 颜色 (Hex) | 文件名 | 使用位置 |
|---------|-----------|--------|--------|
| house | #94a3b8 | house.png | TabBar |
| ... | ... | ... | ... |
## 六、交互逻辑
| 交互类型 | 描述 | 所在页面 |
|---------|------|--------|
| Tab 切换 | 底部 TabBar 导航 | 全局 |
| 页面跳转 | 点击卡片进入详情 | 首页 |
| Toast 提醒 | 点击按钮弹出提醒 | ... |
| ... | ... | ... |
## 七、Mock 数据结构
- [列出每个页面需要的 Mock 数据字段和结构]
```
### 阶段 3:初始化项目骨架
> [!IMPORTANT]
> 小程序项目必须生成在一个**单独的 `miniprogram` 文件夹**中,与源 Demo 文件分离,避免混淆。
按以下顺序创建文件:
```
miniprogram/
├── app.js # 全局入口
├── app.json # 页面注册 + TabBar + window 配置
├── app.wxss # 全局样式(CSS 变量 + 工具类)
├── project.config.json
├── sitemap.json
├── custom-tab-bar/ # 如需自定义 TabBar
│ ├── index.js / index.json / index.wxml / index.wxss
├── assets/
│ └── icons/ # 图标资源
├── utils/
│ ├── mock.js # 所有 Mock 数据集中管理
│ └── util.js # 工具函数
└── pages/ # 每个页面 4 个文件
├── page-name/
│ ├── page-name.js
│ ├── page-name.json
│ ├── page-name.wxml
│ └── page-name.wxss
```
> [!IMPORTANT]
> `project.config.json` 必须配置 `"miniprogramRoot": "miniprogram/"`,确保微信开发者工具正确识别源码目录。
### 阶段 4:逐页转换(先 TabBar 页面,再子页面)
- 严格按照蓝图中的页面清单顺序逐页转换
- 所有页面数据从 `utils/mock.js` 引入
- 交互逻辑仅实现简单交互(跳转用 `wx.navigateTo` / `wx.switchTab`,提醒用 `wx.showToast` / `wx.showModal`)
- 业务逻辑部分用 `wx.showToast({ title: '功能开发中', icon: 'none' })` 占位
**单个页面的转换步骤:**
1. **`.json`(配置)**:设置页面标题、导航栏样式、引用的自定义组件等(组件声明是后续 WXML 中使用自定义组件的前提)
2. **`.wxml`(结构)**:对照 Demo 源码逐元素转换,按标签映射表替换标签
3. **`.wxss`(样式)**:迁移对应 CSS,按样式转换规则处理单位、选择器和布局
4. **`.js`(数据 + 交互)**:从 `mock.js` 引入数据,在 `onLoad` 中 `setData`,绑定简单交互事件
### 阶段 5:逐项验证
按照蓝图文件进行逐步验证(详见 **第七节 验证流程**)。
---
## 二、核心转换规则
### 2.1 标签映射
| HTML / React | 微信小程序 | 说明 |
|---|---|---|
| `<div>` | `<view>` | 通用容器 |
| `<span>` / `<p>` | `<text>` | 文本必须包在 text 中 |
| `<img>` | `<image>` | 必须设宽高;常用 mode:`aspectFill`(裁剪填充)、`aspectFit`(完整显示)、`widthFix`(宽度固定高度自适应)、`scaleToFill`(默认拉伸) |
| `<input>` | `<input>` | 保留,但事件名不同 |
| `<textarea>` | `<textarea>` | 原生组件,层级最高 |
| `<button>` | `<button>` / `<view>` | 视需求选择 |
| `<a href>` | `<navigator>` / 事件 | 小程序无 a 标签 |
| `<ul>` / `<li>` | `<view>` + `wx:for` | 列表渲染 |
| `<svg>` | ❌ 不支持 | 用 image 替代(见图标方案)|
| `<select>` | `<picker>` | 选择器组件 |
| `<form>` | `<form>` | 保留,事件名变化 |
| `<scroll-view>` | `<scroll-view>` | 必须设固定高度才能滚动 |
| 轮播图(JS 库) | `<swiper>` + `<swiper-item>` | 内置轮播组件,支持自动播放和循环 |
| `<video>` | `<video>` | 原生组件,需用 cover-view 覆盖 |
| `<audio>` | `<audio>` / `wx.createInnerAudioContext` | 推荐用 API 方式 |
| `<input type="radio">` | `<radio-group>` + `<radio>` | 单选框 |
| `<input type="checkbox">` | `<checkbox-group>` + `<checkbox>` | 多选框 |
| toggle / switch | `<switch>` | 开关组件 |
| `<input type="range">` | `<slider>` | 滑块组件 |
| 富文本 HTML 内容 | `<rich-text nodes="{{html}}">` | 支持部分 HTML 标签渲染 |
| 覆盖原生组件的浮层 | `<cover-view>` / `<cover-image>` | 用于覆盖 video 等原生组件 |
### 2.2 事件映射
| Web 事件 | 小程序事件 | 说明 |
|---|---|---|
| `onClick` | `bindtap` | 点击事件(冒泡) |
| `onClick`(阻止冒泡) | `catchtap` | 点击事件(阻止冒泡) |
| 长按 | `bindlongpress` | 超过 350ms 触发,推荐代替 longtap |
| `onTouchStart` | `bindtouchstart` | 手指触摸开始 |
| `onTouchMove` | `bindtouchmove` | 手指触摸后移动 |
| `onTouchEnd` | `bindtouchend` | 手指触摸结束 |
| `onChange`(input) | `bindinput` | 输入框内容变化 |
| `onChange`(picker/switch) | `bindchange` | picker、switch、slider 等值变化 |
| `onFocus` | `bindfocus` | 输入框获取焦点 |
| `onBlur` | `bindblur` | 输入框失去焦点 |
| `onSubmit` | `bindsubmit` | 表单提交 |
| `onScroll` | `bindscroll` | 滚动事件(scroll-view) |
| `onLoad`(img) | `bindload` | 图片/视频加载成功 |
| `onError`(img) | `binderror` | 图片/视频加载失败 |
**属性映射**(非事件,但转换时同样重要):
| Web 属性 | 小程序属性 | 说明 |
|---|---|---|
| `className` | `class` | 类名属性 |
| `style={{}}` | `style=""` | 内联样式(字符串格式) |
| `dangerouslySetInnerHTML` | `<rich-text nodes>` | 富文本渲染 |
| `hidden` / `v-show` | `hidden="{{bool}}"` | 控制显隐(不销毁节点,比 `wx:if` 性能更好适合频繁切换) |
| `data-*` | `data-*` | 自定义数据属性,通过 `e.currentTarget.dataset` 获取 |
> [!TIP]
> **事件冒泡机制**:`bind` 前缀允许事件冒泡,`catch` 前缀阻止冒泡。需要阻止父元素响应事件时用 `catch`(如弹窗遮罩的点击穿透问题)。
### 2.3 样式转换(CSS → WXSS)
**核心规则:**
1. **单位转换**:`px` → `rpx`(1px = 2rpx),除以下情况保留 `px`:
- `border`:细边框保留 `1px`(避免在高分屏上过粗),粗边框正常按 1px=2rpx 换算
- 与系统 API 返回值配合的尺寸(如 statusBarHeight)
- `font-size` 可酌情使用 `rpx` 或 `px`
2. **选择器支持情况**:
- ✅ 类选择器(`.class {}`)
- ✅ ID 选择器(`#id {}`)
- ✅ 后代选择器、子选择器(`>`)、兄弟选择器(`~`、`+`)
- ✅ 伪类:`:active`、`:first-child`、`:last-child`、`:not`、`:nth-child`
- ✅ 伪元素:`::before`、`::after`(仅这两个)
- ❌ 标签选择器(`div {}`, `span {}`)
- ❌ `*` 通配符选择器
- ❌ 属性选择器(`[attr]`、`[type="text"]`)
3. **不支持或受限的布局属性**:
- ⚠️ `float`:支持但在 Flex 容器内失效,**推荐用 Flex 布局替代**
- ⚠️ `display: inline-block`:行为可能与 Web 不完全一致,**推荐用 Flex 布局替代**
- ⚠️ `position: fixed`:支持,但父元素有 `transform` 时会失效;仅支持相对视口定位
- ⚠️ `overflow: scroll`:支持不稳定且受渲染引擎影响,**推荐使用 `<scroll-view>` 组件**实现可靠滚动
- ❌ WXSS 中不支持引入**本地字体文件和本地图片**,必须使用在线资源或 Base64 编码
4. **支持的现代 CSS**:
- ✅ `display: flex` 全系列(**推荐首选布局方式**)
- ✅ `display: grid` / `grid-template-columns`
- ✅ `backdrop-filter: blur()`
- ✅ `linear-gradient()`
- ✅ `box-shadow`
- ✅ CSS 变量 `var(--xxx)`(在 `page {}` 中定义,非 `:root`)
- ✅ `border-radius`
- ✅ `position: sticky`
- ✅ `@import` 导入外部样式表
5. **Tailwind CSS 迁移**:将工具类转为等效 WXSS:
- 提取颜色为 CSS 变量定义在 `app.wxss` 的 `page {}` 选择器中
- 将 `flex`, `grid`, `gap`, `rounded` 等转为对应属性
- `hover:` 伪类可用 `.active` 类 + `bindtouchstart/end` 模拟,或省略
6. **全局样式策略**:
- CSS 变量定义在 `app.wxss` 的 `page {}` 中(**不是 `:root`**)
- 通用工具类(flex 布局、文本截断等)定义在 `app.wxss`
- 页面私有样式写在各自的 `.wxss` 文件中
- 自定义组件默认启用**样式隔离**,组件内外样式互不影响
### 2.4 路由与导航
| Web 路由方式 | 小程序对应 |
|--------------------------|------------------------------|
View on GitHub