使用elementUI中的el-table合并表格

一、业务场景说明

开发中遇到一个合并表格的需求,如下图

image-20250110145858077

该表格表头是固定的,末尾 3 行需要将除最后一列的其他列进行合并。中间区域则需要看情况合并行:每个指标分类下面可能有多条指标名称,对于这种具有多条指标名称的数据,其指标分类和权重/得分需要合并行,这也是该需求的难点。

二、具体实现

来看后端返回的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 41,
"indexType": 126,
"indexTypeName": "服务响应性",
"indexId": 129,
"indexName": "服务投诉解决率",
"mode": 1,
"weightPercent": 100.0,
"score": 100.0,
"weightScore": 100.0,
"instId": 27,
"kpiInstId": 832,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100.0,
"secondLevelWeightPercent": 50.0,
"secondLevelWeightScore": 50.0
},
{
"id": 42,
"indexType": 157,
"indexTypeName": "服务可靠性",
"indexId": 174,
"indexName": "运维人员流失率",
"mode": 1,
"weightPercent": 100.0,
"score": 100.0,
"weightScore": 100.0,
"instId": 27,
"kpiInstId": 833,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100.0,
"secondLevelWeightPercent": 50.0,
"secondLevelWeightScore": 50.0
},
{
"id": 43,
"indexType": 157,
"indexTypeName": "服务可靠性",
"indexId": 158,
"indexName": "服务的可用性",
"mode": 1,
"weightPercent": 100.0,
"score": 100.0,
"weightScore": 100.0,
"instId": 27,
"kpiInstId": 833,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100.0,
"secondLevelWeightPercent": 50.0,
"secondLevelWeightScore": 50.0
}
],
"map": {}
}

其中 firstIndexName 表示每个表格总的类型。就是说该数据返回的可能是多个表格的数据,每个表格为一个数组。indexTypeName 对应指标分类,indexName 对应指标名称。可以看到后端返回的是完全独立的数据,我们需要自己先处理下,先按 firstIndexName 将同属于一个表格的数据归纳到一起。再将同属于一个指标分类的数据归并到一起

组件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<template>
<div class="container">
<div class="body">
<el-tabs v-model="activeName" style="width:100%">
<el-tab-pane
v-for="(item, index) in tabDataList"
:key="index"
:label="item.title"
:name="item.title"
>
<el-table
ref="detailTable"
:data="item.dataList"
:max-height="maxHeight"
:span-method="arraySpanMethod"
border
:header-cell-style="{
backgroundColor: '#EFF0F3',
color: '#8c8c8c',
fontSize: '12px',
}"
>
<el-table-column
label="指标分类"
prop="indexTypeName"
align="center"
>
</el-table-column>
<el-table-column label="权重/得分" align="center">
<template slot-scope="scope">
<span
>{{
scope.row.secondLevelWeightPercent
? scope.row.secondLevelWeightPercent + '%'
: ''
}}{{
scope.row.secondLevelWeightScore
? '(' + scope.row.secondLevelWeightScore + ')'
: ''
}}</span
>
</template>
</el-table-column>
<el-table-column label="指标名称" prop="indexName" align="center">
</el-table-column>
<el-table-column label="评估方式" prop="mode" align="center">
</el-table-column>
<el-table-column
label="评估数据来源"
prop="dataResource"
align="center"
>
</el-table-column>
<el-table-column label="权重" prop="weightPercent" align="center">
</el-table-column>
<el-table-column label="分数" prop="score" align="center">
</el-table-column>
<el-table-column label="权重得分" prop="weightScore" align="center">
<template slot-scope="scope">
<span v-if="scope.row.indexTypeName === '评级结果'"
><i
v-for="i in scope.row.weightScore"
:key="i"
class="el-icon-star-off"
:size="20"
style="color:#fcd53f"
>
</i
></span>
<span v-else>{{ scope.row.weightScore }}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
<el-icon><Star /></el-icon>
</div>
</template>

<style scoped>
.container {
padding: 0 16px;
}
.header {
height: 50px;
display: flex;
justify-content: flex-start;
align-items: center;
}
.body {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: calc(100% - 66px);
}
.action-item:hover {
color: #1670f0;
cursor: pointer;
}
::v-deep .el-table__empty-text {
line-height: 38px !important;
}
::v-deep .el-table__empty-block {
height: 100%;
}
::v-deep .el-table__body-wrapper {
height: 100%;
}
::v-deep .el-table {
overflow: visible;
}
</style>

由于返回的是多个表格,所以使用 el-tabs 在多个表格之间切换

