Vue3 + UniApp 项目 BEM 与 UnoCSS 协作规范|告别模板满屏原子类与命名混乱
前言
在开发 youlai-app(一款基于 uni-app + Vue 3 + UnoCSS 的跨平台移动端开源模板)的过程中,遭遇了一个极其痛苦的阶段:
随着业务逻辑变复杂,HTML 模板里的 class 变得越来越长。下面效果 PK 展示的就是 youlai-app work/user/index.vue 的真实代码——一个用户卡片,18 个原子类堆在模板里,没有一个语义类名能帮你快速定位"这是头像"“那是昵称”:
- 审查元素时:看着一堆原子类,根本不知道当前审查的是哪个业务组件
- 复用代码时:只能靠复制粘贴长长的一串字符串
- 团队协作时:BEM 命名五花八门——
.img、.hero、.app-title,接手别人的代码犹如看天书
为了彻底根治这个痛点,我们制定了这套 youlai-app 核心 CSS 规范。它不仅解决了 BEM 和 UnoCSS 的冲突,更是一套严谨的团队代码规范。
🚀 效果 PK:混乱 vs 规范
改造前:
<view class="flex-start">
<image class="w-80rpx h-80rpx rounded-full" :src="item.avatar" mode="aspectFill" />
<view class="flex-1 ml-16rpx">
<view class="flex-start mt-12rpx">
<text class="font-bold text-32rpx">{{ item.nickname }}</text>
<wd-icon name="gender-male" class="ml-8rpx" />
</view>
<text class="text-24rpx color-text-secondary">{{ item.roleNames }}</text>
</view>
</view>
<view class="flex gap-24rpx mt-12rpx">
<wd-icon name="mobile" size="16" class="color-text-secondary" />
<text class="ml-8rpx text-24rpx color-text-secondary truncate">{{ item.mobile }}</text>
</view>
<view class="flex-between mt-16rpx">
<text class="text-24rpx color-text-placeholder">{{ item.createTime }}</text>
<view class="w-88rpx h-88rpx flex-center rounded-full">
<wd-icon name="more" size="18" class="color-text-secondary" />
</view>
</view>
改造后:
<view class="user-card__header">
<image class="user-card__avatar" :src="item.avatar" mode="aspectFill" />
<view class="user-card__main">
<view class="user-card__name-row">
<text class="user-card__name">{{ item.nickname }}</text>
<wd-icon name="gender-male" />
</view>
<text class="user-card__role">{{ item.roleNames }}</text>
</view>
</view>
<view class="user-card__contact">
<wd-icon name="mobile" size="16" />
<text class="user-card__contact-text">{{ item.mobile }}</text>
</view>
<view class="user-card__footer">
<text class="user-card__time">{{ item.createTime }}</text>
<view class="user-card__more">
<wd-icon name="more" size="18" />
</view>
</view>

| 指标 | 改造前 | 改造后 |
|---|---|---|
| 模板中原子类数量 | 18 个 | 0 个 |
| 语义类名数量 | 1 个(item-card) |
11 个 BEM 类 |
| 修改字号需改几处 | 每个模板逐一改 | 只改 SCSS 一处 |
核心收益:模板可读性提升 10 倍,样式修改内聚到 SCSS,不再翻模板找原子类。
一、核心心法:为什么我们既需要 BEM,又需要 UnoCSS?
很多开发者会有疑问:“既然用了 UnoCSS,连 flex、margin 都能直接写,我还要 BEM 干嘛?”
因为纯 UnoCSS 会丧失"语义",而纯 BEM 会降低"效率"。
在我们的规范中,这两者有着极其明确的分工:
- BEM 是"骨架与身份证":负责告诉开发者"这个 DOM 是什么"(语义化),并作为未来可能需要的复杂 CSS 选择器的锚点
- UnoCSS 是"血肉与皮肤":负责告诉浏览器"这个 DOM 长什么样"(视觉表达),提供极速的排版与样式渲染
- Shortcuts 是"收敛器":负责把超长的原子类组合提取为可复用的语义化快捷方式

二、三条红线:彻底划清开发边界
不要再纠结样式写在 SCSS 还是 HTML 里了,请团队所有成员死记以下三条法则:
2.1 法则一:HTML 标签必须有 BEM 语义锚点
有意义的业务组件或复用区块,用 BEM 类名打头 + UnoCSS 原子类辅助;简单的一次性布局直接用原子类即可,无需强行套 BEM。
<view class="flex items-center p-32rpx bg-white rounded-16rpx">...</view>
<view class="user-card flex items-center p-32rpx bg-white rounded-16rpx">...</view>
2.2 法则二:坚决不用 SCSS @apply 桥接
既然用 UnoCSS,就让样式留在模板里。如果你去 SCSS 里大量写 @apply,不仅增加了打包编译的负担,还违背了 UnoCSS "所见即所得"的提效初衷。如果原子类太长,请用 Shortcuts 解决(见第四章)。
2.3 法则三:SCSS 只留给"原子类干不了的脏活"
什么时候才允许打开 <style lang="scss">?只有这三种情况:
- 复杂的伪类/伪元素:
::before、::after、:nth-child - 复杂的 CSS 动画:
@keyframes - 穿透修改第三方组件库样式:
:deep()

