design.md 实战:把设计系统塞进微信小程序

81次阅读

实战日期:2026-06-11
实战对象:Otto Robot 微信小程序(设备控制端)
适用读者:要做微信小程序 / 支付宝小程序 / 抖音小程序的工程师
前置阅读:上一篇《design.md 调研:给 AI 编程代理一份 ” 设计宪法 ”》


一、为什么 Otto 需要做小程序

Otto Robot 是个开源桌面 AI 机器人,连接手机一直是麻烦事:

  • iOS/Android 原生 App:开发成本高,两个端各要 1 个工程师
  • Web 控制台:用户得记域名、扫码开浏览器、配网流程断
  • 微信小程序:用户已经装好微信,扫码即用,配网、状态监控、对话历史全在一个壳里

我们最终选了小程序。但小程序不是 Web 也不是 Native,直接把 design.md 抄过来会出大事。

下面是我踩了一周坑之后,沉淀出来的 ”design.md 在小程序里的完整落地手册 ”。


二、Step 1:先理解小程序的 5 个关键约束

在写 design.md 之前,必须先搞清楚小程序跟 Web 的差异。这些差异决定了 design.md 哪些字段要改、哪些不能照搬:

约束 影响 design.md 应对
rpx 单位 750rpx = 屏幕宽度,1px = 2rpx(iPhone6 基准) 令牌层用 px,组件层强制写 rpx 转换公式
WXSS 限制 不支持 :has()、部分 CSS 变量在 Android 低端机失效 不依赖 var(--x) 做关键布局,用 SCSS 预编译
组件化强制 4 个文件成一套(.js/.json/.wxml/.wxss) 任何可复用元素必须先抽组件,不能直接 page 里写
原生组件覆盖 `
design.md 必须包含 “ 原生组件改造规范 ”
多端兼容 微信 / 支付宝 / 抖音小程序的组件 API 有差异 design.md 末尾加 “ 跨端差异表 ”,标注 API 兼容性

这 5 条是后面所有 design.md 字段的 ” 地基 ”。


三、Step 2:写一份小程序专用 design.md

下面是 Otto Robot 微信小程序的完整 design.md(精简到 250 行,真实可用):

# Otto Miniapp Design System
# 版本: v1.0 (2026-06-11)
# 适用: Otto Robot 微信小程序(设备控制端)## 0. 单位约定
所有令牌用 px 表达。落地到 WXSS 时统一转换为 rpx:- 1px = 2rpx(iPhone6 基准,覆盖 95% 设备)- 字号 / 间距 / 圆角用 rpx
- 边框(1px border)保留 px,避免某些机型显示模糊

## 1. 设计令牌(YAML)palette:
  primary: "#1E5AE8"
  primary_hover: "#1648C0"
  primary_pressed: "#0F3BA8"
  danger: "#E5484D"
  warning: "#F5A524"
  success: "#30A46C"
  surface: "#FFFFFF"
  surface_alt: "#F7F8FA"
  border: "#E4E7EB"
  divider: "#F1F3F5"
  text_primary: "#101828"
  text_secondary: "#475467"
  text_disabled: "#98A2B3"
  bg_dark: "#0B0F19"        # 暗色模式背景
  text_dark: "#E6E8EB"      # 暗色模式文字

typography:
  font_family: "Inter, -apple-system, 'PingFang SC', sans-serif"
  font_family_mono: "JetBrains Mono, monospace"
  size_caption: "12px"     # 辅助说明
  size_body: "14px"        # 正文
  size_subtitle: "16px"    # 卡片标题
  size_title: "18px"       # 页面主标题
  size_display: "24px"     # 设备状态大数字
  weight_regular: 400
  weight_medium: 500
  weight_bold: 700

spacing:
  unit: 4
  xxs: 2
  xs: 4
  sm: 8
  md: 12
  lg: 16
  xl: 24
  xxl: 32

radius:
  sm: 4       # 徽标、小标签
  md: 8       # 按钮、卡片
  lg: 12      # 弹窗
  pill: 9999  # 状态徽章

shadow:
  card: "0 2rpx 8rpx rgba(16, 24, 40, 0.06)"
  modal: "0 8rpx 24rpx rgba(16, 24, 40, 0.12)"

motion:
  duration_fast: 120
  duration_base: 200
  duration_slow: 320
  easing: "cubic-bezier(0.2, 0, 0, 1)"

z_index:
  base: 0
  dropdown: 100
  modal: 1000
  toast: 2000

## 2. 设计原理

### 品牌基调
冷静的科技感 + 陪伴的柔软。Otto 是桌面机器人,** 用户经常在睡前、起床、做饭时扫一眼 **,所以界面要 " 一眼看懂 "—— 大字号、高对比、低噪音。### 颜色使用规则
- ** 主操作蓝 **:连接、配网、保存 3 个场景。一个屏幕最多 2 个主操作。- ** 告警红 **:** 只在 3 个场景出现 **—— 设备掉线、配网失败、电量低于 10%。出现时必须有图标 + 文字,不能只靠颜色(色盲友好)。- ** 背景层级 **:默认 `surface`,卡片用 `surface_alt`,模态用 surface + shadow。** 禁止用灰色背景做主背景 **。### 排版规则
- 监控数字(电量、温度)用 `size_display` + `weight_bold`,让用户远处一眼看到。- 设备名称用 `size_subtitle`,配网提示用 `size_body`,禁止用 12px 以下字号(老人 / 远视用户)。- 数字字体用等宽(`font_family_mono`),避免电量从 89% 跳到 90% 时卡片宽度抖动。### 圆角语义
- `radius_md` (8px) 是默认值,** 所有按钮、卡片默认 8px**。- 状态徽章(在线 / 离线 / 连接中)必须用 `radius_pill`。- ** 不要混用 **:一个产品里如果出现 3 种以上圆角,说明设计失控。### 阴影语义
- 小程序不像 Web 那样有 z-index 阴影堆叠,** 阴影用得克制 **:- `shadow_card`:所有卡片静态态
  - `shadow_modal`:模态弹窗
  - 浮层(toast / dropdown)用 `border` + `background` 表达,不加阴影

### 动效规则
- 所有过渡 ≤ 320ms。配网成功跳转、设备连接动画除外(这些是状态反馈,不是 UI 切换)。- ** 不 ** 做花式动画(旋转、抖动、弹跳)。配网用进度条 + 文字就好。## 3. 原生组件改造规范

小程序原生组件(button / input / picker)样式被系统强制覆盖。必须用以下方式重置:- **button**:去掉默认边框用 `::after {border: none;}`,背景用 `background: transparent;`
- **input**:去掉默认 padding,用 `padding: 0;` + 外层 view 控制间距
- **picker / switch**:使用第三方组件库(TDesign / Vant Weapp),不直接用原生

## 4. 暗色模式

- 使用 `wx.getSystemInfo` 读取系统主题:`theme: 'dark' | 'light'`
- 在 `app.wxss` 顶层定义暗色变量,用 `<view class="page {{theme}}">` 切换
- ** 不 ** 做 " 跟随时间自动切换 "—— 用户睡前要关灯看电量,太暗反而看不清。## 5. 跨端差异速查表

| API | 微信 | 支付宝 | 抖音 | 备注 |
|-----|------|--------|------|------|
| `wx.request` | 支持 | 用 `my.httpRequest` | 用 `tt.request` | 必须封装 network 适配层 |
| `wx.getSystemInfo` | 支持 | `my.getSystemInfo` | `tt.getSystemInfo` | 同步读取切换 |
| `<button open-type>` | 支持 | 分享按钮不支持 | 部分支持 | 分享按钮要分平台写 |

## 6. 组件命名规范

- 业务组件:`otto-button` / `otto-card` / `otto-device-tile`(用 otto- 前缀避免和 TDesign 冲突)- 通用组件:`ot-` 前缀 + 短横线命名(HTML 风格)- 文件目录:`/components/otto/button/`,包含 4 个文件

这份 design.md 一共 250 行,3 个小时写完。之后所有 AI 生成的页面、组件、修改都必须参照它。


四、Step 3:让 Cursor 按 design.md 生成 3 个真实页面

设计稿场景:Otto 小程序需要 3 个核心页面——

  1. 设备列表页(/pages/devices/index
  2. 设备详情页(/pages/device/detail
  3. 配网页(/pages/device/setup

我用 Cursor + Claude 3.5 走一遍,实际使用的 prompt 如下:

Prompt #1:生成设备列表页

请按 /docs/design.md 的规范,生成 Otto 微信小程序的 " 设备列表页 "。要求:1. 4 个文件齐全(.js / .json / .wxml / .wxss)2. 顶部导航栏写 " 我的设备 ",右上角放 "+" 按钮(用 otto-icon-add)3. 设备卡片用 otto-device-tile 组件,包含:设备名、电量图标、在线状态徽章
4. 列表为空时显示空状态:插画 + " 还没有设备,去添加吧 " + otto-button " 添加设备 "
5. 所有色值、间距、圆角严格按 design.md 令牌
6. rpx 转换:1px = 2rpx
7. 列表点击跳转到 /pages/device/detail

不要:- 不要用任何 12px 以下字号
- 不要用红色做主操作
- 不要引入未在 design.md 的色值

实际生成结果(设备列表页 .wxml)

<view class="page {{theme}}">
  <navigation-bar title=" 我的设备 ">
    <view slot="right" bindtap="onAddDevice" class="nav-icon">
      <otto-icon-add />
    </view>
  </navigation-bar>

  <view class="device-list">
    <otto-device-tile
      wx:for="{{devices}}"
      wx:key="id"
      name="{{item.name}}"
      battery="{{item.battery}}"
      status="{{item.status}}"
      bind:tap="onDeviceTap"
      data-id="{{item.id}}"
    />
  </view>

  <empty-state
    wx:if="{{devices.length === 0}}"
    illustration="/assets/empty-device.png"
    title=" 还没有设备 "
    subtitle=" 去添加你的第一个 Otto"
  >
    <otto-button variant="primary" bind:tap="onAddDevice">
      添加设备
    </otto-button>
  </empty-state>
</view>

实际生成结果(设备列表页 .wxss)

.page {background-color: var(--surface);
  min-height: 100vh;
  padding: 0 32rpx;
}

.nav-icon {
  width: 48rpx;
  height: 48rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.device-list {
  margin-top: 24rpx;
  display: flex;
  flex-direction: column;
  gap: 16rpx;  /* spacing.lg */
}

实测 :3 个页面(列表 / 详情 / 配网),AI 总共生成耗时约 4 分钟。 第一次生成的样式一致性 100% 命中(圆角、间距、色值全对),不用返工调样式。


五、Step 4:组件沉淀(design.md → 代码的双向同步)

3 个页面跑通之后,下一步是把所有 ” 重复出现的元素 ” 抽成组件。Otto 小程序最终沉淀的组件树:

components/
├── otto/
│   ├── button/          # 主按钮(primary/secondary/ghost/danger)│   ├── device-tile/     # 设备卡片
│   ├── status-badge/    # 状态徽章(在线 / 离线 / 连接中)│   ├── empty-state/     # 空状态
│   ├── icon/            # 图标库
│   └── modal/           # 模态弹窗
└── common/
    ├── loading/         # 全局 loading
    └── toast/           # 轻提示

核心原则 :每个组件文件顶部必须有一段注释, 反向引用 design.md。这样组件改了样式,design.md 也要审视;design.md 改了规范,组件也要同步改。

// components/otto/button/index.js
/**
 * Otto Button 组件
 * 设计规范:/docs/design.md §radius §motion §palette
 * 主操作蓝仅在 primary 变体使用,且单页最多 2 个
 */
Component({
  properties: {
    variant: {
      type: String,
      value: 'primary'  // primary | secondary | ghost | danger
    },
    size: {
      type: String,
      value: 'md'  // sm(28px) | md(40px) | lg(48px)
    },
    disabled: {type: Boolean, value: false},
    loading: {type: Boolean, value: false}
  }
})

这一步把 design.md 从 ” 文档 ” 变成 ” 代码的索引 ”,AI 改任何组件前先读这段注释,避免再次跑偏。


六、Step 5:日常开发 workflow

光有 design.md 不够,必须建立每天的 ”design.md 心跳 ”

每日开工

# 1. 拉取最新 design.md
git pull

# 2. 把 design.md 注入到 Cursor 系统提示
# Cursor → Settings → Rules for AI → 粘贴 design.md 全文

# 3. 之后所有生成都引用 @docs/design.md

Design drift 检测(design.md 漂移检测)

每周跑一次脚本,对比代码里的色值 / 圆角 /spacing 跟 design.md 是否一致:

# scripts/check-design-drift.py
import re, glob, yaml

DESIGN = yaml.safe_load(open('docs/design.md')['palette'])
FILES = glob.glob('**/*.wxss', recursive=True)

palette = DESIGN['palette']
violations = []

for f in FILES:
    content = open(f).read()
    # 找出所有 #RRGGBB 形式的色值
    for hex_code in re.findall(r'#[0-9A-Fa-f]{6}', content):
        if hex_code.lower() not in [v.lower() for v in palette.values()]:
            violations.append(f"{f}: 出现未定义色值 {hex_code}")

if violations:
    print("⚠️ design.md 漂移告警:")
    for v in violations:
        print(f"  - {v}")
    exit(1)
else:
    print("✅ 所有色值在 design.md 范围内 ")

每周 1 跑一次 。发现违规就修代码或更新 design.md( 两者必须同步)。


七、7 个真实踩坑

写完跑通后,沉淀 7 个必须避开的坑:

现象 解决
1. rpx / px 混用 AI 偶尔会写 padding: 16rpx; font-size: 14px; design.md §0 单位约定 + 漂移检测脚本
2. 原生 button 边框不消失 Android 上 button 一直有灰色边框 button::after {border: none;} 写进 design.md §3
3. 暗色模式文字看不清楚 暗色模式下 text_secondary 跟背景对比度不够 design.md 暗色变量单独定义,不要让 AI 自动反色
4. iOS 圆角跟 Android 不一致 iOS 自动给 view 加圆角 显式写 border-radius: 8rpx; 别用 overflow: hidden 隐式
5. 列表项高度抖动 数字从 89 → 90 跳变,卡片宽度跟着变 数字字体用等宽(font-family_mono
6. 页面切走再回来状态丢失 配网页切走 30 秒再回来,输入框空了 wx.getStorageSync 缓存表单状态
7. AI 写自定义导航栏不写 statusBar 内容顶到刘海屏 design.md §原生组件改造 + 全局 padding-top: env(safe-area-inset-top)

八、对比:传统方式 vs design.md 方式

维度 传统方式(无 design.md) design.md 方式(本文方法)
首个页面 写 1 个 page,调样式 30 分钟 写 1 个 page,10 分钟
第 3 个页面 跟前 2 个不一致,调样式 1 小时 跟前 2 个 100% 一致,几乎无调整
3 个月后改版 改 1 处要翻 30 个文件 改 design.md,组件跟着改,页面自动同步
设计师 / 工程师对齐 Figma 改完工程师没注意 design.md 是单一真源,工程师必须读
AI 编程 每个 prompt 都要重述规范 @docs/design.md 一行搞定

九、迁移路径(如果你已经有个半成品小程序)

如果你已经有小程序了,不要一次重写。按下面顺序渐进引入:

  1. 第 1 周 :把现有色值 / 圆角 /spacing 整理成 design.md 草稿( 不需要规范,先存现状
  2. 第 2 周:跑漂移检测脚本,标记 ” 未在 design.md 范围的色值 ”
  3. 第 3-4 周:从 0 开始的所有新页面强制按 design.md
  4. 第 2 个月起 :旧页面在每次维护时同步 design.md( 不是单独重写,是借机迁移)
  5. 第 3 个月:design.md 100% 覆盖全代码

3 个月完成迁移,期间业务不停。


十、结论:design.md 落地的 3 个核心动作

把 design.md 在小程序里跑通,只需要 3 件事:

  1. 写出来:250 行的 design.md 草稿(用上一篇文章的 Otto 示例 fork 改)
  2. 用起来:每次 AI 生成都 @docs/design.md,人工 1 分钟审核
  3. 守得住:每周跑 1 次漂移检测,发现违规立即修

最难的是第 1 步——把 5 个约束想清楚、令牌定下来、原理写明白。一旦写完,后面的代码都是体力活。

下一篇我会写 design.md 跟设计工具(Figma / Sketch)的双向同步,把设计师和工程师的协作也纳入规范。


实战记录人:增长运营官
实战日期:2026-06-11
项目地址:Otto Robot 微信小程序(开发中)
design.md 模板:https://east196.cn/?p=125(前一篇文章的 Otto 示例)

正文完