动态合并单元格
之前有篇文章写了 el-table
通过 :span-method
方法实现合并单元格的方法, 但是当时只写了合并第一列的, 就有小伙伴询问, 如果多列合并怎么办, 刚好最近有个项目遇到了动态表格并且要合并多列单元格, 在详细的记录一下吧
合并的原理, 见之前的博客 el-table :span-method 方法动态合并单元格
背景
这次遇到的这个表格, 比之前的更加动态了, 他的表头除了前几个和后几个固定的之外, 中间的分类也是动态的, 内容和个数都不确定
要实现的就是将类别一二三和类别一合计相同的合并
具体操作
先看一下服务端下发的数据结构把 服务端为了方便我们前端知道什么时候该合并, 就下发了树形的结构
其中 appealTypes
就是对应的动态表头, item
对应的就是表格的数据
但是如果我们通过 :span-method
来合并, 我们需要就是平铺的数据
设计结构
因为这个项目是之前的项目, 新开的功能, 所以没有用之前封装的table组件, 而是直接用的 el-table
处理数据
- 先将树形图的所有path遍历出来
- 处理我们想要的平铺格式的数据
- 通过平铺的数据动态的计算需要合并列的
spanArr
也就是说我们最终想要的数据是这样的, 把树形的所有路径, 都处理成一个对象放到一个数组里, 平铺开, 我们就可以通过某个字段来计算出需要合并的个数了
具体代码
<el-table
:header-cell-style="{ background: '#EFF1F5', color: '#76829A' }"
:data="tableData"
height="100%"
:span-method="spanMethod"
border
style="width: 100%">
<el-table-column
align="center"
prop="category1"
label="类别一">
</el-table-column>
<el-table-column
align="center"
prop="category2"
label="类别二">
</el-table-column>
<el-table-column
align="center"
prop="category3"
label="类别三">
</el-table-column>
<el-table-column
align="center"
prop="category4"
label="类别四">
</el-table-column>
<el-table-column
align="center"
v-for="item in propfigData"
:prop="item.prop"
:label="item.label">
</el-table-column>
<el-table-column
align="center"
prop="totalNumber"
label="类别四合计">
</el-table-column>
<el-table-column
align="center"
prop="proportion"
label="占比">
</el-table-column>
<el-table-column
align="center"
prop="category1TotalNumber"
label="类别一合计">
</el-table-column>
</el-table>
export default {
data() {
return {
spanArrOne: [],
spanArrTwo: [],
spanArrThree: [],
propfigData: [],
tableData: [],
}
},
mounted() {
this.configData();
},
methods: {
configData() {
this.reqGetStatisticPageList();
},
reqGetStatisticPageList() {
this.$client.getStatisticPageList()
.then(res => {
const data = res.data;
// 拿到所有路径
const allPath = this.getAllPath(data.item);
// 平铺成想要的格式
const allTileList = this.getAllTileList(allPath, data.appealTypes);
// 处理动态表头
this.propfigData = data.appealTypes.map((item, idx) => {
return {
prop: `appealTypes${idx}`, // 自定义的prop
label: item,
}
})
this.tableData = [ ...allTileList ];
// 根据id值来计算合并的数量, 相邻的数据如果id一样(因为已经排好序了), 就认为合并
this.spanArrOne = this.getSpanArr(this.tableData, 'category1Id');
this.spanArrTwo = this.getSpanArr(this.tableData, 'category2Id');
this.spanArrThree = this.getSpanArr(this.tableData, 'category3Id');
})
.catch(err => {
this.$message({
showClose: true,
message: err.data.message,
type: "error",
});
})
},
getAllPath(tree) {
const paths = [];
for (let i = 0; i < tree.length; i++) {
if (tree[i].list && tree[i].list.length > 0) {
const res = this.getAllPath(tree[i].list); //如果有子节点便继续深入,直到到达叶子节点
for (let j = 0; j < res.length; j++) {
paths.push([tree[i], ...res[j]]); //子节点返回后将其返回的路径与自身拼接
}
} else {
paths.push([tree[i]]);
}
}
return paths;
},
getAllTileList(list) {
let result = [];
list.forEach((item, index) => {
let tempArr = {};
item[3].dataList.forEach((itm, idx) => {
tempArr[`appealTypes${idx}`] = itm;
})
result.push({
category1: item[0].name || '',
category1Id: item[0].id + '' || '',
category1Level: item[0].level || '',
category2: item[1].name || '',
category2Id: item[1].id + '' || '',
category2Level: item[1].level || '',
category3: item[2].name || '',
category3Id: item[2].id + '' || '',
category3Level: item[2].level || '',
category4: item[3].name || '',
category4Id: item[3].id + '' || '',
category4Level: item[3].level || '',
totalNumber: item[3].totalNumber,
proportion: this.formatNumber(item[3].proportion * 100),
category1TotalNumber: item[0].totalNumber,
...tempArr,
})
})
return result;
},
formatNumber(val) {
let result = val.toFixed(2) - 0;
if (parseInt(result % 0.1)) {
return result + '%';
} else {
return parseInt(result / 1) + '%';
}
},
// 计算 数据合并 索引
getSpanArr(data, params) {
let arr = []; // 接收重构数组
let spanArr = []; // 控制合并的数组
let pos = 0; // 设置索引
// 排序
this.groupBy(data, params).map(v => (arr = arr.concat(v)))
arr.map(res => { // 双向绑定 把源数据改为arr
data.shift();
data.push(res);
})
const redata = arr.map(v => v[params]);
redata.reduce((old, cur, i) => {
if (cur === old) {
spanArr[pos] += 1;
spanArr.push(0);
} else {
spanArr.push(1);
pos = i;
}
return cur;
}, {});
return spanArr;
},
// 根据某个字段进行排序 输出二维数组
groupBy(data, params) {
const groups = {};
data.forEach(v => {
const group = JSON.stringify(v[params]);
groups[group] = groups[group] || [];
groups[group].push(v);
})
return Object.values(groups);
},
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0 || column.property == 'category1TotalNumber') {
const _row = this.spanArrOne[rowIndex];
const _col = _row > 0 ? 1 : 0;
return {
rowspan: _row,
colspan: _col
}
}
if(columnIndex === 1) {
const _row = this.spanArrTwo[rowIndex];
const _col = _row > 0 ? 1 : 0;
return {
rowspan: _row,
colspan: _col
}
}
if(columnIndex === 2) {
const _row = this.spanArrThree[rowIndex];
const _col = _row > 0 ? 1 : 0;
return {
rowspan: _row,
colspan: _col
}
}
}
}
}
🎉🎉
不过还是建议, 尽量根据唯一的字段来合并, 比如id, 如果根据名称合并, 可能会出现不同父级下的子级名称相同, 导致出现问题, 但是用id合并一定不会出现问题的~
欢迎大家一起讨论学习😊~