2337 字
约 7 分钟
17
S14-X05 · Element Plus 企业级后台组件库的取舍

后台选组件库,选的不是好不好看,而是"它替你做掉了哪些决定,又把哪些硬塞给你"。Element Plus 的强项是表单、表格、弹窗这些刚需;弱项是自由度——默认值很强预设,改起来要么顺它的变量体系,要么跟它的 DOM 结构打架。

这篇讲六件事:版本对应 → 引入方式 → 主题 → 国际化 → 表单与表格 → 封装边界,末尾列坑。


1. 版本对应:Vue 3 只有 Element Plus

包 Vue 版本 状态 说明
element-ui Vue 2 维护模式 不跟进 Vue 3,babel-plugin-component 也停在 Vue 2
element-plus Vue 3.2+ 活跃 TS + 组合式 API 重写,API 有大量改动

迁移易漏三处:el-icon 换成 @element-plus/icons-vue 组件;size="medium" 已废弃;el-table 的 slot-scope 全换 #default="scope"。按 Element Plus 2.x 文档核对再动手,能省一半返工。


2. 引入方式:全量还是按需

// 全量:两行搞定,代价全进包
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
// 按需:vite.config.ts,样式自动带上
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({ resolvers: [ElementPlusResolver()], dts: 'src/auto-imports.d.ts' }),
    Components({ resolvers: [ElementPlusResolver({ importStyle: 'css' })], dts: 'src/components.d.ts' }),
  ],
})
维度 全量 按需
配置成本 两行 两个插件 + d.ts
产物 组件与样式全进包 只含模板里用到的
函数式组件 自动可用 靠 AutoImport 的 resolver 兜
<component :is> 动态用法 可用 失效,解析器看不到运行时字符串
SCSS 变量定制 需重写入口 需 importStyle: 'sass'

两条硬边界:

  1. 按需靠静态分析。<component :is="'ElButton'">、h(ElButton) 不显式 import 就会运行时找不到组件。
  2. importStyle 决定 SCSS 变量能否覆盖:'css' 是预编译产物,改 $colors 无效;要 SCSS 定制就改 'sass',编译更慢。

别凭感觉判断按需一定更小。vite build 后看 dist/assets 各 chunk 体积,或挂 rollup-plugin-visualizer 核对。


3. 主题:CSS 变量优先,SCSS 兜底

Element Plus 的样式分两层:SCSS 变量(编译期)生成 CSS 变量(运行期)。运行时能改的都走 CSS 变量。

/* 换主色必须同时补齐派生色阶 */
:root {
  --el-color-primary: #2563eb;
  --el-color-primary-light-3: #5b8def;
  --el-color-primary-light-5: #8fb2f5;
  --el-color-primary-light-9: #eef4fe;
  --el-color-primary-dark-2: #1d4ed8;
  --el-border-radius-base: 6px;
}
html.dark {                      /* 暗色要单独写一份 */
  --el-bg-color: #1a1a1a;
  --el-text-color-primary: #e5e7eb;
}

只改 --el-color-primary 是最常见的半成品:light-3/light-5/light-9 是独立定义的,不跟着变,于是按钮 hover 还是旧色、el-tag 浅底还是旧色阶。