三、BEM 命名防腐层:专治团队命名"五花八门"
为了防止团队成员乱造 BEM 名字,我们为 youlai-app 制定了严格的命名词汇表。这是一道防腐层,建议直接抄作业!
3.1 Block 命名:必须带页面/场景前缀
严禁使用 .hero、.list、.item 这种极易冲突的裸类名作为 Block。
格式:{页面/模块名}-{区块名}
首页(index) → home-hero / home-nav / home-notice / home-stat
我的(mine) → mine-hero / mine-profile / mine-community / mine-menu
3.2 Element 通用词汇(强制统一)
组件内部的元素,统一使用以下后缀,禁止团队内部自由发挥:
| 语义 | 标准词汇 | 禁止用法 |
|---|---|---|
| 头部 | __header |
|
| 标题 | __title |
|
| 头像 | __avatar |
|
| 操作区 | __actions |
|
| 容器 | __wrap |
|
| (注:表格有所精简,完整版请见规范文档) |
注意:BEM 最大层级只能有两级!如果遇到
Block__Element__Element,说明你需要提取一个新的 Block。
四、实战武器:如何消灭"类名面条汤"?
制定了 BEM 规范后,我们怎么解决 UnoCSS 原子类太长难以阅读的问题?我们提供两大实战武器。
4.1 武器一:Shortcuts 提取组件级样式
当一个 BEM 元素的样式过于复杂,或者在项目中多次重复时,把它提取到 uno.config.ts 中。
修改 uno.config.ts:
import { defineConfig } from 'unocss'
export default defineConfig({
shortcuts: {
// 格式:'语义化名称': '原子类组合'
'btn-primary': 'bg-blue-500 text-white py-16rpx px-32rpx rounded-8rpx text-center active:bg-blue-600',
'user-avatar': 'w-96rpx h-96rpx rounded-full border-2 border-gray-100 shadow-sm',
}
})
4.2 武器二:Vue 模板多行类名格式化
对于一些只用一次、没必要提炼 Shortcut,但又很长的布局类,直接利用 Vue 的数组绑定语法进行多行换行,保证代码结构像诗一样优美。
改造前(一行写到天荒地老):
<view class="community-post__header flex items-center justify-between p-24rpx border-b border-solid border-gray-100 bg-white">
改造后(多行格式化,清晰可读):
<view
class="community-post__header"
:class="[
'flex-between',
'p-24rpx bg-white',
'border-b border-solid border-gray-100'
]"
>
五、典型场景:状态切换怎么写最优雅?
在实际开发中,引入 UnoCSS 后,很多人的状态切换逻辑臃肿不堪。
❌ 反面教材(逻辑臃肿,满屏原子类):
<view class="msg-card" :class="isRead ? 'bg-gray-50 text-gray-400' : 'bg-white text-black font-bold'">
✅ 神级混搭(BEM Modifier 结合 UnoCSS):
依然保持 BEM 语义,让状态逻辑清晰可见。视觉表现交由 Shortcuts 统一管理,模板只负责表达"意图"。
<view
class="msg-card p-24rpx rounded-8rpx"
:class="isRead ? 'msg-card--read text-gray-400' : 'msg-card--unread font-bold'"
>
<text class="msg-card__title">系统通知</text>
</view>
总结
通过 BEM 定语义、UnoCSS 定视觉、Shortcuts 做收敛的三层协作规范,youlai-app 实现了:
- 模板可读:每个元素都有 BEM 语义名,审查元素不再迷失在原子类海洋
- 命名统一:Block 前缀 + Element 标准词汇表,团队协作不再各写各的
- 修改内聚:高频修改的字号/颜色通过 Shortcuts 统一管理,改一处全局生效
- 开发高效:日常样式直接敲原子类,只有复杂交互才打开 SCSS
相关开源项目:
| 项目 | 简介 | 源码 |
|---|---|---|
| youlai-app | uni-app + Vue 3 + UnoCSS 移动端 | Gitee |
| vue3-element-admin | Vue 3 + Element Plus 管理端 | Gitee |
| youlai-boot | Spring Boot 4 权限管理系统 | Gitee |
BEM 和 UnoCSS 不是非此即彼的选择,而是各有所长的搭档。写代码就像写文章——BEM 是"主语",让读者知道你在说什么;UnoCSS 是"形容词",让表达又快又准。希望这篇规范能帮你告别选择困难,写出既有语义又高效的企业级代码。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)