表格合并

lxf2023-04-09 20:42:01

在日常的中后台工作中,表格合并是个很常见的需求。本文实现了一种表格合并的方案,让你不再为表格合并的开发而发愁,提升你的工作效率

rowspan & colspan

表格合并主要依赖 rowspancolspan 两个属性
rowspan: 设置单元格可横跨的行数,为0表示横跨到最后一行
colspan: 设置单元格可横跨的列数,为0表示横跨到最后一列

写了一个简单demo,供大家体验

技术栈%20%20Vue3%20+%20ElementPlus

ElementPlus%20组件%20table%20进行表格合并需要我们实现%20span-method%20方法。官网也给出了对应的例子。但是问题的难点就在于如何实现%20span-method%20方法,每次不同合并需求,开发同学都得写一遍,有没有简单点的方法呢?

合并方案

接下来看看我们实际面对的场景,并给出一种简单通用的解决方案

  • 需求场景一 业务中有一些表格卡片,这些卡片均是服务端下发,前端进行渲染。每次有新的需求,前端不需要开发,只要服务端下发对应的数据即可。那么该如何实现该需求呢?

    • 思考
      因为是服务端下发,所以首先需要让服务端知道他自己要下发啥? 我们需要一种数据结构来描述表格的合并信息,并且该数据结构应该非常易于理解(别人一看就懂的那种)
      有了服务端下发的合并信息之后,我们需要根据该信息实现 span-method 方法
    • 解决方案
      1. 设计描述表格合并信息的数据结构
      // 单元格合并信息
      export interface CellMergeInfo {
          // 行合并信息
          row?: {
              start: number; // 起始行,索引从0开始
              end: number; // 终止行,索引从0开始
              col: number[]; // 合并列,哪几列需要合并,索引从0开始
          };
          // 列合并信息
          col?: {
              start: number; // 起始列,索引从0开始
              end: number; // 终止列,索引从0开始
              row: number[]; // 合并行,哪几行需要合并,索引从0开始
          };
      }
      // 表格合并信息
      export type TableMergeInfo = CellMergeInfo[]
      
      以上面的截图为例,我们用我们的数据结构描述一下合并信息
      // 第1,2,3列的第一行到第四行进行合并
      const mergeInfo: TableMergeInfo = [
        {
          row: {
            start: 0,
            end: 3,
            col: [0,1,2],
          }
        }
      ]
      
      1. 实现 span-method 方法
      function mergwTable({ rowIndex, columnIndex }) {
        const array = mergeInfo;
        for (let i = 0; i < array.length; i++) {
          const ele = array[i];
          if (ele.row) {
            const { start, end, col } = ele.row;
            if (col.includes(columnIndex)) {
              if (rowIndex === start) {
                return {
                  rowspan: end - start + 1,
                  colspan: 1,
                };
              }
              if (rowIndex > start && rowIndex <= end) {
                return {
                  rowspan: 0,
                  colspan: 0,
                };
              }
            }
          }
          if (ele.col) {
            const { start, end, row } = ele.col;
            if (row.includes(rowIndex)) {
              if (columnIndex === start) {
                return {
                  rowspan: 1,
                  colspan: end - start + 1,
                };
              }
              if (columnIndex > start && columnIndex <= end) {
                return {
                  rowspan: 0,
                  colspan: 0,
                };
              }
            }
          }
        }
      }
      
  • 需求场景二 表格合并 我们需要统计人的年龄,性别,学历等信息,为了更好的展示效果,我们需要对表格进行一些合并操作。

    • 思考
      我们还是沿用需求场景一的设计,唯一不同的是当前表格的合并信息是动态的,需要根据数据动态计算合并信息。
    • 解决方案
      假设服务端给到我们如下数据:
    const tableData = [
      {
        name: "张三", // 姓名
        age: "22",  // 年龄
        sex: "男",  // 性别
        academic: "小学", // 学历
        school: "xxx小学", // 学校
        idCard: 1, // 身份证
      },
      {
        name: "张三",
        age: "22",
        sex: "男",
        academic: "初中",
        school: "xxx中学",
        idCard: 1,
      },
      {
        name: "张三",
        age: "22",
        sex: "男",
        academic: "高中",
        school: "xxx高中",
        idCard: 1,
      },
      {
        name: "李四",
        age: "32",
        sex: "男",
        academic: "小学",
        school: "xxx小学",
        idCard: 2,
      },
      {
        name: "李四",
        age: "32",
        sex: "男",
        academic: "初中",
        school: "xxx中学",
        idCard: 2,
      },
    ];
    

    观察数据和我们最终想要的合并效果可以发现,只要同一列相邻两行信息一样&&隶属于同一个主体,我们就可以进行合并),这个主体在我们目前的场景中是人,在其他场景中可能是其他,但是主体都有一个特质,独一无二,可以进行唯一的区分。

    // 动态计算行合并信息
    // param 需要进行合并的列取的字段
    // paramColIndex param所在列的索引
    // uniqueParam 用来区分主体的参数,如userId,id 等等
    // data 表格渲染的数据
    // 比如 createRowMergeInfo("name", 0, "idCard", tableData);
    function createRowMergeInfo(
      param: string,
      paramColIndex: number,
      uniqueParam: string,
      data: any[]
    ) {
      const result: any[] = [];
      let startRow = 0;
      for (let i = 1; i < data.length; i++) {
        const prev = data[i - 1][param];
        const now = data[i][param];
        const prevUnique = data[i - 1][uniqueParam];
        const nowUnique = data[i][uniqueParam];
        // 主体不一致的时候或者上下两行不同时,停止合并
        if (now !== prev || prevUnique !== nowUnique) {
          result.push({
            row: {
              start: startRow,
              end: i - 1,
              col: [paramColIndex],
            },
          });
          startRow = i;
        }
        if (i === data.length - 1) {
          result.push({
            row: {
              start: startRow,
              end: i + 1,
              col: [paramColIndex],
            },
          });
        }
      }
      return result;
    }
    const row1 = createRowMergeInfo("name", 0, "idCard", tableData);
    const row2 = createRowMergeInfo("age", 1, "idCard", tableData);
    const row3 = createRowMergeInfo("sex", 2, "idCard", tableData);
    const mergeInfo = ref<TableMergeInfo>([...row1, ...row2, ...row3]);
    

总结

回顾一下我们的方案:

  1. 设计了一种数据结构 TableMergeInfo 来语义化的描述表格合并信息
  2. 根据表格合并信息实现了 span-method 的逻辑
  3. 对于静态的合并信息,我们可以直接按照需求写死合并信息
  4. 对于动态的合并信息,我们需要根据数据来动态的计算合并信息,并且给出了方法 createRowMergeInfo 来实现行合并信息的计算;目前没有遇到动态列合并的场景,因此 createColMergeInfo 没有给出实现
  • 方案优点
    1. 通用性强,适用多个场景
    2. 开发同学的工作由实现 span-method 变为如何生成合并信息;对于静态合并,可以直接写死;对于动态合并,使用 createRowMergeInfo 方法来进行生成;大大减轻了开发同学的工作量

写在最后

至此,我们使用该方案完成了所有表格合并的需求,让团队同学可以快速完成该类需求。 虽然使用的是 Vue + ElementPlus 技术栈,但是思路是一样的,只不过写法稍有改动而已。
如还有其他需求场景,欢迎补充。

表格合并