同样用 Element Plus,为什么你的后台总有一股“模板味”?

一个后台页面,功能都做完了:筛选能查,表格能翻页,弹窗能保存。

但你盯着它看,总觉得差点意思。

查询是主按钮,新增是主按钮,每一行又摆着三四个蓝色操作;姓名、账号、状态、时间一样醒目;筛选一个框,表格一个框,外面再套一个框。

单独看,每个组件都没问题。放在一起,页面却没有重点。

这种"模板味",往往就出在组件之间的关系还没有被设计。

这篇只拿一个用户管理页动手:保留 Element Plus,保留业务信息,把布局、表格和主题接起来。

先看同一张页面的两种写法

基础组合:

调整以后:

两张图使用同一组数据,也都有姓名、账号、角色、状态和创建时间。查询、新增、编辑、分配角色、重置密码这些入口也保留了。

变化集中在四件事:

  • 新增回到页头,查询留在筛选区。
  • 姓名与账号合并为"用户",同一格内分出主次。
  • 高频操作直接显示,低频操作收进"更多"。
  • 用间距和轻量分隔建立层级,减少完整网格的存在感。

这组图是为本文制作的 Vue 3 + Element Plus 教学演示,使用虚构数据,并非某个项目改版前后的历史截图。示例中的操作频率是假设,实际项目需要根据用户工作流程决定。

接下来拆开看,这几步分别怎么落到代码里。

一、先把按钮放对位置,再考虑它是什么颜色

把查询、重置、新增、导出放进同一排,开发起来很顺手。但它们并不属于同一件事。

查询和重置控制的是当前列表的筛选条件 ;新增是在创建一个新的业务对象。位置相邻,却不意味着职责相同。

可以先整理成这样的骨架:

vue 复制代码
<section class="user-page">
  <header class="user-page__header">
    <div>
      <h1>用户管理</h1>
      <p>管理团队账号、关联角色与访问状态</p>
    </div>
    <el-button type="primary" @click="openCreate">新增用户</el-button>
  </header>

  <form class="user-page__filters" @submit.prevent="search">
    <!-- 带明确标签的筛选字段 -->
    <el-button native-type="submit">查询</el-button>
    <el-button @click="reset">重置</el-button>
  </form>

  <el-table class="admin-table" :data="users">
    <!-- 业务列 -->
  </el-table>

  <footer class="user-page__pagination">
    <!-- 分页 -->
  </footer>
</section>

这里省略了业务逻辑,只看页面结构:进入页面先知道自己在哪里,接着可以新增,也可以缩小列表范围,最后处理具体的一行。

在这个示例里,"新增"是页面级主操作,所以保留实心主按钮。"查询"紧挨着筛选字段,位置已经表达了用途,可以降低颜色权重。

这不是"每个页面只能有一个主按钮"的硬规则。如果你的页面是交易流水查询,用户大部分时间都在反复查数据,查询完全可以成为最突出的操作。

先确认用户进来最常做什么,才能决定谁该抢眼。

同样,行内操作也不能一律藏起来。这里假设编辑最常用,分配角色、重置密码相对低频,才把后两项放进"更多"。如果客服每天都在重置密码,把它收进去只会增加操作次数。

二、表格的质感,先从"哪些字段应该一起读"开始

数据库里有几列,不代表界面就必须有几列。

姓名和账号通常是在共同回答"这是谁"。把它们分开,用户要来回扫两列;合在一起,姓名负责识别,账号负责消除重名歧义。

vue 复制代码
<el-table-column label="用户" min-width="220">
  <template #default="{ row }">
    <div class="user-identity">
      <strong>{{ row.displayName || row.username }}</strong>
      <span>{{ row.username }}</span>
    </div>
  </template>
</el-table-column>
css 复制代码
.user-identity {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 3px;
}

.user-identity strong {
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 600;
}

.user-identity span {
  color: var(--text-secondary);
  font-size: 12px;
}

同样的信息,现在有了阅读顺序。没有加插画,也不需要给每个人配一个随机颜色的头像。

但合并也有边界:如果用户经常单独按账号排序、批量复制账号,独立列可能更合适。视觉上更紧凑,不应牺牲实际任务。

另外两个容易见效的小调整:

让状态保留文字。"启用""停用"即使加了颜色,也要能直接读出来。颜色辅助扫描,文字负责明确含义。不要为了整齐,把所有状态都刷成品牌色。

**按数据类型决定对齐。**金额通常右对齐,更容易比较位数;姓名和描述左对齐,方便连续阅读;时间可以使用 font-variant-numeric: tabular-nums,让支持等宽数字的字体保持数字宽度一致。不要为了看起来规整,把所有列都居中。

