前言
最近开发后台管理系统时遇到一个常见需求:vxetable 表头分组显示,且不同分组使用不同背景色。已知 vxetable 原生支持设置 class,但项目用的是自定义封装的 table 库,初期不想改动组件源码,于是踏上了从「硬编码 CSS」到「组件封装」的优化之路,最终兼顾了灵活性和复用性,分享给有类似需求的同学~
先上需求截图:
核心需求:
- 表头分两行,第一行是大分组(4 个分组,对应 4 种颜色)
- 第二行是具体列名,需继承对应分组的背景色
- 不侵入封装组件源码(初期),后续考虑复用性
方案一:硬编码 CSS(快速实现)
最直接的思路:通过 nth-child 定位表头的行和列,逐个设置背景色。适合分组少、列数固定的场景,优点是简单易懂,缺点是扩展性差(改列数要重构 CSS)。
代码实现
.table-group {
:deep(.tn-table-pharmacy-statistical-productSummary) {
.vxe-table--header-wrapper {
.vxe-table--header {
// 第一行表头(大分组)
.vxe-header--row:first-child {
th:nth-child(10) {
&.col--group {
background-color: #ffe8e8; // 分组1颜色
}
}
th:nth-child(11) {
&.col--group {
background-color: #fbf9d4; // 分组2颜色
}
}
th:nth-child(12) {
&.col--group {
background-color: #e1f3fb; // 分组3颜色
}
}
th:nth-child(13) {
&.col--group {
background-color: #fcedff; // 分组4颜色
}
}
}
// 第二行表头(具体列)- 后续补充对应列的颜色
.vxe-header--row:nth-child(2) {
th:nth-child(1),
th:nth-child(2),
th:nth-child(3) {
background-color: #ffe8e8; // 对应分组1
}
// 其余分组列同理...
}
}
}
}
}
点评
- 优点:0 学习成本,直接上手写,适合紧急需求
- 缺点:列数变动时需要手动修改所有 nth-child 索引,维护成本高
- 适用场景:临时需求、列数固定且极少变动的表格
方案二:Less 循环(简化代码)
项目已引入 Less,重复的硬编码逻辑可以用「循环」优化。定义分组配置(起始列、结束列、颜色),通过循环自动生成样式,解决硬编码的复用问题。
核心思路
- 定义分组数组:包含「分组名、起始列、结束列、背景色」
- 自定义 Less 循环:遍历分组数组,生成对应行列的样式
- 第一行表头通过「循环下标 + 偏移量」定位(10-13 列)
- 第二行表头通过「列范围」定位(n+start 到 n+end)
代码实现
// 定义分组颜色变量
@revice-color: #ffe8e8;
@grand-color: #fbf9d4;
@recovery-color: #e1f3fb;
@back-color: #fcedff;
.table-group {
:deep(.tn-table-pharmacy-statistical-productSummary) {
.vxe-table--header-wrapper {
.vxe-table--header {
// 分组配置:名称 + 第二行起始列 + 第二行结束列 + 颜色
@group-ranges:
"revice" 1 3 @revice-color, // 分组1:第二行1-3列
"grand" 4 10 @grand-color, // 分组2:第二行4-10列
"recovery" 11 13 @recovery-color, // 分组3:第二行11-13列
"back" 14 17 @back-color; // 分组4:第二行14-17列
// 自定义循环函数
.loop-groups(@array; @index: 1) when (@index <= length(@array)) {
// 提取当前分组配置
@item: extract(@array, @index);
@start: extract(@item, 2); // 起始列
@end: extract(@item, 3); // 结束列
@color: extract(@item, 4); // 背景色
// 第一行表头:第10-13列(循环下标+9 = 目标列索引)
@target-index: @index + 9;
// 第二行表头:对应列范围
.vxe-header--row:nth-child(1) th:nth-child(@{target-index}),
.vxe-header--row:nth-child(2) .custom-header-cell-class:nth-child(n+@{start}):nth-child(-n+@{end}) {
background-color: @color;
// 可选:添加边框区分分组
border-right: 1px solid #fff;
}
// 递归循环下一个分组
.loop-groups(@array; @index + 1);
}
// 执行循环
.loop-groups(@group-ranges);
}
}
}
}
点评
- 优点:配置化管理分组,修改列范围 / 颜色只需改数组,无需动样式逻辑
- 缺点:依赖 Less 循环语法,对新手不友好;仍需手动维护列范围
- 适用场景:列数变动不频繁,需要统一管理分组样式的场景
方案三:Less each 语法(进一步精简)
Less 3.7+ 支持 each 语法,可直接遍历数组 / 映射,比自定义循环更简洁,无需手动处理递归,代码可读性更高。
代码实现
// 颜色变量不变(同上)
@revice-color: #ffe8e8;
@grand-color: #fbf9d4;
@recovery-color: #e1f3fb;
@back-color: #fcedff;
.table-group {
:deep(.tn-table-pharmacy-statistical-productSummary) {
.vxe-table--header-wrapper {
.vxe-table--header {
// 分组配置(与方案二一致)
@group-ranges:
"revice" 1 3 @revice-color,
"grand" 4 10 @grand-color,
"recovery" 11 13 @recovery-color,
"back" 14 17 @back-color;
// each 遍历分组数组
each(@group-ranges, .(@v, @k, @i) {
@start: extract(@v, 2);
@end: extract(@v, 3);
@color: extract(@v, 4);
@target-index: @i + 9; // 第一行目标列索引
// 生成样式(与方案二一致)
.vxe-header--row:nth-child(1) th:nth-child(@{target-index}),
.vxe-header--row:nth-child(2) .custom-header-cell-class:nth-child(n+@{start}):nth-child(-n+@{end}) {
background-color: @color;
border-right: 1px solid #fff;
}
});
}
}
}
}
点评
- 优点:代码更简洁,无需自定义递归循环,Less 原生语法更易维护
- 缺点:仍需手动维护列范围,不支持动态列数
- 适用场景:熟悉 Less 语法,需要精简代码的场景
方案四:改造封装组件(终极方案)
前面的方案都是「治标不治本」—— 列数动态变化时,仍需手动修改 CSS 配置。考虑到同事可能也会遇到类似需求,最终决定轻微改造封装的 table 组件,支持传入 headerClassName,从根源上解决复用问题。
核心思路
- 改造封装组件:让表头单元格支持接收 headerClassName(从 columns 配置中读取)
- 业务层配置:在 columns 中给对应分组的列指定统一的 headerClassName
- 样式层:通过 headerClassName 统一设置背景色,无需关心列索引
步骤 1:改造封装组件(关键)
找到封装的 table 组件(如 xxTable.vue),在表头渲染部分添加 headerClassName 支持:
说明:vxetable 原生支持 header-cell-class-name 属性,封装组件时只需透传该属性(无需大幅改造)。
步骤 2:业务层配置 columns
在业务代码中,给每个分组的列添加统一的 headerClassName:
步骤 3:编写全局样式
通过 headerClassName 统一设置背景色,无需关心列索引:
.table-group{
:deep(.tn-table-pharmacy-statistical-productSummary){
.vxe-table--header-wrapper{
.vxe-table--header{
.group-revice{
background-color: @revice-color;
}
.group-grand{
background-color: @grand-color;
}
.group-recovery{
background-color: @recovery-color;
}
.group-back{
background-color: @back-color;
}
}
}
}
}
最终效果
点评
- 优点:
- 复用性极强:同事遇到类似需求时,只需配置 headerClassName 即可
- 灵活性高:列数增减、分组调整时,无需修改样式,只需改 columns 配置
- 侵入性低:仅需给封装组件添加 headerClassName 绑定,不影响原有功能
- 缺点:需要轻微改造封装组件(但长期收益远超成本)
- 适用场景:团队协作、多次复用、列数动态变化的场景
总结:4 种方案对比
| 方案 | 核心特点 | 维护成本 | 复用性 | 适用场景 |
|---|---|---|---|---|
| 硬编码 CSS | 直接定位行列 | 高(改列数需重构) | 低 | 临时需求、列数固定 |
| Less 循环 | 配置化生成样式 | 中(改列范围需改数组) | 中 | 列数变动少、统一管理样式 |
| Less each | 精简循环语法 | 中(同循环方案) | 中 | 熟悉 Less、追求简洁代码 |
| 组件改造 | 支持 headerClassName | 低(仅改 columns 配置) | 高 | 团队协作、多次复用 |
最终建议
- 紧急临时需求:选「硬编码 CSS」
- 个人开发、列数固定:选「Less each」
- 团队协作、长期复用:选「组件改造」(推荐)
通过这次优化,不仅解决了当前需求,还为团队后续类似场景提供了标准方案,避免了重复写 CSS 的麻烦。如果你的项目也用了 vxetable 或自定义封装表格,不妨试试「组件改造」方案,一次改造,长期受益~