Files
Neo-ZQYY/apps/miniprogram - 副本/doc/h5-to-miniprogram-pitfalls.md

19 KiB
Raw Blame History

H5 → 微信小程序转换避坑指南

基于本项目 docs/h5_ui/ 原型与 apps/miniprogram/miniprogram/pages/ 已转换页面的实际对比,结合微信小程序官方文档整理。 适用于后续页面开发(如 board-coach、task-list、customer-detail 等)的快速参考。


一、WXML vs HTML — 标签与结构

1.1 标签映射表

H5 (HTML) 小程序 (WXML) 说明
<div> <view> 最基础的容器
<span> / <p> <text> 文本必须用 <text> 包裹才可选中/换行
<a href="..."> <navigator url=""> 或用 wx.navigateTo() 编程式跳转
<img src="..."> <image src="" mode=""> 必须指定 mode,默认 320×240
<input> <input><t-input> 原生 input 事件名不同;推荐 TDesign
<textarea> <textarea><t-textarea> 同上
<button> <button><t-button> 小程序 button 有 open-type 能力
<ul>/<li> <view wx:for="{{list}}"> 没有列表语义标签
<select> <picker><t-picker> 完全不同的交互模式
<label for="id"> <label for="id"> 支持,但 for 只能绑定 checkbox/radio/switch
<svg> 内联 <image src="xx.svg"> 不支持内联 SVG只能作为图片引用
<iframe> <web-view> 需配置业务域名白名单

1.2 实际对比login 页面

H5 原型 — 内联 SVG 图标:

<svg class="w-14 h-14 text-white" viewBox="0 0 24 24" fill="currentColor">
  <circle cx="12" cy="12" r="10" .../>
</svg>

小程序转换 — 改为 image 引用:

<image class="logo-icon" src="/assets/icons/logo-billiard.svg" mode="aspectFit" />

:小程序不支持内联 SVG。所有 SVG 图标需提取为独立 .svg 文件放到 assets/icons/,通过 <image> 引用。

1.3 不存在的标签/属性

H5 特性 小程序替代方案
<h1>~<h6> <text> + 样式类
<table> <view> 手动布局
<form> + <input name> 小程序 <form> 或直接 setData 收集
onclick="fn()" bindtap="fn"bind:tap="fn"
class="a b c" 动态 class="base {{condition ? 'a' : 'b'}}"
innerHTML <rich-text nodes="{{html}}">

二、WXSS vs CSS — 样式差异

2.1 支持的选择器(有限)

选择器 支持 说明
.class
#id
element (如 view)
element, element 群组选择器
::before / ::after
* 通配符 Tailwind 的 * reset 全部失效
> 子选择器 ⚠️ 部分版本支持,不推荐依赖
+ / ~ 兄弟选择器 ⚠️ 同上
:nth-child() ⚠️ 部分支持
@media 支持,但用 rpx 更好

2.2 rpx 单位 — 最大差异

H5 用 px/rem/vw,小程序用 rpxresponsive pixel

  • 屏幕宽度固定 = 750rpx
  • iPhone6 上 1rpx = 0.5px,即 1px = 2rpx
  • 设计稿以 750px 宽为基准时,数值直接写 rpx

实际对比login 页面

H5 原型Tailwind

<div class="w-24 h-24 rounded-3xl">  <!-- 96px × 96px -->

小程序转换:

.logo-box {
  width: 192rpx;   /* 96px × 2 = 192rpx */
  height: 192rpx;
  border-radius: 48rpx;
}

换算规则H5 的 px 值 × 2 = rpx 值(基于 750 宽设计稿)。

2.2.1 H5 → 小程序全局缩放比例87.5%

H5 原型基于 375px 视口设计iPhone SE/6/7/8直接 ×2 转 rpx 后在大屏手机iPhone 15 Pro Max430pt 宽)上元素偏大。经实测对比,对所有 rpx 值统一乘以 0.875 缩放系数后视觉效果最佳。

