html-to-miniprogram — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited html-to-miniprogram (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
将任意前端 Demo(HTML/React/Vue 单文件或多文件)转换为微信小程序原生开发项目,精准还原 UI 和简单交互。
[!IMPORTANT] 转换范围:仅转换前端页面 UI 和简单交互(页面跳转、Tab 切换、Toast 提醒、弹窗等),不实现业务逻辑(如网络请求、用户认证、数据持久化等)。所有页面用到的数据统一整合在 utils/mock.js 中管理。[!IMPORTANT] 交互语言:与用户的所有对话、确认、提问、说明必须使用中文。包括但不限于:任务描述、设计决策询问、进度汇报、问题反馈等。代码中的变量名、文件路径等技术标识符保留英文。
[!CAUTION] 连续执行:用户确认设计决策(阶段 1)后,阶段 2 ~ 5(生成蓝图、初始化骨架、逐页转换、验证)必须一口气连续完成,中间不得暂停等待用户确认。不要在完成几个页面后就停下来汇报进度或请求继续——所有页面必须连续完成后再进入验证阶段。只有在遇到无法自主决策的问题时才暂停询问用户。
[!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中通过 CSSdisplay:none或 JS 切换显示的独立视图
>
分析完成后,必须明确告知用户总页面数(如"共发现 13 个页面:4 个 TabBar 页面 + 9 个子页面"),让用户确认是否有遗漏。
[!CAUTION] 以下决策直接影响蓝图内容和后续实现方式,必须在蓝图创建前完成确认,避免蓝图与实际执行脱节。
页面完整性确认模板(必须原样输出结构):
已识别页面总数:N(TabBar: X,子页面: Y)
TabBar 页面:
- pages/xxx/xxx
- pages/xxx/xxx
子页面:
- pages/xxx/xxx
- pages/xxx/xxx
疑似遗漏页面(若无则写“无”):
- ...根据阶段 1 的分析结果和用户确认的设计决策,创建转换蓝图文件 conversion-blueprint.md,置于项目根目录。
[!IMPORTANT] 蓝图是整个转换过程的单一事实来源。蓝图内容必须与用户确认的设计决策一致。
蓝图包含以下内容:
# [项目名] 转换蓝图
## 一、页面清单
| 序号 | 页面名称 | 路径 | 类型 |
|------|---------|------|------|
| 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 数据字段和结构][!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/",确保微信开发者工具正确识别源码目录。
utils/mock.js 引入wx.navigateTo / wx.switchTab,提醒用 wx.showToast / wx.showModal)wx.showToast({ title: '功能开发中', icon: 'none' }) 占位单个页面的转换步骤:
mock.js 引入数据,在 onLoad 中 setData,绑定简单交互事件按照蓝图文件进行逐步验证(详见 第七节 验证流程)。
| 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 等原生组件 |
| 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(如弹窗遮罩的点击穿透问题)。
核心规则:
px → rpx(1px = 2rpx),除以下情况保留 px:border:细边框保留 1px(避免在高分屏上过粗),粗边框正常按 1px=2rpx 换算font-size 可酌情使用 rpx 或 px.class {})#id {})>)、兄弟选择器(~、+):active、:first-child、:last-child、:not、:nth-child::before、::after(仅这两个)div {}, span {})* 通配符选择器[attr]、[type="text"])float:支持但在 Flex 容器内失效,推荐用 Flex 布局替代display: inline-block:行为可能与 Web 不完全一致,推荐用 Flex 布局替代position: fixed:支持,但父元素有 transform 时会失效;仅支持相对视口定位overflow: scroll:支持不稳定且受渲染引擎影响,推荐使用 `<scroll-view>` 组件实现可靠滚动display: flex 全系列(推荐首选布局方式)display: grid / grid-template-columnsbackdrop-filter: blur()linear-gradient()box-shadowvar(--xxx)(在 page {} 中定义,非 :root)border-radiusposition: sticky@import 导入外部样式表app.wxss 的 page {} 选择器中flex, grid, gap, rounded 等转为对应属性hover: 伪类可用 .active 类 + bindtouchstart/end 模拟,或省略app.wxss 的 page {} 中(不是 `:root`)app.wxss.wxss 文件中| Web 路由方式 | 小程序对应 |
|---|---|
| React Router / hash 路由 | app.json 的 pages 注册 |
| Tab 切换 | wx.switchTab({ url }) |
| 页面跳转 | wx.navigateTo({ url }) |
| 页面重定向(替换当前页) | wx.redirectTo({ url }) |
| 返回上一页 | wx.navigateBack() |
| 参数传递(query string) | options 参数 / globalData |
路由类型判断:
switchTab,不能用 navigateTo[!WARNING] 页面栈限制:小程序页面栈最多 10 层,超过后navigateTo会失败。深层级跳转考虑用redirectTo(替换当前页,不增加栈)。
| Web 概念 | 小程序对应 |
|---|---|
useState / data() | Page({ data: {} }) |
setState / 赋值 | this.setData({ key: value }) |
useEffect / mounted | onLoad() / onShow() |
props | 组件的 properties |
context / provide | getApp().globalData |
fetch / axios | Mock 数据直接引入(不实现真实请求) |
localStorage | wx.setStorageSync() / getStorageSync() |
条件渲染 {cond && <X/>} | wx:if="{{cond}}" |
列表渲染 .map() | wx:for="{{list}}" wx:key="id" |
| 模板字符串 | {{}} 数据绑定 |
[!TIP] `wx:key` 用法:值为列表项的属性名字符串(不加item.前缀),如wx:key="id"。如果列表项本身是唯一字符串/数字,可用wx:key="*this"。不设wx:key会触发警告且影响渲染性能。
Mock 数据策略:
[!NOTE] 所有页面数据统一在utils/mock.js中定义和导出,页面 JS 通过const mock = require('../../utils/mock.js')引入,在onLoad中setData。不实现 `wx.request` 等网络请求。
微信小程序不支持 SVG 标签,需要替换方案。图标方案应在阶段 1 中与用户确认,蓝图内容根据用户选择适配。
#### 方案 A:Emoji 占位(快速原型)
使用 Emoji 字符代替图标,无需额外资源文件,适合快速验证布局。
实施规范:
app.wxss 中定义通用 Emoji 图标类: /* Emoji 图标通用样式 */
.emoji-icon {
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
line-height: 1;
}<text> 标签包裹 Emoji,同时添加 emoji-icon 基础类和具体图标类: <!-- 返回按钮(使用 Unicode 字符) -->
<text class="emoji-icon back-icon">‹</text>
<!-- 普通 Emoji 图标 -->
<text class="emoji-icon phone-icon">📞</text>
<!-- 右箭头 -->
<text class="emoji-icon arrow-icon">›</text>font-size 控制大小(不是 width/height): /* ✅ 正确:用 font-size 控制 Emoji 大小 */
.back-icon {
font-size: 56rpx;
color: var(--slate-800);
}
/* ❌ 错误:width/height 对文本无效 */
.back-icon {
width: 52rpx;
height: 52rpx;
}| 原图标用途 | 推荐 Emoji / 字符 | 说明 |
|---|---|---|
| 返回按钮 | ‹(U+2039) | Unicode 单左尖括号,比 < 更美观 |
| 右箭头 | ›(U+203A) | Unicode 单右尖括号 |
| 首页 | 🏠 | |
| 搜索 | 🔍 | |
| 用户/头像 | 👤 | |
| 设置 | ⚙️ | |
| 电话 | 📞 | |
| 编辑 | ✏️ | |
| 删除 | 🗑️ | |
| 添加 | ➕ | |
| 已认证/通过 | ✅ | |
| 禁止/下架 | 🚫 | |
| 文档 | 📄 | |
| 日历 | 📅 | |
| 位置 | 📍 | |
| 图表 | 📊 |
[!TIP] 蓝图中应包含完整的图标名称 → Emoji 字符映射表,确保全项目一致性。
#### 方案 B:SVG 转 PNG 图片
如果用户选择精准视觉还原,使用以下工具将 SVG 图标转换为 PNG:
工具 1:Shell 脚本方式(`generate_icons.sh`)
从 Lucide 等图标库下载 SVG,替换颜色后用 sips 转为 PNG:
#!/bin/bash
set -euo pipefail
# 定义图标数组,格式:"图标名:颜色:文件名"
ICONS=(
"house:#94a3b8:house.png"
"house:#ffffff:house-active.png"
# ... 按蓝图中的图标清单填写
)
mkdir -p miniprogram/assets/icons
for item in "${ICONS[@]}"; do
IFS=':' read -r name color filename <<< "$item"
if ! curl -s -L -f "https://unpkg.com/lucide-static@latest/icons/$name.svg" -o temp.svg; then
echo "下载失败: $name" >&2
continue
fi
sed "s/currentColor/$color/g" temp.svg > colored.svg
sips -s format png colored.svg --out "miniprogram/assets/icons/$filename" -z 64 64 > /dev/null
rm temp.svg colored.svg
done[!NOTE] 上述脚本依赖sips(macOS)。非 macOS 环境可改用magick colored.svg "miniprogram/assets/icons/$filename"生成 PNG。
工具 2:HTML 页面方式(`icon_generator.html`)
在浏览器中用 Lucide JS 库渲染 SVG 到 Canvas,导出 PNG 的 base64 数据:
const ICONS_TO_GENERATE = [
{ name: 'house', color: '#94a3b8', filename: 'house.png' },
// ... 按蓝图中的图标清单填写
];
// 通过 Canvas 绘制 SVG 并导出 base64 PNG[!TIP] 两种工具可按需在项目的 tools/ 目录下创建,根据蓝图中的图标清单填充具体的图标列表。当 Demo 的 TabBar 不是标准样式时(如浮动胶囊、异形底栏),需使用自定义 TabBar:
app.json 中设置 "tabBar": { "custom": true, ... }custom-tab-bar/ 组件(固定路径名)onShow 中更新选中态: onShow() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 0 }) // 当前页索引
}
}custom: true,app.json 的 tabBar.list 仍需完整配置(框架要求)当页面需要自定义顶部导航栏(渐变背景、大标题等):
"navigationStyle": "custom"app.js 的 onLaunch 中获取系统信息: const systemInfo = wx.getWindowInfo()
this.globalData.statusBarHeight = systemInfo.statusBarHeight
const menuButton = wx.getMenuButtonBoundingClientRect()
this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.heightstyle(不要把 {{}} 写进 .wxss): <view class="nav-wrap" style="padding-top: {{statusBarHeight}}px;">
...
</view> const app = getApp()
Page({
data: { statusBarHeight: 0 },
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 0 })
}
})所有页面数据集中在 utils/mock.js 中管理:
// utils/mock.js
// 首页数据
const homeData = {
banners: [ /* ... */ ],
categories: [ /* ... */ ],
hotItems: [ /* ... */ ],
};
// 其他页面数据...
const profileData = { /* ... */ };
module.exports = {
homeData,
profileData,
// ...
};页面中引用方式:
// pages/home/home.js
const mock = require('../../utils/mock.js')
Page({
data: {},
onLoad() {
this.setData(mock.homeData)
},
// 简单交互
onItemTap(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({ url: `/pages/detail/detail?id=${id}` })
},
onButtonTap() {
wx.showToast({ title: '功能开发中', icon: 'none' })
}
})item 和 index,可通过 wx:for-item / wx:for-index 重命名pages/home/home.jsthis.animate() 或 WXS 响应事件(wx.createAnimation() 已不推荐使用)bindinput + setDatasetData 数据量不宜过大,避免传入整个大对象;尽量只更新变化的字段wx:if 会销毁/重建节点,hidden 仅控制显隐不销毁。频繁切换时用 hidden 性能更好onLoad 仅在页面首次加载时执行一次,onShow 每次页面显示都执行(TabBar 页面切换回来时也会触发 onShow)完成所有页面转换后,结合蓝图文件 conversion-blueprint.md 进行逐项验证:
逐一核对蓝图中的页面清单,确认:
app.json 中页面注册是否完整核对蓝图中的路由结构,确认:
switchTab)navigateTo)navigateBack)核对蓝图中的样式体系,确认:
核对蓝图中的图标清单,根据所选方案进行验证:
Emoji 方案验证项:
app.wxss 中已定义 .emoji-icon 全局样式<image> 标签已替换为 <text class="emoji-icon ..."> 标签(非图标的图片如 banner、头像等仍使用 <image>)font-size(非 width/height)PNG 方案验证项:
assets/icons/核对蓝图中的交互逻辑,确认:
核对蓝图中的 Mock 数据结构,确认:
utils/mock.js 包含所有页面的数据[!TIP] 验证过程中每完成一项,仅在conversion-blueprint.md的验证清单中标记[x]。发现问题立即修复后再继续。
转换时按以下优先级推进:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.