表格实现合并单元格

实现的效果

在这里插入图片描述

一、列合并

此需求的列合并比较简单, 直接使用el-table-column包括即可

<el-table-column align="center" sortable label="目标">
      <el-table-column prop="target1" sortable label="预设目标" />
      <el-table-column prop="target1" sortable label="目标值" />
</el-table-column>

二、行合并

1. 排序

1)原因

因为哪些单元格需要合并,哪些单元格就必须挨着,不挨着就无法进行单元格合并

2)实现思路

1、使用sort
2、由于大多数场景判断的字段都是字符串格式,不能直接使用a-b的形式来判断,所以使用判断大小来代替;
3、由于可能存在多个判断条件,比如按照学校和专业排序,学校相同的还需要按专业排序;
4、排序规则

  • 如果a.xx < b.xx,则返回-1,代表升序;
  • 如果a.xx > b.xx,则返回1,代表降序;
  • 如果相等,则需要判断是否还有其他判断条件,如果没有,则返回0,代表不做处理;如果有,则执行递归,继续以其他判断条件按照以上两个步骤进行判断大小;

3)代码实现

/**
 * 按一系列参数排序
 * @param {*} table 原数组
 * @param {*} names 排序的列的数组
 */
getSortArr(table, names) {
  return table.sort((a, b) => {
    function sortFn(names, index) {
      const itemA = a[names[index]]
      const itemB = b[names[index]]
      if (itemA < itemB) {
        return -1;
      } else if (itemA > itemB) {
        return 1;
      } else {
        // 如果当前列的值相同
        // 如果最大列索引值还大于当前列索引,则递归, 判断下一列的值,否则返回0即可
        if (names.length - 1 > index) {
          return sortFn(names, index + 1);
        } else {
          return 0;
        }
      }
    }
    return sortFn(names, 0)
  });
},

2. 生成单元格数据

1)原因

因为每一个单元格是否需要合并、是否需要被合并,以及需要合并的话要合并多个单元格,需要用一个固定的数据来控制

2)实现思路?

1、遍历排序之后的数据,判断该项是否与前一项的条件相等(第一项无需判断)
2、判断条件是有层级的,比如需要判断学校和专业,如果当前是在判断学校是否相等,那么就不需要考虑专业的情况;但如果当前是在判断专业,那么必须保证两者学校也是同一个,否则不能作合并处理;
3、定义一个数组,用于存储各个单元格的值;当不需要作合并处理时,存储一个和所在列索引值一致的数据即可,当需要作合并处理时,存储一个大于所在列索引值的数据,当需要被合并(即该单元格不会显示)时,存储0;

  • 举例:存储数据为[0,1,3,0,4],
    - 索引为0,1,4的单元格,存储值和索引一致,代表不需要合并
    - 索引为2的单元格,存储值为3,代表需要合并到索引为3的单元格
    - 索引为3的单元格,存储值为0,代表需要被合并,该单元格不需要显示

3)代码实现

/**
 * 生成各单元格的数据
 * @param {*} tableData 原数据
 * @param {*} rowSpanType 判断条件列的数组
 */
handleTableData(tableData, rowSpanType) {
  const result = {}; // 存储数据
  // 由于是多个判断条件,所以需要遍历
  for (var i = 0; i < rowSpanType.length; i++) {
    const rowSpanArr = []; // 存储数据
    let position = 0; // 存储数据的索引值
    // 遍历原数据的每一项,与前一项对比
    tableData.forEach((item, index) => {
      // 第一项直接存储,不作处理
      if (index == 0) {
        rowSpanArr.push(0);
        position = 0;
      } else {
        // 判断与前一项的值是否相等(包括此列之前所有的列的值)
        function isEqual() {
          for (var j = i; j >= 0; j--) {
            if (item[rowSpanType[j]] == tableData[index - 1][rowSpanType[j]]) {
              continue;
            } else {
              return false;
            }
          }
          return true;
        }
        if (isEqual()) {
          rowSpanArr[position] += 1; // 前一项需要合并,存储值+1,代表需要合并到哪一行
          rowSpanArr.push(0); // 该项需要被合并,存储值为0,
        } else {
          // 与索引相等,代表不需要合并
          rowSpanArr.push(index);
          position = index;
        }
      }
    });
    result[rowSpanType[i]] = rowSpanArr;
  }
  return result;
},

3. 合并

思路分析

el-table提供了合并行或列的计算方法,即span-method,直接使用即可
参数: row当前行,column当前列的数据、rowIndex当前行索引、columnIndex当前列索引
返回值(可返回数组或对象)
- 没有处理,代表不需要合并,也不需要被合并
- 返回1,代表不作合并处理
- 返回0,代表被合并
- 返回值大于1,代表会合并到多少单元格
- 举例:当columnIndex=0, rowIndex=0时,return [2,1]或者{rowspan:2,colspan:1},代表第一行第一列合并了第二行第一列

难点

但是什么条件下返回,返回什么值是个问题,所以每个单元格都需要一个数据来控制自己是否需要合并,是否需要被合并,以及如果合并需要合并多少格,通过思路2我们已经实现。

代码实现

spanMethod({ row, column, rowIndex, columnIndex }) {
  // 由于是多个判断条件,多个列都需要合并,所以需要遍历
  for (var i = 0; i < this.rowSpanType.length; i++) {
  	// 作某一列的合并处理
    if (column.property === this.rowSpanType[i]) {
      // 拿到当前单元格的存储数据
      const rowspan = this.rowSpanArr[this.rowSpanType[i]][rowIndex];
      // rowspan == rowIndex 代表不需要合并单元格,此处只需处理需要合并、需要被合并的单元格
      if (rowspan != rowIndex) {
        // rowspan===0代表被合并,!=0代表合并的目标行数
        if (rowspan === 0) {
          // 被合并的单元格,返回0即可
          return { rowspan: 0 };
          // return [0, 1]
        } else {
          // 合并数 = rowspan合并到的行数 - 当前所在的行数 + 1
          return { rowspan: rowspan - rowIndex + 1, colspan: 1 };
          // return [rowspan - rowIndex + 1, 1]
        }
      }
    }
  }
},

三、整体代码实现

使用的话,直接更改data中两个值即可,其他地方不需要动。

  • tableData 改成你的表格数据
  • rowSpanType 存放你需要合并的列(有顺序)
<template>
  <div>
    <el-table :data="tableData" :span-method="spanMethod" border style="width: 100%">
      <el-table-column prop="company" label="公司" />
      <el-table-column prop="division" label="部门" />
      <el-table-column prop="type" label="类别" />
      <el-table-column prop="name" label="项目" />
      <el-table-column align="center" sortable label="指标">
        <el-table-column prop="amount1" sortable label="指标1" />
        <el-table-column prop="amount2" sortable label="指标2" />
      </el-table-column>
      <el-table-column align="center" sortable label="目标">
        <el-table-column prop="target1" sortable label="预设目标" />
        <el-table-column prop="target1" sortable label="目标值" />
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tableData: [],
      // 单元格的数据
      rowSpanArr: {},
      // 需要排序和合并的列。按顺序
      // 此处代表先按公司排序,公司相同的按部分排序,再相同的按类型排序,以此类推
      rowSpanType: ['company', 'division', 'type'],
    };
  },
  created() {
    this.tableData = this.initTableData(10);
    this.tableData = this.getSortArr(this.tableData, this.rowSpanType);
    this.rowSpanArr = this.handleTableData(this.tableData, this.rowSpanType);
  },
  methods: {
    /**
     * 生成模拟表格数据
     * @param {*} num 数据数量(行数)
     */
    initTableData(num) {
      const result = []
      // 生成随机数据
      for (var i = 0; i <= num; i++) {
        const company = ['A', 'B', 'C'][this.getRandomInt(0, 2)];
        const division = ['x', 'y', 'z'][this.getRandomInt(0, 2)];
        const type = ['a', 'b', 'c', 'd', 'e', 'f'][this.getRandomInt(0, 5)];
        const name = this.getRandomInt(1, 4);
        result.push({
          id: i,
          company: `公司${company}`,
          division: `部门${division}`,
          type: `类别${type}`,
          name: `${company}、${division}、${type}`,
          amount1: `${this.getRandomInt(100, 1000)}`,
          amount2: `${this.getRandomInt(100, 1000)}`,
          target1: `${this.getRandomInt(100, 1000)}`,
          target2: `${this.getRandomInt(100, 1000)}`,
        });
      }
      return result;
    },
    /**
     * 按一系列参数排序
     * @param {*} table 原数组
     * @param {*} names 排序的列的数组
     */
    getSortArr(table, names) {
      return table.sort((a, b) => {
        function sortFn(names, index) {
          const itemA = a[names[index]]
          const itemB = b[names[index]]
          if (itemA < itemB) {
            return -1;
          } else if (itemA > itemB) {
            return 1;
          } else {
            // 如果当前列的值相同
            // 如果最大列索引值还大于当前列索引,则递归, 判断下一列的值,否则返回0即可
            if (names.length - 1 > index) {
              return sortFn(names, index + 1);
            } else {
              return 0;
            }
          }
        }
        return sortFn(names, 0)
      });
    },
    /**
     * 生成随机数
     * @param {*} min  最小值
     * @param {*} max  最大值
     */
    getRandomInt(min, max) {
      return min + parseInt(Math.random() * (max - min + 1));
    },
    spanMethod({ row, column, rowIndex, columnIndex }) {
      for (var i = 0; i < this.rowSpanType.length; i++) {
        if (column.property === this.rowSpanType[i]) {
          const rowspan = this.rowSpanArr[this.rowSpanType[i]][rowIndex];
          // rowspan == rowIndex 代表不需要合并单元格,不作处理,在此只处理需要合并的
          if (rowspan != rowIndex) {
            // rowspan===0代表被合并,!=0代表合并的目标行数
            if (rowspan === 0) {
              // 被合并的域
              return { rowspan: 0 };
            } else {
              // rowspan合并到的行数 - 当前所在的行数 + 1 = 合并数
              return { rowspan: rowspan - rowIndex + 1, colspan: 1 };
            }
          }
        }
      }
    },
    /**
     * 生成各行的合并数据
     * @param {*} tableData 原数据
     * @param {*} rowSpanType 需要合并的列的数组
     */
    handleTableData(tableData, rowSpanType) {
      const result = {};
      for (var i = 0; i < rowSpanType.length; i++) {
        const rowSpanArr = [];
        let position = 0; // 当前索引
        tableData.forEach((item, index) => {
          if (index == 0) {
            rowSpanArr.push(0);
            position = 0;
          } else {
            // 判断与前一项的值是否相等(包括此列之前所有的列的值)
            function isEqual() {
              for (var j = i; j >= 0; j--) {
                if (item[rowSpanType[j]] == tableData[index - 1][rowSpanType[j]]) {
                  continue;
                } else {
                  return false;
                }
              }
              return true;
            }
            if (isEqual()) {
              // 代表需要合并到哪一行
              rowSpanArr[position] += 1;
              rowSpanArr.push(0);
            } else {
              // 与索引相等,代表不需要合并
              rowSpanArr.push(index);
              position = index;
            }
          }
        });
        result[rowSpanType[i]] = rowSpanArr;
      }
      return result;
    },
  },
};
</script>
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flutter 目前还没有内置的表格控件,但是可以使用 `Table` 和 `TableCell` 来实现表格合并单元格。 具体实现步骤如下: 1. 定义表格数据和合并单元格的规则。 ```dart List<List<String>> tableData = [ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ['A3', 'B3', 'C3'], ['A4', 'B4', 'C4'], ]; Map<int, List<TableCell>> mergedCells = { 0: [ TableCell( child: Text('A1'), rowSpan: 2, columnSpan: 2, ), null, TableCell( child: Text('C1'), rowSpan: 2, ), ], }; ``` 2. 根据表格数据和合并单元格的规则构建表格控件。 ```dart Table( children: tableData.asMap().entries.map((row) { int rowIndex = row.key; List<String> rowData = row.value; List<TableCell> cells = rowData.asMap().entries.map((cell) { int columnIndex = cell.key; String cellData = cell.value; if (mergedCells.containsKey(rowIndex) && mergedCells[rowIndex][columnIndex] != null) { // 如果当前单元格需要合并,则返回合并单元格的配置 return mergedCells[rowIndex][columnIndex]; } else { // 否则返回普通单元格 return TableCell( child: Text(cellData), ); } }).toList(); return TableRow( children: cells, ); }).toList(), ) ``` 3. 运行程序,即可看到合并单元格表格效果。 完整代码如下: ```dart import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { List<List<String>> tableData = [ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ['A3', 'B3', 'C3'], ['A4', 'B4', 'C4'], ]; Map<int, List<TableCell>> mergedCells = { 0: [ TableCell( child: Text('A1'), rowSpan: 2, columnSpan: 2, ), null, TableCell( child: Text('C1'), rowSpan: 2, ), ], }; return MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text('Table with merged cells'), ), body: Center( child: Table( children: tableData.asMap().entries.map((row) { int rowIndex = row.key; List<String> rowData = row.value; List<TableCell> cells = rowData.asMap().entries.map((cell) { int columnIndex = cell.key; String cellData = cell.value; if (mergedCells.containsKey(rowIndex) && mergedCells[rowIndex][columnIndex] != null) { // 如果当前单元格需要合并,则返回合并单元格的配置 return mergedCells[rowIndex][columnIndex]; } else { // 否则返回普通单元格 return TableCell( child: Text(cellData), ); } }).toList(); return TableRow( children: cells, ); }).toList(), ), ), ), ); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

[chao]

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值