less 循环事件

43 阅读7分钟

前言

最近开发后台管理系统时遇到一个常见需求:vxetable 表头分组显示,且不同分组使用不同背景色。已知 vxetable 原生支持设置 class,但项目用的是自定义封装的 table 库,初期不想改动组件源码,于是踏上了从「硬编码 CSS」到「组件封装」的优化之路,最终兼顾了灵活性和复用性,分享给有类似需求的同学~

先上需求截图:

image.png

核心需求:

  1. 表头分两行,第一行是大分组(4 个分组,对应 4 种颜色)
  1. 第二行是具体列名,需继承对应分组的背景色
  1. 不侵入封装组件源码(初期),后续考虑复用性

方案一:硬编码 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,重复的硬编码逻辑可以用「循环」优化。定义分组配置(起始列、结束列、颜色),通过循环自动生成样式,解决硬编码的复用问题。

核心思路

  1. 定义分组数组:包含「分组名、起始列、结束列、背景色」
  1. 自定义 Less 循环:遍历分组数组,生成对应行列的样式
  1. 第一行表头通过「循环下标 + 偏移量」定位(10-13 列)
  1. 第二行表头通过「列范围」定位(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,从根源上解决复用问题。

核心思路

  1. 改造封装组件:让表头单元格支持接收 headerClassName(从 columns 配置中读取)
  1. 业务层配置:在 columns 中给对应分组的列指定统一的 headerClassName
  1. 样式层:通过 headerClassName 统一设置背景色,无需关心列索引

步骤 1:改造封装组件(关键)

找到封装的 table 组件(如 xxTable.vue),在表头渲染部分添加 headerClassName 支持:

image.png

说明:vxetable 原生支持 header-cell-class-name 属性,封装组件时只需透传该属性(无需大幅改造)。

步骤 2:业务层配置 columns

在业务代码中,给每个分组的列添加统一的 headerClassName:

image.png

步骤 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;
       }
      }
    }
  }
}

最终效果

image.png

点评

  • 优点:
  1. 复用性极强:同事遇到类似需求时,只需配置 headerClassName 即可
  1. 灵活性高:列数增减、分组调整时,无需修改样式,只需改 columns 配置
  1. 侵入性低:仅需给封装组件添加 headerClassName 绑定,不影响原有功能
  • 缺点:需要轻微改造封装组件(但长期收益远超成本)
  • 适用场景:团队协作、多次复用、列数动态变化的场景

总结:4 种方案对比

方案核心特点维护成本复用性适用场景
硬编码 CSS直接定位行列高(改列数需重构)临时需求、列数固定
Less 循环配置化生成样式中(改列范围需改数组)列数变动少、统一管理样式
Less each精简循环语法中(同循环方案)熟悉 Less、追求简洁代码
组件改造支持 headerClassName低(仅改 columns 配置)团队协作、多次复用

最终建议

  • 紧急临时需求:选「硬编码 CSS」
  • 个人开发、列数固定:选「Less each」
  • 团队协作、长期复用:选「组件改造」(推荐)

通过这次优化,不仅解决了当前需求,还为团队后续类似场景提供了标准方案,避免了重复写 CSS 的麻烦。如果你的项目也用了 vxetable 或自定义封装表格,不妨试试「组件改造」方案,一次改造,长期受益~