规则

  • 尺寸、间距、圆角、阴影偏移:H5 px × 2 × 0.875 = 最终 rpx取偶数
  • 字号:同上规则,如 text-2xl(24px) → 48rpx × 0.875 = 42rpx
  • t-icon size同上规则w-5(20px) → 40rpx × 0.875 = 35rpx
  • max-width 等约束宽度:同上规则
  • 背景纹理间距(如十字纹 bg-pattern):不缩放,保持原值

换算速查(常用 Tailwind 值):

Tailwind H5 px 原始 rpx ×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
w-10 / h-10 (40px) 40 80 70 70
w-14 / h-14 (56px) 56 112 98 98
w-24 / h-24 (96px) 96 192 168 168
w-28 / h-28 (112px) 112 224 196 196
rounded-xl (12px) 12 24 21 22
rounded-2xl (16px) 16 32 28 28
rounded-3xl (24px) 24 48 42 42

来源no-permission 页面实测确定。先后尝试了非统一缩放、80%、87.5% 三种方案87.5% 在 iPhone 15 Pro Max 上与 H5 原型视觉一致度最高。后续所有页面转换统一使用此系数。

2.3 Tailwind CSS → 手写 WXSS

小程序不支持 Tailwind CSS无构建链集成所有 Tailwind 工具类必须手写为 WXSS。

Tailwind 类 WXSS 等价写法
flex flex-col items-center display: flex; flex-direction: column; align-items: center;
gap-4 gap: 32rpx;4 × 8px × 2rpx
p-5 padding: 40rpx;
rounded-2xl border-radius: 32rpx;
text-sm font-size: 28rpx;
text-gray-7 color: #8b8b8b;
bg-white/60 background: rgba(255,255,255,0.6);
backdrop-blur-sm 不支持 backdrop-filter
shadow-lg box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.06);
min-h-screen min-height: 100vh;

2.4 不支持/有差异的 CSS 特性

CSS 特性 小程序支持情况 替代方案
backdrop-filter: blur() 不支持 用半透明背景色模拟
position: fixed ⚠️ 部分场景异常 position: sticky 或组件自带吸顶
@import url() 远程 只支持本地 @import "path.wxss"
@font-face 远程字体 ⚠️ wx.loadFontFace() 动态加载
CSS 变量 var() 支持 TDesign 大量使用
linear-gradient 支持 正常使用
animation / @keyframes 支持 正常使用
transition 支持 正常使用
env(safe-area-inset-*) 支持 刘海屏适配必用

2.5 样式作用域

  • app.wxss = 全局样式
  • 页面 .wxss = 仅当前页面生效(自动隔离)
  • 组件 .wxss = 默认隔离(styleIsolation: 'isolated'

H5 的全局 CSS reset* { box-sizing: border-box; })在小程序中无效。需要在每个需要的元素上手动设置 box-sizing


三、事件系统 — 最容易踩坑

3.1 事件绑定对比

H5 小程序 说明
onclick="fn()" bindtap="fn" 不能传参!
onclick="fn(1)" data-id="1" bindtap="fn" 通过 dataset 传参
addEventListener 不支持 只能在 WXML 中声明式绑定
event.target.value e.detail.value 取值路径不同
event.preventDefault() catchtap 用 catch 前缀阻止冒泡
event.stopPropagation() catchtap 同上

3.2 传参方式

H5

<button onclick="handleClick(item.id, item.name)">点击</button>

小程序

<view data-id="{{item.id}}" data-name="{{item.name}}" bindtap="handleClick">点击</view>
handleClick(e: WechatMiniprogram.TouchEvent) {
  const { id, name } = e.currentTarget.dataset
}

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

3.3 实际对比login 页面的协议勾选

H5 原型

<input type="checkbox" id="agreeCheckbox" onchange="updateButtonState()">
<script>
  checkbox.addEventListener('change', updateButtonState);
