前言

在开发 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,连 flexmargin 都能直接写,我还要 BEM 干嘛?”

因为纯 UnoCSS 会丧失"语义",而纯 BEM 会降低"效率"。

在我们的规范中,这两者有着极其明确的分工:

  • BEM 是"骨架与身份证":负责告诉开发者"这个 DOM 是什么"(语义化),并作为未来可能需要的复杂 CSS 选择器的锚点
  • UnoCSS 是"血肉与皮肤":负责告诉浏览器"这个 DOM 长什么样"(视觉表达),提供极速的排版与样式渲染
  • Shortcuts 是"收敛器":负责把超长的原子类组合提取为可复用的语义化快捷方式

在这里插入图片描述

复杂伪类/动画

写标签

先起 BEM 名字
明确语义

再敲 UnoCSS 原子类
极速排版

原子类太长?

提取到 Shortcuts
统一收敛

完成

打开 SCSS
写选择器


二、三条红线:彻底划清开发边界

不要再纠结样式写在 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">?只有这三种情况:

  1. 复杂的伪类/伪元素::before::after:nth-child
  2. 复杂的 CSS 动画@keyframes
  3. 穿透修改第三方组件库样式: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 __head / __top
标题 __title __name / __heading
头像 __avatar __photo
操作区 __actions __btns
容器 __wrap __container(全局容器用)
(注:表格有所精简,完整版请见规范文档)

注意: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

在线体验https://vue.youlai.tech

BEM 和 UnoCSS 不是非此即彼的选择,而是各有所长的搭档。写代码就像写文章——BEM 是"主语",让读者知道你在说什么;UnoCSS 是"形容词",让表达又快又准。希望这篇规范能帮你告别选择困难,写出既有语义又高效的企业级代码。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