H5 的问题不是功能,是环境:适配 / 兼容 / 性能 / 安全区,全部提前写进 Spec,踩一次新坑,加一条规则,下次永不再踩。
核心思路
| Spec 文件 | 用途 | 维护频率 |
|---|---|---|
h5-env.spec.md |
技术选型 + 适配方案 + 兼容性红线 + 已知坑清单 | 踩到新坑就加一条 |
h5-design.spec.md |
视觉规范 + 安全区 + 动效标准 + Design Token | 设计稿更新时同步 |
h5-page.spec.md |
具体页面的业务逻辑和交互定义 | 每个新页面写一份 |
Step 1 — 写 h5-env.spec.md(技术选型 + 兼容性红线)
这是整个 H5 项目最核心的 Spec,一次性写好,所有页面共用:
# h5-env.spec.md(H5 环境规范,所有页面共用)
## 技术选型(已定,不可更改)
- 框架:Vue3 + Vite + TypeScript
- 路由:Vue Router 4(history 模式,需服务端 nginx 配合)
- 状态:Pinia(不用 Vuex)
- UI 库:Vant 4(不用 Element Plus,不适配移动端)
- 适配方案:postcss-px-to-viewport(设计稿 375px,不用 rem 方案)
- HTTP:axios + 拦截器统一处理 token 过期和错误码
## 兼容性红线(必须能跑的环境)
- iOS Safari 14+
- Android WebView(Chrome 85+)
- 微信内置浏览器(iOS + Android 均需测试)
- 不支持 IE,不兼容 PC 浏览器宽屏布局
## 已知坑清单(AI 生成代码时必须主动规避)
### 坑 1:flexbox gap 在 iOS 14 有 bug
❌ 错误写法:display: flex; gap: 12px;
✅ 正确写法:子元素用 margin-right: 12px(最后一个除外)
### 坑 2:position: fixed 在微信 iOS 键盘弹出时会位移
❌ 错误写法:底部操作栏用 position: fixed; bottom: 0
✅ 正确写法:JS 监听 window.resize 动态计算 bottom 值
或:整页用 flex 布局,底部区域不用 fixed
### 坑 3:100vh 在 iOS Safari 包含地址栏高度
❌ 错误写法:height: 100vh(页面底部会被地址栏遮住)
✅ 正确写法:height: 100dvh(现代浏览器)
或:JS 方案:document.documentElement.style.setProperty('--app-height', window.innerHeight + 'px')
### 坑 4:点击延迟 300ms(iOS < 13)
✅ 处理:已在 main.ts 全局引入 FastClick,无需单独处理
### 坑 5:图片上传 iOS 微信只能选相册
✅ 正确写法:<input type="file" accept="image/*">(不加 capture 属性)
### 坑 6:Date 字符串解析 iOS 不兼容
❌ 错误写法:new Date('2024-01-01 12:00:00')(iOS 返回 Invalid Date)
✅ 正确写法:dayjs('2024-01-01 12:00:00')(统一使用 dayjs)
### 坑 7:1px 边框在高清屏变粗
✅ 正确写法:使用 Vant 内置 hairline 类,或 transform: scaleY(0.5)
## 性能红线
- 首屏 FCP < 2s(4G 网络)
- 单页 JS bundle < 300KB(gzip 后)
- 图片:CDN + WebP 格式 + vant LazyLoad 懒加载
- 禁止在页面组件里直接 import 大型库(echarts 等必须按需引入)
- 路由懒加载:所有页面组件用 () => import() 异步引入
## 微信 JSSDK 使用规范
- 只在需要的页面按需初始化,不全局初始化
- 签名接口统一走后端:POST /api/wx/jsconfig?url=encodeURIComponent(location.href)
- 前端不存 appid / secret
- 分享配置在 router.afterEach 中按需设置
- 微信授权:统一走后端 /api/wx/oauth 跳转,不在前端处理 code
Step 2 — 写 h5-design.spec.md(视觉适配规范)
# h5-design.spec.md(视觉适配规范)
## 设计基准
- 设计稿尺寸:375px 宽(iPhone 13 基准)
- 适配方案:postcss-px-to-viewport 自动转换
- 直接写设计稿标注的 px 值,不需要手动换算
- 示例:设计稿标注 16px → 代码写 16px → 自动适配所有屏幕
## 安全区规范(刘海屏 / 全面屏必须处理)
需要处理安全区的元素:
- 顶部固定导航栏 → padding-top: env(safe-area-inset-top)
- 底部固定 TabBar / 操作按钮 → padding-bottom: env(safe-area-inset-bottom)
- 全屏弹窗(上下都要处理)
通用写法:
.fixed-bottom {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
## 色彩系统(Design Token)
- --color-primary: #FF6B35 主色,橙色品牌色
- --color-primary-light: #FFF0EB 主色浅底,选中/hover 背景
- --color-text-main: #1A1A1A 正文主色
- --color-text-sub: #666666 次要文字
- --color-text-hint: #AAAAAA 提示/占位文字
- --color-border: #EEEEEE 分割线/边框
- --color-bg-gray: #F5F5F5 灰底背景
- --color-success: #07C160 成功状态(微信绿)
- --color-warning: #FF9500 警告状态
- --color-danger: #EE0A24 危险/错误状态
## 间距系统(设计稿 px,直接写)
- 页面左右边距:16px
- 卡片内边距:12px(紧凑)/ 16px(标准)
- 列表项高度:56px(标准)/ 64px(含副标题)
- 主按钮高度:48px
- 小按钮高度:36px
## 字体规范
| 用途 | 大小 | 行高 | 字重 |
| ----------- | ---- | ---- | ---- |
| 页面大标题 | 20px | 1.3 | 600 |
| 卡片标题 | 16px | 1.4 | 500 |
| 正文 | 14px | 1.6 | 400 |
| 副标题/描述 | 13px | 1.5 | 400 |
| 小字/标签 | 12px | 1.5 | 400 |
禁止使用 rem / em 单位(适配统一由 vw 方案处理)
## 圆角规范
- 大卡片:12px
- 小卡片 / 标签:8px
- 主按钮:4px(方正风格)/ 100px(胶囊风格)
- 头像:50%(圆形)
## 动效规范
- 页面切换:slide-left(200ms ease-out)
- 弹窗出现:fade + slide-up(250ms ease-out)
- 加载态:vant Skeleton 骨架屏(不用 spinner)
- 下拉刷新:vant PullRefresh 组件
## z-index 层级规范
| 场景 | z-index |
| ------------------- | ------- |
| 页面内容 | 1 |
| 悬浮操作按钮 | 10 |
| 顶部导航栏(fixed) | 20 |
| 底部操作栏(fixed) | 20 |
| 弹窗遮罩 | 100 |
| 弹窗内容 | 101 |
| Toast / 提示 | 200 |
## Vant 主题定制(已在 main.ts 配置,直接用)
- 主色已覆盖为品牌橙:--van-primary-color: #FF6B35
- 圆角已统一:--van-border-radius-md: 8px
- 不要在组件上单独覆盖主题变量
Step 3 — 写 h5-page.spec.md(以"商品详情页"为例)
只写业务差异,环境和视觉引用公共 Spec:
# product-detail.spec.md(商品详情页)
## 引用
遵循 @h5-env.spec.md 和 @h5-design.spec.md 的所有规范
## 页面路由
/product/:id(id 为商品ID,URL 参数,必传)
## 页面结构(从上到下)
### ① 顶部导航栏(fixed,必须处理顶部安全区)
- 左:返回按钮(icon-arrow-left,点击 router.back())
- 中:标题"商品详情"
- 右:分享按钮(icon-share-o,触发微信分享)
### ② 商品图片轮播(van-swipe)
- 高度:280px(固定)
- autoplay: false(不自动播放)
- 图片加载失败:显示占位图 /assets/images/img-error.png
### ③ 商品基本信息区
- 价格:主色 #FF6B35,20px,font-weight: 600
- 原价:灰色删除线,13px(无原价时不展示此行)
- 商品名称:16px 加粗,最多 2 行,超出省略号
- 销量 + 评价数:12px 灰色,如"已售 1234 件 · 218 条评价"
### ④ 规格选择(点击展开 van-action-sheet)
- 展示已选规格摘要(如"已选:黑色 / XL / 1件")
- ActionSheet 内容:颜色选择 + 尺寸选择 + 数量(van-stepper)
- 底部:加入购物车 + 立即购买 两个按钮
### ⑤ 商品详情(富文本)
- v-html 渲染后端返回的 HTML
- 图片全部加 lazy 属性(van-lazyload 已全局注册)
### ⑥ 底部操作栏(fixed,必须处理底部安全区)
- 左:收藏图标 + 客服图标
- 右:加入购物车(灰底)+ 立即购买(主色)
## 接口
| 接口 | 方法 | 路径 | 说明 |
| ---------- | ---- | -------------------- | ------------------------------------ |
| 商品详情 | GET | /api/product/:id | 页面初始化 |
| SKU 库存 | GET | /api/product/:id/sku | 选择规格后调用 |
| 加入购物车 | POST | /api/cart/add | body: { productId, skuId, quantity } |
| 收藏切换 | POST | /api/favorite/toggle | body: { productId } |
## 关键交互逻辑
1. 页面加载:先展示 van-skeleton,接口返回后替换(不用 spinner)
2. 库存为 0:购买和加购按钮均显示"已售罄"且 disabled
3. 未选规格:点击购买/加购 → Toast "请先选择规格" → 自动展开规格 ActionSheet
4. 加购成功:底部购物车图标数字 +1 动画,Toast "已加入购物车"
5. 收藏:icon 切换实心/空心,乐观更新(先改 UI,失败再回滚)
6. 商品不存在(404):展示"商品已下架"空状态页,显示"返回首页"按钮
## 微信分享配置
- title:product.name(商品名称)
- desc:product.subTitle(商品副标题)
- imgUrl:product.images[0](主图第一张)
- link:window.location.href(当前页 URL)
Step 4 — 生成 Prompt(附截图效果更好)
请基于以下 Spec,生成 H5 商品详情页完整代码:
【环境规范】@h5-env.spec.md
【视觉规范】@h5-design.spec.md
【页面定义】@product-detail.spec.md
【参考代码】@src/views/order/detail.vue(风格最接近的已有页面)
【视觉参考】[附上设计稿截图]
生成文件:
1. src/views/product/detail.vue ← 主页面(含规格 ActionSheet)
2. src/api/product.ts ← 接口封装(追加,不覆盖已有函数)
3. src/types/product.d.ts ← 类型定义
特别注意:
- h5-env.spec.md 中的 7 条已知坑必须全部主动规避
- 顶部和底部安全区必须处理(padding + env(safe-area-inset-*))
- 所有图片加 van-lazyload 懒加载指令
- Date 字符串处理统一使用 dayjs,不用 new Date()
- 生成完成后列出每个文件的核心改动点
Step 5 — 必须真机测试的三个环境
✅ iOS Safari(iPhone 13 或以上)
重点检查:安全区 / 100vh / 键盘弹出时 fixed 元素位移
✅ 微信内置浏览器(iPhone)
重点检查:分享功能 / JSSDK 初始化 / 图片选择
✅ 微信内置浏览器(Android)
重点检查:flexbox gap / 字体渲染 / 动效流畅度
这三个环境的 Bug 不会在 Chrome DevTools 模拟器里暴露,真机测试不可省略。
持续维护:每踩一个新坑就更新 Spec
每次项目中发现新的兼容性问题,立刻在 h5-env.spec.md 的"已知坑清单"追加:
### 坑 8:微信浏览器长按图片出现保存菜单影响交互
✅ 处理:轮播图片加 pointer-events: none,触摸事件绑在容器上
### 坑 9:Android 部分机型 border-radius 不生效于 overflow: hidden 的子元素
✅ 处理:父元素加 transform: translateZ(0) 触发 GPU 合成层
### 坑 10:iOS 16 以下 :focus-visible 不支持
✅ 处理:表单高亮改用 :focus,不用 :focus-visible
这份 Spec 随时间增长,是团队最有价值的技术资产之一。
收益(面试话术)
"我们 H5 项目以前每个页面平均要花半天处理兼容性问题,同样的坑在不同页面重复踩。改成 SDD 后,把所有踩过的坑写进 h5-env.spec.md,AI 生成代码时自动规避,新页面的兼容性 Bug 下降了约 80%,测试阶段发现的环境问题从平均 58 个降到 12 个。"
Spec 目录结构建议
docs/specs/
h5-env.spec.md ← 长期维护,踩坑即更新
h5-design.spec.md ← 设计稿迭代时同步
pages/
product-detail.spec.md
order-list.spec.md
user-profile.spec.md
checkout.spec.md
...