</script>

小程序转换

<view class="agreement" bindtap="onAgreeChange">
  <view class="checkbox {{agreed ? 'checkbox--checked' : ''}}">
    <t-icon wx:if="{{agreed}}" name="check" size="20rpx" color="#fff" />
  </view>
</view>
onAgreeChange() {
  this.setData({ agreed: !this.data.agreed })
}

:小程序没有原生 checkbox 的 checked 双向绑定,需要手动用 setData + 条件样式类模拟。


四、数据绑定与渲染

4.1 模板语法对比

功能 H5 (原生/框架) 小程序 WXML
插值 ${variable} / {{variable}} {{variable}}
条件渲染 if/else + DOM 操作 wx:if / wx:elif / wx:else
列表渲染 forEach + innerHTML wx:for="{{list}}" wx:key="id"
显示/隐藏 style.display = 'none' hidden="{{!show}}"wx:if
动态 class classList.toggle() class="base {{active ? 'on' : ''}}"
动态 style element.style.color = 'red' style="color: {{color}};"

4.2 wx:if vs hidden

<!-- wx:if条件为 false 时不渲染 DOM切换时销毁/重建 -->
<view wx:if="{{status === 'pending'}}">审核中</view>

<!-- hidden始终渲染只切换 display -->
<view hidden="{{status !== 'pending'}}">审核中</view>
  • 频繁切换 → 用 hidden(避免重复创建销毁)
  • 初始条件不太可能变 → 用 wx:if(减少初始渲染量)

4.3 实际对比reviewing 页面的条件渲染

H5 原型:只有一种状态(审核中),用静态 HTML。

小程序转换:支持 pending/rejected 两种状态,用 wx:if 动态切换:

<view class="top-gradient top-gradient--{{status}}"></view>
<t-icon wx:if="{{status === 'pending'}}" name="time" size="112rpx" color="#fff" />
<t-icon wx:else name="close-circle" size="112rpx" color="#fff" />
<text class="main-title">{{status === 'pending' ? '申请审核中' : '申请未通过'}}</text>

wx:if 中的表达式必须在 {{}} 内,且不支持复杂 JS 表达式(如函数调用)。需要复杂逻辑时用 WXS 或在 JS 中预处理好数据。


五、路由与导航

5.1 对比

H5 小程序 说明
window.location.href = 'xx.html' wx.navigateTo({ url: '/pages/xx/xx' }) 保留当前页,跳新页
window.location.replace() wx.redirectTo() 关闭当前页,跳新页
history.back() wx.navigateBack() 返回上一页
无直接等价 wx.reLaunch() 关闭所有页面,打开新页
无直接等价 wx.switchTab() 跳转 tabBar 页面

5.2 实际对比reviewing 页面的"更换账号"

H5 原型

function switchAccount() {
  localStorage.clear();
  window.location.href = 'login.html';
}

小程序转换

onSwitchAccount() {
  const app = getApp<IAppOption>()
  app.globalData.token = undefined
  wx.removeStorageSync("token")
  wx.removeStorageSync("refreshToken")
  wx.reLaunch({ url: "/pages/login/login" })  // 清空页面栈
}

  • 页面栈最多 10 层,navigateTo 超过会静默失败
  • 跳转 tabBar 页面必须用 switchTab,用 navigateTo 会报错
  • 路径必须以 / 开头,且不带 .wxml 后缀
  • reLaunch 会销毁所有页面,适合登录/登出等场景

六、存储与网络

6.1 本地存储

H5 小程序 说明
localStorage.setItem(k, v) wx.setStorageSync(k, v) 同步写入
localStorage.getItem(k) wx.getStorageSync(k) 同步读取
localStorage.removeItem(k) wx.removeStorageSync(k) 同步删除
localStorage.clear() wx.clearStorageSync() 清空全部
上限 ~5MB 上限 10MB 小程序更大

