实现的效果
一、列合并
此需求的列合并比较简单, 直接使用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>