【超详细】再也不怕表格合并了

623 阅读2分钟

写作之前

先来一张效果图 效果图

  1. 右侧是一个正常的table,有很多列,此处只是随便弄了2列
  2. 分类的层级不固定1-5级
  3. 左侧第一列是table,但是这个table遇到分类一样的时候需要自动合并

合并规则

初始化

  1. 第一列前4个都是A1所以需要合并,A2单独一列无需合并
  2. 第二列B1单独一列,B2合并3列
  3. 第三列虽然第1,2行都是C1但是不能合并,因为前面的分类不一样

如何实现这样的交互

  • 首先我们看到这样的一个自动合并,首先想到的肯定是 table,再利用 rowspan 合并列
  • 行合并之后下一行td的数量将会比上一行少

html结构如何设计

要显示一个这样的table结构,肯定有3层的for循环, 每一个分类一个 tbody

<table>
  <tbody v-for="item in showTbodyList" :key="item.name">
    <tr v-for="(tr, index) in item.children" :key="index">
      <td v-for="it in tr" :key="it.key" :rowspan="it.rowspan">
        {{ it.name }}
      </td>
    </tr>
  </tbody>
</table>

后台数据结构

暂时后端只有5级,但是我们要考虑的肯定是 n

interface IData{
	level1: string, // 第一级分类
    level2: string, // 第二级分类
    level3: string, // 第三级分类
    level4: string, // 第四级分类
    level5: string, // 第五级分类
    key: string, // 数据唯一id
    // ...其他属性忽略
}

原始数据

开始的图片的数据如下

如果要显示,那么最终的数据应该是这样的

组件设计

首先分析那些是变化的

  • 展示的数据
  • 用于表示的层级字段名

所以得到以下 props

props: {
	// 层级的字段名
    levelList: {
      type: Array,
      required: true,
    },
    //要展示的list
    list: {
      type: Array,
      required: true,
    },
  },

获取最大的层级深度

因为层级是不固定的所以我们需要获取最大的层级深度maxColumns 此处我们使用计算属性实现,从后往前遍历,如果有一个数据有值,那么此时的i就是最大的maxColumns

maxColumns() {
      const { list, levelList } = this;
      for (let len = levelList.length, i = len - 1; i >= 0; i--) {
        const level = levelList[i];
        if (list.some(item => item[level])) {
          return i + 1;
        }
      }
      return 0;
    }

如何得到没有合并的表格

  • 首先找出第一级分类的名字,对数据筛选里面有第一级分类的,那么这就是tbody的数据
  • tbody的数据行数就是tr的行数
  • td的个数就是maxColumns 所以代码如下
getTbodyList() {
      const { list, levelList, maxColumns } = this;
      // 获取第一列有几种
      const classification = Array.from(
        new Set(list.map(item => item[levelList[0]])),
      );
      return classification.map(name => {
        const tbodyList = list.filter(item => item[levelList[0]] === name);
        const trLength = tbodyList.length;
        const children = [];
        // 行
        for (let i = 0; i < trLength; i++) {
          const tr = [];
          // 列
          for (let j = 0; j < maxColumns; j++) {
            const tdName = tbodyList[i][levelList[j]] || '';
            tr.push({
              name: tdName,
              rowspan: null,
              key: Math.random(),
            });
          }
          children.push(tr);
        }
        return {
          name,
          children,
        };
      });
    }

此时我们可以获取到 合并规则 时的样子

合并行

初始化

上一步生成tableData之后

  • 以列为单位
  • 找出当前列的分类
  • 遍历每一个分类
  • 开始时下标i为0
  • 找出一列中分类一样的数据trs,将trs0条的i个的rowspan设置为trs.length
  • 设置 tableDatatrs,下标 i+1
  • 开始递归
setTableRowSpan(tableData) {
      const { maxColumns } = this;
      function setRowspan(tbodyData, i = 0) {
        if (i >= maxColumns) {
          return;
        }
        const classification = Array.from(
          new Set(tbodyData.map(item => item[i].name)),
        );
        for (let j = 0; j < classification.length; j++) {
          const trs = tbodyData.filter(
            item => item[i].name === classification[j],
          );
          trs[0][i].rowspan = trs.length;
          setRowspan(trs, i + 1);
        }
      }
      tableData.forEach(item => {
        setRowspan(item.children);
      });
    }

清除rowspan为null的td

cleanEmptyTd(table) {
      table.forEach(tbody => {
        tbody.children.forEach(tr => {
        	// 必须从后往前
          for (let i = tr.length - 1; i >= 0; i--) {
            const td = tr[i];
            if (!td.rowspan) {
              tr.splice(i, 1);
            }
          }
        });
      });
    }

github

本文所有代码都放到github

如果有更好的方案,或者文中有错误的地方评论区见,也可以直接github提issure