:小程序没有 Cookie登录态必须自行通过 Storage + header token 管理。

6.2 网络请求

H5 小程序 说明
fetch() / XMLHttpRequest wx.request() 需配置域名白名单
无限制 并发上限 10 个 超出排队
任意域名 必须 HTTPS + 白名单 开发时可关闭校验

七、TDesign 组件替代 H5 原生元素

本项目使用 TDesign 小程序组件库,以下是常见替代关系:

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

TDesign 样式覆盖 4 种方式

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

实际示例login 页面按钮定制):

.login-btn {
  --td-button-large-height: 96rpx !important;
  --td-button-large-font-size: 32rpx !important;
  --td-button-border-radius: 24rpx !important;
}
.login-btn--active {
  background: linear-gradient(135deg, #0052d9, #3b82f6) !important;
  box-shadow: 0 12rpx 32rpx rgba(0, 82, 217, 0.3);
}

八、高频踩坑清单

8.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>

8.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 变量覆盖

8.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 替代

8.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

九、转换 Checklist新页面开发用

开发新页面时,按此清单逐项检查:

  • HTML 标签全部替换为 WXML 组件(div→viewspan→textimg→image
  • 内联 SVG 提取为文件,改用 <image><t-icon>
  • Tailwind 类全部手写为 WXSSpx × 2 × 0.875 = rpx见 §2.2.1 缩放规则)
  • backdrop-filter 等不支持的 CSS 改为替代方案
  • 事件绑定改为 bindtap / bind:change,传参用 data-*
  • alert/confirm 改为 wx.showToast / wx.showModal
  • localStorage 改为 wx.setStorageSync
  • 路由跳转改为 wx.navigateTo / wx.reLaunch
  • 表单收集改为 setData + 事件回调
  • 图片设置 mode 属性(aspectFit / aspectFill / widthFix
  • 列表渲染加 wx:key
  • 布尔属性用 {{}} 包裹(checked="{{true}}"
  • TDesign 组件在页面 .json 中注册 usingComponents
  • 安全区适配:padding-top: env(safe-area-inset-top)
  • 页面配置:enablePullDownRefreshnavigationBarTitleText

十、board-customer 迁移经验补充

来源board-customer 页面 8 维度卡片迁移实战2026-03-07

10.1 复杂维度用独立布局

最专一维度的助教明细表不适合用通用的 card-mid-rowcard-grid,直接用独立的 loyal-table 布局(左侧竖线 border-left: 4rpx solid #eee + 表头 + 数据行)。同时在助教行的 wx:if 中排除 dimType !== 'loyal',避免信息重复。

关键:当某个维度的卡片结构与其他维度差异过大时,不要硬套通用模板,直接写独立布局更清晰。

10.2 heart-icon 组件TS observer 替代 WXS

小程序 WXS 不支持 emoji surrogate pair\uD83D\uDC96),渲染为乱码。解决方案:

  • 用 TS observers 监听 score 属性变化,计算对应 emoji 字符串
  • WXML 中用 {{heartEmoji}} 数据绑定渲染
  • 样式:font-size: 22rpx; line-height: 1; position: relative; top: -4rpx 和文字对齐

10.3 助教字体颜色三态 + badge 渐变

助教名字颜色规则(通过 CSS class 控制):

  • 跟 badge → .assistant--assigneecolor: #e34d59; font-weight: 700(红色加粗)
  • 弃 badge → .assistant--abandonedcolor: #a6a6a6(灰色)
  • 无 badge → .assistant--normalcolor: #242424(黑色)

Badge 样式(白字 + 渐变背景 + 阴影):

  • 跟:background: linear-gradient(135deg, #e34d59, #f26a76); box-shadow: 0 2rpx 6rpx rgba(227,77,89,0.28)
  • 弃:background: linear-gradient(135deg, #d4d4d4, #b6b6b6); box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.14)