三、边框、斑马纹和留白,都应该帮助读数据

border、stripe 都有适用场景。宽表格里跨很多列比对数据,清晰的网格或斑马纹可能很有用。

但只有几个字段的用户列表,如果同时出现卡片阴影、外边框、纵向网格、斑马纹,再给每个状态套一个标签,装饰本身就会占掉不少注意力。

本文这张表可以只保留横向分隔与 Hover 反馈,把纵向关系交给对齐和间距。

Element Plus 表格提供了可覆盖的 CSS 变量。下面放在全局样式文件 ,并通过 .admin-table 限定到采用这套规范的表格:Table 文档。

css 复制代码
.admin-table {
  --el-table-header-bg-color: var(--surface);
  --el-table-tr-bg-color: var(--surface);
  --el-table-row-hover-bg-color: var(--surface-hover);
  --el-table-border-color: var(--line);
  --el-table-text-color: var(--text-secondary);
  --el-table-header-text-color: var(--text-primary);
}

.admin-table .el-table__cell {
  padding: 12px 0;
}

.admin-table .cell {
  padding-inline: 24px;
}

变量处理颜色,后两个选择器处理单元格的空间。它们依赖组件内部类名,升级组件库时要一起检查;不要把这些覆盖复制到每个业务页面里。

如果放进 Vue 的 scoped 样式,内部节点可能无法被普通后代选择器命中。可以使用项目统一的组件覆盖文件;确实需要局部覆盖时,再按 Vue 的规则使用 :deep()。选一种明确的维护方式,别让同一张表被多个地方轮流覆盖。

示例把页头、筛选字段和表格内容的起始位置统一到 24px,视觉上就有了一条稳定的对齐线。

这里的 12px、24px 都只是这张页面的取值。高频录入和密集检索的后台,需要更紧凑的行高;长文本多的列表,又需要允许换行。

大留白不是高级感的通行证。多看一屏数据,有时比多留一块空白更重要。

四、只改 --el-color-primary,为什么还不够?

很多人的第一步都是:

css 复制代码
:root {
  --el-color-primary: #087f5b;
}

然后发现:按钮正常时变绿了,Hover、浅色背景、选中态却没有完全跟着走。

原因是组件并不只消费一个主色。Element Plus 的主题还包含 primary-light-*、primary-dark-2 等色阶;只覆盖基础变量,不会自动重算已经生成的其他色阶。主题文档提供了 Sass 定制和 CSS 变量两种路径。

如果项目主色相对固定,可以在官方 Sass 变量模块首次加载前配置:

scss 复制代码
// styles/element-theme.scss
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': #087f5b,
    ),
  )
);

// 全量样式引入示例;按需引入项目不要再加载这一行。
@use 'element-plus/theme-chalk/src/index.scss' as *;

这份入口应替代原来的预编译 element-plus/dist/index.css,而不是在它后面再叠一套。按需引入时,则需要让定制变量先于对应组件的 Sass 加载。

如果需要运行时换品牌色,也可以用 CSS 变量,但要同时维护相关色阶。本文演示为了直接在浏览器打开,采用了显式声明色阶的做法。

还有一个容易忽略的点:自动生成了色阶,不等于所有组合都适合阅读。尤其是浅色 Hover 背景配白字,要实际检查对比度,必要时单独调整按钮交互态。

五、深色主题暴露的问题,往往来自变量放错了地方

如果业务代码到处写着 #fff、#606266,增加深色主题就会变成到处补丁。

先把页面需要的颜色按用途命名:

css 复制代码
:root {
  --surface: #fff;
  --surface-hover: #f2f6f4;
  --text-primary: #202b33;
  --text-secondary: #5c6872;
  --line: #e3e8ec;
}

html[data-theme='dark'] {
  color-scheme: dark;
  --surface: #1c242c;
  --surface-hover: #27333b;
  --text-primary: #e8edf0;
  --text-secondary: #b4c0c9;
  --line: #35434e;
}

这只是正文、表面和分隔线的最小示例,不是完整主题。输入框、禁用态、状态色、遮罩和浮层还需要各自的定义。

这里有两个细节。

第一,**弱分隔线和输入框边界不要机械共用一种浅灰。**分隔线可以退后,输入框还得让人知道哪里能点、当前焦点在哪里。整页淡到看不清,并不会更精致。

第二,全局主题变量应放在 html 或 :root,不能只放在 .user-page。

例如 Select 的下拉浮层默认会被传送到页面其他位置。它不再是业务容器的后代,也就无法继承只定义在容器上的变量。Select 文档中的 teleported、append-to 描述了相关行为。

