上篇回顾:2.2-02 对比了 Vue 可变数据与 React 不可变数据两种哲学。本篇讲 UI 组件库------后端转前端搭管理后台的「最后一公里」。你不需要从零写表单、表格、弹窗,用 Element Plus 或 Ant Design 能快速搭出专业界面。但「什么时候该封装业务组件、什么时候不该」是个重要的判断题。

一、开篇:从手写 CSS 到用组件库的转变
后端工程师第一次写管理后台,通常手写 HTML + CSS:
html
<table>
<tr><th>名称</th><th>操作</th></tr>
<tr><td>Java</td><td><button>编辑</button></td></tr>
</table>
能用,但丑。加 CSS 美化要写半天,响应式、交互、校验全要手写。
用 Element Plus:
vue
<el-table :data="tableData" stripe>
<el-table-column prop="name" label="名称" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button @click="edit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
自带样式、分页、排序、加载态。组件库的价值不是「好看」,而是「把常见的交互模式封装成可复用的积木」。
二、两大主流组件库
| 维度 | Element Plus | Ant Design |
|---|---|---|
| 框架 | Vue3 | React(也有 Vue 版) |
| 风格 | 简洁、中后台 | 专业、设计规范完善 |
| 生态 | 国内中后台主流 | 国际化、大厂出品 |
| 文档 | 中文友好 | 英文为主 |
| 适合 | Vue 项目、国内业务 | React 项目、国际化业务 |
选型建议:Vue 项目用 Element Plus,React 项目用 Ant Design。
三、按需引入:tree-shaking 的价值
3.1 全量引入的问题
javascript
// 全量引入(不推荐)
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
app.use(ElementPlus);
打包体积 1MB+,大部分组件你没用到。
3.2 按需引入
javascript
// Vite 配置自动按需引入
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] }),
]
};
模板里直接用 <el-button>,构建时自动只打包用到的组件。体积从 1MB 降到几十 KB。
四、三大高频场景的标准解法
4.1 表单校验
vue
<template>
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-button @click="submit">提交</el-button>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue';
const formRef = ref();
const form = reactive({ username: '', email: '' });
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '长度 3-20', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式错误', trigger: 'blur' }
]
};
const submit = async () => {
await formRef.value.validate(); // 校验通过才继续
// 提交逻辑
};
</script>
4.2 表格分页
vue
<template>
<el-table :data="tableData" v-loading="loading">
<el-table-column prop="name" label="名称" />
<el-table-column prop="createdAt" label="创建时间" />
</el-table>
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
@change="loadData"
/>
</template>
4.3 弹窗
vue
<el-dialog v-model="visible" title="编辑">
<el-form :model="form"><!-- 表单内容 --></el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</template>
</el-dialog>
五、主题定制与样式穿透
5.1 CSS 变量定制
css
:root {
--el-color-primary: #409eff;
--el-color-success: #67c23a;
}
5.2 样式穿透(:deep)
组件库的内部元素有 scoped 样式,外部改不了。用 :deep:
vue
<style scoped>
:deep(.el-table .cell) {
font-size: 14px;
}
</style>
六、二次封装:什么时候该封、封什么
6.1 该封装的场景
- 重复组合:同一个表格+搜索+分页组合用了 5 次以上
- 业务规则:所有表单的邮箱字段都有相同的校验+提示
- 统一风格:全系统的按钮尺寸/颜色要一致
6.2 不该封装的场景
- 只用了 1~2 次:封装的代价比直接写还大
- 需求不稳定:业务还在变,封装后改起来更麻烦
- 过度抽象:把所有配置都做成 props,组件比原版还复杂
6.3 封装原则:开放封闭
vue
<!-- 好的封装:固定业务部分,开放其余 -->
<template>
<el-table :data="data" :columns="columns">
<!-- 业务固定:分页、加载态内置 -->
<!-- 业务可变:列配置、插槽透传 -->
</el-table>
</template>
反面教材:把 el-table 的所有 props 都透传一遍,再加 20 个业务 props------这种封装比直接用 el-table 还复杂。
七、小结表
| 概念 | 价值 | 注意 |
|---|---|---|
| 按需引入 | 减小打包体积 | 用 unplugin 自动化 |
| 表单校验 | 减少手写校验逻辑 | rules 写在 setup 里 |
| 表格分页 | 标准化列表交互 | v-model 双向绑定页码 |
| 样式穿透 | 改组件库内部样式 | 用 :deep 不用 ::v-deep |
| 二次封装 | 复用业务模式 | 不过度封装 |