// 想一次性生成整套色阶,走 SCSS 变量
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: ('primary': ('base': #2563eb), 'success': ('base': #16a34a))
);
@use 'element-plus/theme-chalk/src/index.scss' as *;
定制层次 手段 生效时机 适用
单点微调 CSS 变量 运行时 品牌色、圆角、字号
整族色阶 SCSS $colors 编译期 换主色且要全部派生色
组件结构 :deep() 覆盖 运行时 布局微调,但脆弱
深度定制 自建组件 — 库已不适用

html.dark 只是把 --el-bg-color、--el-text-color-primary 重新赋值一遍,自定义的"品牌色浅底"也得给暗色写一份,否则会亮得刺眼。


4. 国际化

<!-- App.vue:组件库文案跟随 vue-i18n 的 locale -->
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'

const { locale } = useI18n()
const elLocale = computed(() => (locale.value === 'zh-CN' ? zhCn : en))
</script>

<template>
  <el-config-provider :locale="elLocale" :z-index="3000">
    <router-view />
  </el-config-provider>
</template>

el-config-provider 是全局配置唯一入口:语言包、默认尺寸、弹层 z-index 起始值。弹窗套弹窗 z-index 打架,多半是没设或设得太低。语言包按需 import,别 import * as lang。


5. 表单校验

<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'

const formRef = ref<FormInstance>()
const form = reactive({ name: '', email: '', age: 0 })

const rules: FormRules = {
  name: [{ required: true, message: '必填', trigger: 'blur' }],
  email: [{ type: 'email', message: '格式不正确', trigger: ['blur', 'change'] }],
  age: [{
    validator: (_r, v, cb) => (v < 18 ? cb(new Error('需年满 18 岁')) : cb()),
    trigger: 'change',
  }],
}

async function submit() {
  const ok = await formRef.value?.validate().catch(() => false)
  if (!ok) return
  await fetch('/api/users', { method: 'POST', body: JSON.stringify(form) })
}
</script>
用法 返回 何时用
validate() 无参 Promise<boolean>,失败 reject await 流程
validate(cb) undefined,回调拿 (valid, fields) 要知道哪几个字段失败
validateField(['a']) Promise 分步表单只校验当前步
clearValidate() — 弹窗重开清红字

三条规则:await validate() 失败是 reject 不是返回 false,不接 .catch 就是未捕获拒绝;自定义 validator 必须调 callback;trigger: 'change' 在 el-input 上是每次输入都触发,要"失焦校验 + 输入清红字"就用 blur + clearValidate。


6. el-table 的大数据量问题

el-table 每个单元格一个节点,一万行即几万个 DOM,卡在渲染不在数据。

方案 适用量级 代价
服务端分页 + el-pagination 任意 需后端支持,不能一屏看全
前端分页(拿全量再 slice) 数千行 首屏传输大
el-table-v2(虚拟滚动) 十万行级 API 不同,插槽能力弱
换 vxe-table 等 十万行 + 复杂表头 换库成本
<script setup lang="ts">
const page = ref(1), size = ref(50)
const all = ref<Row[]>([])
const pageData = computed(() =>
  all.value.slice((page.value - 1) * size.value, page.value * size.value))
</script>

<template>
  <el-table :data="pageData" height="600" border>
    <el-table-column prop="id" label="ID" width="80" fixed />
    <el-table-column prop="name" label="名称" show-overflow-tooltip />
  </el-table>
  <el-pagination v-model:current-page="page" :page-size="size"
    :total="all.length" layout="total, prev, pager, next" />
</template>

el-table-v2 与 el-table 不是同一套 API:列走 columns 数组、单元格走 cellRenderer。选它前先确认多级表头、行合并、fixed 列——这三项在 v2 上要么不支持,要么写法完全不同。

两条细节:show-overflow-tooltip 比自定义 tooltip 轻得多;fixed 列会复制 DOM,列多翻倍。


7. 二次封装的边界

场景 该封装 直接改样式
同一业务规则反复出现(金额右对齐 + 千分位) ✅ —
统一交互(删除二次确认) ✅ —
只想让按钮圆角不同 — ✅ CSS 变量
要改组件 DOM 结构 ✅ 自建 ❌ :deep() 会随版本碎掉
要透传全部 props/事件 ✅ v-bind="$attrs" —

判断标准只有一条:调用方少写的代码是否大于封装本身的维护成本。只包 type 和 size 的 BaseButton 是纯负债。要封就封到业务语义层:

<script setup lang="ts">
defineOptions({ inheritAttrs: false })
withDefaults(defineProps<{ label?: string; loading?: boolean }>(), { label: '保存' })
</script>

<template>
  <el-button v-bind="$attrs" type="primary" :loading="loading">{{ label }}</el-button>
</template>

inheritAttrs: false 是关键:不关掉自动继承,class 和 style 会同时落在根节点和内部 el-button 上。


8. 组件库与设计系统

组件库是实现,设计系统是规范。性价比最高的做法是加一层 token:颜色、圆角、字号收进 --app-*,再映射到 --el-*,换库时改动面只剩映射层。直接对着组件库画设计稿,做出来的后台会长得像别人的。


9. 排错:六个高频现象

现象 原因 处理
弹窗内 formRef 为 null el-dialog 默认不渲染内容 在 @open 或 nextTick 后取值
el-select 绑对象选不中 未指定 value-key 设 value-key="id",或改绑 id
暗色下自定义区域仍是亮色 只覆盖了 :root 补 html.dark 一套
主色改了 hover 还是旧色 派生色阶未同步 补齐 light-3/light-5,或用 SCSS $colors
ElMessage 样式丢失 按需引入但没走 resolver import 'element-plus/es/components/message/style/css'
表格滚动表头错位 height 用百分比或祖先有 transform 给固定像素高度,去掉 transform

第一条最高频:el-dialog 未打开前内容不存在,onMounted 里 resetFields() 必然报错。


10. 边界:什么时候别用 Element Plus

场景 更合适 理由
C 端展示页 自建 / Tailwind 默认样式是"后台味"
强设计驱动产品 无头组件(Radix Vue / Headless UI) 结构自由,样式自控
只要三五个组件 手写或用 VueUse 引入成本大于收益
极致包体 按需 + 自建轻组件 运行时依赖无法完全 tree-shake
复杂数据网格 AG Grid / TanStack Table el-table 的表头与列能力有天花板

Element Plus 的最佳射程是中后台管理系统:表单多、表格多、弹窗多、交互固定、用户是内部人员。离得越远,每小时都在跟默认值谈判。


11. 自测

  1. element-ui 能配 Vue 3 吗?迁移时哪三处最容易漏?
  2. 按需引入在哪种写法下会失效,为什么?
  3. 只改 --el-color-primary,为什么按钮 hover 色没变?
  4. validate() 失败时返回什么,不接 .catch 会怎样?
  5. el-table 与 el-table-v2 的 API 差异具体在哪三点?
  6. 弹窗里的表单为什么 formRef 一开始是 null?
S14-X05 · Element Plus 企业级后台组件库的取舍
https://www.clxhxhhr.top/posts/5715/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。