具体逻辑实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<script>
export default {
data() {
return {
tabDataList: [],
activeName: '',
}
},
created() {
this.getDetailList()
},
methods: {
getDetailList() {
this.$api
.getList()
.then((res) => {
if (!res.data.length) {
this.tabDataList.push({
title: '无数据',
dataList: [],
})
return
}
// 对数据进行处理
//按firstIndexName进行分类,firstIndexName相同的在一个tab表格
res.data.forEach((item) => {
item.rowSpan = 1
if (!this.tabDataList.length) {
this.tabDataList.push({
title: item.firstIndexName,
dataList: [item],
id: item.id,
})
}
this.tabDataList.forEach((ele) => {
if (ele.id !== item.id) {
if (ele.title === item.firstIndexName) {
// 这里再次判断一下indexTypeName是否存在,存在则添加一个合并行的标记,方便后面做合并
const isMerge = ele.dataList.find(
(i) => i.indexTypeName === item.indexTypeName
)
if (isMerge) {
isMerge.rowSpan++
item.rowSpan = 0
}
ele.dataList.push(item)
} else {
this.tabDataList.push({
title: item.firstIndexName,
dataList: [item],
id: item.id,
})
}
}
})
})
this.activeName = this.tabDataList[0].title
// 获取评等级规则
this.$api.getAllScoreRule().then((res) => {
this.tabDataList.forEach((item) => {
const totalScore = item.dataList.reduce(
(pre, cur) => pre + cur.secondLevelWeightScore,
0
)
// 根据等级规则计算等级
const level = res.data.find(
(i) => i.geValue <= totalScore && i.ltValue >= totalScore
)
item.totalScore = totalScore
item.level = level ? level.scoreLevel : ''
item.star = level ? level.star : 0
item.dataList.push(
{ indexTypeName: '总得分', weightScore: totalScore },
{ indexTypeName: '评级', weightScore: item.level },
{ indexTypeName: '评级结果', weightScore: item.star }
)
})
})
// 再次处理数据,方便后面合并
})
.catch((err) => {
this.$message.error(err.message)
})
},
arraySpanMethod({ row, column, rowIndex, columnIndex }) {
/** 注意使用该方法合并行列时,除了指明要合并的行列、合并的行列数外,还需要显式的指明被合并的行列(通过return {
*rowspan: 0,
*colspan: 0
*}方式)
*/
if (
row.indexTypeName === '总得分' ||
row.indexTypeName === '评级' ||
row.indexTypeName === '评级结果'
) {
if (columnIndex === 0) {
return {
rowspan: 1,
colspan: 7,
}
} else if (columnIndex !== 0 && columnIndex !== 7) {
return {
rowspan: 0,
colspan: 0,
}
}
} else {
if (columnIndex === 0 || columnIndex === 1) {
if (row.rowSpan > 1) {
return {
rowspan: row.rowSpan,
colspan: 1,
}
} else if (row.rowSpan === 0) {
return {
rowspan: 0,
colspan: 0,
}
}
}
}
},
},
}
</script>

总得分需要根据分项得分乘以权重计算得出。再根据总得分匹配对应的评级。最终得到一个总的 list,它包含的每一项都是一个 el-tab-pane 的表格,表格的名字为 title,表格的数据为一个 list,对应的字段名为 dataList。每一个表格数据会比原始数据多处 3 条(末尾合并列的三行),这 3 条数据的值都是经过处理的。

中间合并行区域的关键点在于这里

image-20250110162932889

找到当前这条数据是否跟之前的数据有相同的 indexTypeName(对应指标分类),有的话,那么这条数据就应该被合并,所以这时候通过 find 方法找到第一条同一个 indexTypeName 的数据,将其合并标记+1,同时当前这条数据合并标记为 0 ,表示它将会被合并。

最终处理过后的数据如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
[
{
"title": "政务云服务质量",
"dataList": [
{
"id": 41,
"indexType": 126,
"indexTypeName": "服务响应性",
"indexId": 129,
"indexName": "服务投诉解决率",
"mode": 1,
"weightPercent": 100,
"score": 100,
"weightScore": 100,
"instId": 27,
"kpiInstId": 832,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100,
"secondLevelWeightPercent": 50,
"secondLevelWeightScore": 50,
"rowSpan": 1
},
{
"id": 42,
"indexType": 157,
"indexTypeName": "服务可靠性",
"indexId": 174,
"indexName": "运维人员流失率",
"mode": 1,
"weightPercent": 100,
"score": 100,
"weightScore": 100,
"instId": 27,
"kpiInstId": 833,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100,
"secondLevelWeightPercent": 50,
"secondLevelWeightScore": 50,
"rowSpan": 2
},
{
"id": 43,
"indexType": 157,
"indexTypeName": "服务可靠性",
"indexId": 158,
"indexName": "服务的可用性",
"mode": 1,
"weightPercent": 100,
"score": 100,
"weightScore": 100,
"instId": 27,
"kpiInstId": 833,
"firstIndex": 238,
"firstIndexName": "政务云服务质量",
"dataResource": null,
"secondLevelScore": 100,
"secondLevelWeightPercent": 50,
"secondLevelWeightScore": 50,
"rowSpan": 0
},
{
"indexTypeName": "总得分",
"weightScore": 150
},
{
"indexTypeName": "评级",
"weightScore": "A"
},
{
"indexTypeName": "评级结果",
"weightScore": 5
}
],
"id": 41,
"totalScore": 150,
"level": "A",
"star": 5
}
]
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 congtianfeng
  • 访问人数: | 浏览次数: