实现的效果
一、列合并
此需求的列合并比较简单, 直接使用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合并到的行数 - 当前所在的行数 + 1return { 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>