样式规范
核心原则
BEM 定义语义,UnoCSS 补充微调,SCSS 只做脏活。
| 层级 | 职责 | 示例 |
|---|---|---|
| BEM | 组件语义锚点 | profile-card、profile-card__avatar |
| UnoCSS | 简单布局微调(≤3 个属性) | flex items-center gap-16rpx |
| SCSS | 伪类/动画/穿透等原子类做不了的事 | :deep()、@keyframes |
选型决策
| 场景 | 方案 | 示例 |
|---|---|---|
| 简单布局(1-2 个属性) | 原子类 | class="flex items-center" |
| 组件样式(3+ 属性) | BEM | class="profile-card" |
| 重复出现的组合 | Shortcuts 收敛 | class="flex-center" |
| 主题相关 | CSS 变量 + BEM | background: var(--color-primary) |
vue
<!-- ✅ 组件样式归 BEM,微调用原子类 -->
<view class="profile-card">
<image class="profile-card__avatar" :src="user.avatar" />
<text class="profile-card__name">{{ user.name }}</text>
</view>
<!-- ✅ 简单间距直接用原子类 -->
<view class="mt-16rpx p-4">
<wd-search v-model="keywords" placeholder="搜索" />
</view>
<!-- ❌ 同一元素混用过多原子类 + BEM -->
<view class="profile-card flex flex-col p-28rpx bg-white rounded-28rpx shadow-sm">
</view>BEM 命名
格式
block__element--modifier
│ │ │
│ │ └── 状态/变体(双连字符)
│ └── 组成部分(双下划线)
└── 独立功能实体Block 必须带页面前缀
text
首页(index) → home-hero / home-nav / home-stat
我的(mine) → mine-hero / mine-profile / mine-menu
登录(login) → login-form / login-brand / login-navElement 通用词汇
| 语义 | 标准词汇 | 禁止 |
|---|---|---|
| 头部 | __header | |
| 底部 | __footer | |
| 标题 | __title | |
| 副标题 | __subtitle | |
| 描述 | __desc | |
| 图标 | __icon | |
| 图片 | __image | |
| 头像 | __avatar | |
| 标签 | __tag | |
| 徽章 | __badge | |
| 操作按钮 | __action | |
| 操作区 | __actions | |
| 内容 | __body | |
| 输入框 | __input | |
| 表单行 | __field | |
| 列表 | __list | |
| 列表项 | __item |
SCSS 嵌套
scss
.login {
background: linear-gradient(135deg, var(--color-bg-tertiary), var(--color-primary-light));
&__field {
display: flex;
align-items: center;
&:focus-within {
box-shadow: 0 0 0 2px var(--color-primary);
}
}
&__code-btn {
height: 32px;
border-radius: 8px;
&--active { color: var(--color-primary); background: var(--color-primary-light); }
&--disabled { color: var(--color-text-placeholder); background: var(--color-bg-tertiary); }
}
}主题变量
变量速查
主题色
| 语义 | Light | Dark | 变量 |
|---|---|---|---|
| 主色 | #4d80f0 | #3b82f6 | --color-primary |
| 主色浅 | #e8f0fe | #1e3a5f | --color-primary-light |
| 成功 | #34d19d | #34d399 | --color-success |
| 成功浅 | #e6f7f1 | #064e3b | --color-success-light |
| 警告 | #f0883a | #fbbf24 | --color-warning |
| 警告浅 | #fff4e6 | #78350f | --color-warning-light |
| 危险 | #ff4757 | #f87171 | --color-danger |
| 危险浅 | #fff1f2 | #7f1d1d | --color-danger-light |
背景色
| 语义 | Light | Dark | 变量 |
|---|---|---|---|
| 主背景 | #ffffff | #1f2937 | --color-bg |
| 次背景 | #f5f5f5 | #111827 | --color-bg-secondary |
| 三级背景 | #f1f5f9 | #1e293b | --color-bg-tertiary |
文字色
| 语义 | Light | Dark | 变量 |
|---|---|---|---|
| 主文字 | #1f2937 | #f9fafb | --color-text |
| 次文字 | #6b7280 | #9ca3af | --color-text-secondary |
| 占位文字 | #9ca3af | #6b7280 | --color-text-placeholder |
边框色
| 语义 | Light | Dark | 变量 |
|---|---|---|---|
| 默认 | #e5e7eb | #374151 | --color-border |
| 浅色 | #f3f4f6 | #1f2937 | --color-border-light |
使用原则
scss
// ✅ 使用变量
.card { background: var(--color-bg); border: 1rpx solid var(--color-border); }
// ❌ 禁止硬编码
.card { background: #ffffff; border: 1rpx solid #e5e7eb; }Wot Design 变量桥接
scss
--wot-color-theme: var(--color-primary);
--wot-color-success: var(--color-success);
--wot-color-warning: var(--color-warning);
--wot-color-danger: var(--color-danger);
--wot-color-bg: var(--color-bg);
--wot-color-text: var(--color-text);z-index 层级
只有 position: fixed/sticky/absolute 且存在层叠竞争的元素才设 z-index,禁止魔法数字。
scss
--z-dropdown: 100; // 下拉菜单、Popover
--z-sticky: 200; // 吸顶栏、吸底栏
--z-overlay: 300; // 遮罩层
--z-popup: 400; // Popup
--z-dialog: 500; // Dialog / MessageBox
--z-toast: 600; // Toast
--z-navbar: 700; // 固定导航栏
--z-fab: 800; // 浮动按钮间距与圆角
间距
| 语义 | 值 | 场景 |
|---|---|---|
| xxs | 4rpx | 极小间距 |
| xs | 8rpx | 图标与文字 |
| sm | 16rpx | 模块间分隔 |
| md | 24rpx | 卡片内边距 |
| lg | 32rpx | 页面左右边距 |
| xl | 40rpx | — |
| xxl | 48rpx | 大区块分隔 |
scss
--spacing-xxs: 4rpx; --spacing-xs: 8rpx; --spacing-sm: 16rpx;
--spacing-md: 24rpx; --spacing-lg: 32rpx; --spacing-xl: 40rpx;
--spacing-xxl: 48rpx;圆角
| 场景 | 值 |
|---|---|
| 小元素(按钮、标签) | 8rpx |
| 中元素(输入框) | 16rpx |
| 大卡片 | 24rpx |
| 圆形 | 50% |
阴影
| 级别 | 值 | 场景 |
|---|---|---|
| sm | 0 1px 2px rgba(0,0,0,0.05) | 卡片 |
| md | 0 4px 6px rgba(0,0,0,0.1) | 弹窗 |
| lg | 0 10px 15px rgba(0,0,0,0.1) | 模态框 |
字体
| 场景 | 字号 | 字重 | 行高 |
|---|---|---|---|
| 辅助信息 | 24rpx | 400 | 1.4 |
| 次要文字 | 26rpx | 400 | 1.5 |
| 正文 | 28rpx | 400 | 1.6 |
| 小标题 | 30rpx | 600 | 1.4 |
| 标题 | 32rpx | 600 | 1.3 |
| 大标题 | 36rpx | 700 | 1.2 |
UnoCSS Shortcuts
| 快捷类 | 展开 | 用途 |
|---|---|---|
flex-center | flex justify-center items-center | 居中 |
flex-between | flex justify-between items-center | 两端对齐 |
flex-start | flex justify-start items-center | 起始对齐 |
flex-col-center | flex flex-col items-center | 纵向居中 |
新增 Shortcut 条件:全局出现 ≥ 5 次 + 有明确语义 + 不含业务色值。
WXSS 兼容性
| 特性 | 状态 | 替代 |
|---|---|---|
:deep() | 编译后残留,WXSS 报错 | 组件 custom-class + 非 scoped style |
[attr*="val"] | 不支持 | 具体类名 |
:has() / :is() | 不支持 | 具体选择器 |
vue
<!-- ✅ custom-class + 非 scoped 覆盖组件样式 -->
<wd-card custom-class="my-card">
<template #title>标题</template>
</wd-card>
<style lang="scss">
.my-card { margin: 0 !important; }
.my-card .wd-card__body { padding: 24rpx !important; }
</style>反模式
| 反模式 | 正确做法 |
|---|---|
| 硬编码颜色值 | 使用 CSS 变量 |
| 普通流内容设 z-index | 只有定位元素才设 |
::deep([attr*="val"]) | 使用具体类名 |
| Block 名过于通用 | 使用页面/功能前缀 |
height: 100vh | 使用 min-height: 100vh |
| z-index 魔法数字 | 使用 var(--z-*) |
SCSS 大量 @apply | 原子类留在模板 |
| AI 蓝紫渐变 | 纯色或品牌色 |
| Emoji 当图标 | SVG 图标 |
| 过度装饰(阴影+渐变+动画) | 最多一个装饰效果 |
自查清单
- [ ] 页面根元素
class="page" - [ ] 颜色全部使用 CSS 变量
- [ ] z-index 使用
var(--z-*),普通流内容未设 z-index - [ ] 页面容器
min-height: 100vh - [ ] BEM 类名带页面前缀
- [ ] 模板中原子类 ≤ 3 个,超过则归入 BEM