这就是"输入框已经变深色,下拉菜单还是白色"的一个常见排查方向。还要检查浮层实际消费的 --el-bg-color-overlay 等变量,不能只改输入框背景。

页面截图能确认大体层次,但验证主题还得亲自打开下拉菜单、移动鼠标、用 Tab 切换焦点,再看看禁用和空数据状态。

真正不协调的颜色,常常藏在这些时刻。

六、做到第二张页面,就该把这些规则收起来

第一张页面做好后,最容易发生的事是:下一张页面复制一份 CSS,再改几个数字。

几轮之后,用户页是 24px,角色页是 20px,菜单页又补了一层阴影。同一套组件库,再次拼出了三种风格。

我更愿意把这些内容按职责分开:

放在哪里 维护什么
主题文件 表面、文字、边框、状态与交互颜色
组件适配文件 Element Plus 变量与必要的内部样式覆盖
列表页公共样式 页头、筛选、表格、分页的布局与间距
业务页面 字段内容、权限条件、操作顺序与数据行为

我维护的开源项目 LY Fullstack 里,用户管理页就采用了这种分工:页面保留业务列和操作,公共 CRUD 样式组织工作区,表格适配与主题变量分别维护。

想接着看真实项目,可以从这三个文件读起:

本文为了讲清取舍,做了更小的教学示例:比如把部分操作收进"更多"、调整双行内容的留白,这些不是项目原页面的逐项复刻。源码中的行高、操作布局应该结合它自己的业务场景理解。

这种分工也不意味着要立刻造一个"万能表格",把所有插槽、按钮和条件都塞进一份 JSON。先复用已经稳定的样式和布局,业务差异继续留在页面里,维护起来通常更直观。

下次接到后台页面,可以先检查这五件事

  1. 最重要的操作,能不能一眼找到?
  2. 本来属于同一个对象的信息,是否被拆得太散?
  3. 边框、标签和颜色,是在帮助阅读,还是彼此争抢注意力?
  4. 换到另一张业务页面,间距和交互规则是否仍然一致?
  5. 打开浮层、切换主题、出现空数据以后,页面是否还完整?

Element Plus 已经提供了很多可靠的零件。页面最终呈现出来的样子,取决于我们怎样安排这些零件,让用户更容易完成手头的事。

如果准备从自己的后台挑一处修改,我建议先看那一列永远摆满蓝色文字的"操作"。你可能会发现,真正每天都在点的,只有其中一两个。

不过,页面优化到这里,还有一件更值得继续往下做的事。

在 AI 已经能帮我们写接口、梳理数据模型的今天,前端开发者也可以把交付往前推进一步:让这张页面背后的功能真正跑起来。

点下"新增用户",数据怎么存进数据库?列表的筛选和分页,后端怎么接?"分配角色"和"重置密码",又该怎样校验权限,才能避免任何登录用户都能调用?

这些问题接起来,眼前这张页面才算有了完整的业务链路。

下一篇,我们就沿着这张用户管理页,用 NestJS + TypeScript,从数据库、接口到前后端联调,把它背后的真实功能做出来。

后端用的也是大家熟悉的 TypeScript,不用先换一门语言。如果你已经在用 Vue + TS,可以带着现有基础跟着做:从第一个接口开始,逐步补上数据库和权限这些知识,不必一上来就被"学后端"三个字吓住。过程中也会聊聊,哪些工作可以交给 AI,哪些规则需要我们自己想清楚。

相关推荐
葡萄城技术团队2 小时前
用 Sheet 页导航视图管理复杂 SpreadJS 工作簿
前端
志尊宝2 小时前
Vue3 零基础每日笔记(054):Pinia 三件套详解——state / getters / actions 全搞懂
前端·javascript·vue.js·vue·前端开发
Caroline5162 小时前
GPT Image 2.5还能这么玩!一套提示词生成IP吉祥物,附完整案例
前端·gpt·tcp/ip
wangyadong3172 小时前
# el-table 复选框无法选中问题记录
前端·javascript·vue.js
葡萄城技术团队3 小时前
给 SpreadJS 单元格装上自动完成
前端
恋猫de小郭3 小时前
Flutter 多窗口重要优化合并,多窗口性能和实用性大幅提升
android·前端·flutter
StudyAndLife3 小时前
开源一个 API 开放平台门户前端:本地 npm run dev 就能逛接口文档
前端
广州华水科技3 小时前
GNSS变形监测一体机在大坝安全监测中的应用与优势分析
前端
hunteritself3 小时前
夯爆了!Qoder 狂肝 2 小时,293 个测试全绿,Credits 一分没扣
前端·人工智能·chrome·深度学习·机器学习