前言
有时遇到一些需求,需要实现ElementUI中,el-tabled组件合并单元格的功能,稍微了解一下它的数据格式,不难可以写出比合并方法。但是在鼠标经过单元行时,会出现高亮的行与鼠标经过的行不一致的BUG。因此还需要实现@cell-mouse-enter和@cell-mouse-leave这两个方法,才可解决此问题。
一、多列合并
1.示例代码
<template>
<div class="merge-cell">
<div class="merge-cell-navbar">记录一下 el-table 合并行小技巧</div>
<div class="merge-cell-content">
<div class="merge-cell-content-container">
<el-table
:data="tableData"
border
height="100%"
:header-cell-style="
{
padding: '4px', // 设置Table表头单元内边距
backgroundColor: '#e7f0ff', // 设置Table表头背景颜色
borderColor: '#b6d1ff', // 设置Table表头边框颜色
color: '#000', // 设置Table表头文字颜色
fontSize: '13px', // 设置Table表头文字大小
fontWeight: 'normal', // 设置Table表头文字粗细
}
"
:span-method="handleSpanMethod"
:row-class-name="handleRowClassName"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<el-table-column prop="zone" label="GameZone / 服务器区域 / 游戏区域" align="center" />
<el-table-column prop="career" label="职业" width="180" align="center" />
<el-table-column label="英雄" align="center">
<el-table-column prop="hero" label="英雄姓名" width="280" align="center" />
<el-table-column prop="firstSkill" label="一技能" width="220" align="center" />
<el-table-column prop="secondSkill" label="二技能" width="220" align="center" />
<el-table-column prop="thirdSkill" label="三技能" width="220" align="center" />
<el-table-column label="操作" width="180" align="center">
<template #default="scope">
<el-tooltip effect="dark" content="收藏" placement="top" :enterable="false" :hide-after="0" @click="handleEditClick(scope.$index, scope.row)">
<el-button size="small" circle>
<el-icon size="18"><StarFilled /></el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
tableData: [
{
zone: "王者一区",
career: "坦克",
hero: "亚瑟",
firstSkill: "誓约之盾",
secondSkill: "回旋打击",
thirdSkill: "圣剑裁决",
},
{
zone: "王者一区",
career: "坦克",
hero: "吕布",
firstSkill: "方天画斩",
secondSkill: "贪狼之握",
thirdSkill: "魔神降世",
},
{
zone: "王者一区",
career: "坦克",
hero: "项羽",
firstSkill: "无畏冲锋",
secondSkill: "破釜沉舟",
thirdSkill: "霸王斩杀",
},
{
zone: "王者一区",
career: "战士",
hero: "云缨",
firstSkill: "断月",
secondSkill: "追云",
thirdSkill: "逐星",
},
{
zone: "王者一区",
career: "战士",
hero: "赵怀真",
firstSkill: "拨云见明",
secondSkill: "气定神凝",
thirdSkill: "阴阳逆转",
},
{
zone: "王者二区",
career: "刺客",
hero: "镜",
firstSkill: "开锋",
secondSkill: "裂空",
thirdSkill: "见影",
},
{
zone: "王者二区",
career: "刺客",
hero: "澜",
firstSkill: "破浪",
secondSkill: "断空",
thirdSkill: "处决",
},
{
zone: "王者二区",
career: "刺客",
hero: "李白",
firstSkill: "将进酒",
secondSkill: "神来之笔",
thirdSkill: "青莲剑歌",
},
{
zone: "王者三区",
career: "法师",
hero: "妲己",
firstSkill: "灵魂冲击",
secondSkill: "偶像魅力",
thirdSkill: "女王崇拜",
},
{
zone: "王者三区",
career: "射手",
hero: "后羿",
firstSkill: "多重箭矢",
secondSkill: "落日余晖",
thirdSkill: "灼日之矢",
},
{
zone: "王者三区",
career: "射手",
hero: "鲁班7号",
firstSkill: "河豚手雷",
secondSkill: "无敌鲨嘴炮",
thirdSkill: "空中支援",
},
{
zone: "王者三区",
career: "辅助",
hero: "孙膑",
firstSkill: "时空爆弹",
secondSkill: "时之波动",
thirdSkill: "时光流逝",
},
{
zone: "王者三区",
career: "辅助",
hero: "庄周",
firstSkill: "化蝶",
secondSkill: "蝴蝶效应",
thirdSkill: "天人合一",
},
],
first_row: {
},
second_row: {
},
}),
methods: {
/**
* 合并单元格句柄方法
*/
handleSpanMethod({
row, // 行
column, // 列
rowIndex, // 行索引
columnIndex, // 列索引
}) {
if (columnIndex === 0 || columnIndex === 1) {
// 获取当前单元格的值
const currentValue = row[column.property];
// 获取上一行相同列的值
const preRow = this.tableData[rowIndex - 1];
const preValue = preRow ? preRow[column.property] : null;
// 如果当前值和上一行的值相同,则将当前单元格隐藏
if (currentValue === preValue) {
return {
rowspan: 0, colspan: 0 };
} else {
// 否则计算当前单元格应该跨越多少行
let rowspan = 1;
for (let i = rowIndex + 1; i < this.tableData.length; i++) {
const nextRow = this.tableData[i];
const nextValue = nextRow[column.property];
if (nextValue === currentValue) {
rowspan++;
} else {
break;
}
}
return {
rowspan, colspan: 1 };
}
}
},
/**
* 鼠标移入表格事件句柄方法
*/
handleCellMouseEnter(row, column, cell, event) {
this.second_row = this.tableData.filter((item) => {
return this.filterSameKeys(item, row, ["zone", "career"]);
})[0];
this.first_row = this.tableData.filter((item) => {
return this.filterSameKeys(item, row, ["zone"]);
})[0];
},
/**
* 鼠标移出表格事件句柄方法
*/
handleCellMouseLeave() {
this.currentIndex = "";
this.currentColumnIndex = "";
this.first_row = {
};
this.second_row = {
};
},
/**
* 根据 keys 数组所有字段去做合并
*/
filterSameKeys(item, row, keys) {
let flag = true;
keys.forEach((key) => {
flag = flag && item[key] === row[key];
});
return flag;
},
/**
* 给表格行添加自定义类名
*/
handleRowClassName({
row }) {
let flag1 = this.first_row == row ? "first_row" : "";
let flag2 = this.second_row == row ? "second_row" : "";
return `${
flag1} ${
flag2}`;
},
},
};
</script>
<style lang="less" scoped>
.merge-cell {
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.merge-cell-navbar {
position: relative;
width: 100%;
height: 100px;
background-color: #686868;
text-align: center;
line-height: 100px;
color: #fff;
font-size: 25px;
}
.merge-cell-content {
position: relative;
flex: 1;
padding: 100px;
overflow: hidden;
.merge-cell-content-container {
position: relative;
width: 100%;
height: 100%;
overflow: auto;
}
}
:deep(.el-table) {
td .cell {
padding: 2.5px 0;
color: #686868;
font-size: 13px;
}
.first_row td:nth-child(1),
.second_row td:nth-child(1),
.first_row.second_row td:nth-child(2) {
background: #f5f7fa !important;
}
}
/* ^ 设置Table表格边框颜色 */
:deep(.el-table--border) {
&::before {
background-color: #b6d1ff;
}
&::after {
background-color: #b6d1ff;
}
.el-table__cell {
border-color: #b6d1ff;
}
.el-table__inner-wrapper {
&::before {
background-color: #b6d1ff;
}
&::after {
background-color: #b6d1ff;
}
.el-table__border-left-patch {
background-color: #b6d1ff;
}
}
}
/* / 设置Table表格边框颜色 */
}
</style>
2.运行效果
二、单列合并
1.示例代码
<template>
<div class="merge-cell">
<div class="merge-cell-navbar">记录一下 el-table 合并行小技巧</div>
<div class="merge-cell-content">
<div class="merge-cell-content-container">
<el-table
:data="tableData"
border
height="100%"
:header-cell-style="
{
padding: '4px', // 设置Table表头单元内边距
backgroundColor: '#e7f0ff', // 设置Table表头背景颜色
borderColor: '#b6d1ff', // 设置Table表头边框颜色
color: '#000', // 设置Table表头文字颜色
fontSize: '13px', // 设置Table表头文字大小
fontWeight: 'normal', // 设置Table表头文字粗细
}
"
:span-method="handleSpanMethod"
>
<el-table-column prop="zone" label="GameZone / 服务器区域 / 游戏区域" align="center" />
<el-table-column prop="career" label="职业" width="180" align="center" />
<el-table-column label="英雄" align="center">
<el-table-column prop="hero" label="英雄姓名" width="280" align="center" />
<el-table-column prop="firstSkill" label="一技能" width="220" align="center" />
<el-table-column prop="secondSkill" label="二技能" width="220" align="center" />
<el-table-column prop="thirdSkill" label="三技能" width="220" align="center" />
<el-table-column label="操作" width="180" align="center">
<template #default="scope">
<el-tooltip effect="dark" content="收藏" placement="top" :enterable="false" :hide-after="0" @click="handleEditClick(scope.$index, scope.row)">
<el-button size="small" circle>
<el-icon size="18"><StarFilled /></el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
tableData: [
{
zone: "王者一区",
career: "坦克",
hero: "亚瑟",
firstSkill: "誓约之盾",
secondSkill: "回旋打击",
thirdSkill: "圣剑裁决",
},
{
zone: "王者一区",
career: "坦克",
hero: "吕布",
firstSkill: "方天画斩",
secondSkill: "贪狼之握",
thirdSkill: "魔神降世",
},
{
zone: "王者一区",
career: "坦克",
hero: "项羽",
firstSkill: "无畏冲锋",
secondSkill: "破釜沉舟",
thirdSkill: "霸王斩杀",
},
{
zone: "王者一区",
career: "战士",
hero: "云缨",
firstSkill: "断月",
secondSkill: "追云",
thirdSkill: "逐星",
},
{
zone: "王者一区",
career: "战士",
hero: "赵怀真",
firstSkill: "拨云见明",
secondSkill: "气定神凝",
thirdSkill: "阴阳逆转",
},
{
zone: "王者二区",
career: "刺客",
hero: "镜",
firstSkill: "开锋",
secondSkill: "裂空",
thirdSkill: "见影",
},
{
zone: "王者二区",
career: "刺客",
hero: "澜",
firstSkill: "破浪",
secondSkill: "断空",
thirdSkill: "处决",
},
{
zone: "王者二区",
career: "刺客",
hero: "李白",
firstSkill: "将进酒",
secondSkill: "神来之笔",
thirdSkill: "青莲剑歌",
},
{
zone: "王者三区",
career: "法师",
hero: "妲己",
firstSkill: "灵魂冲击",
secondSkill: "偶像魅力",
thirdSkill: "女王崇拜",
},
{
zone: "王者三区",
career: "射手",
hero: "后羿",
firstSkill: "多重箭矢",
secondSkill: "落日余晖",
thirdSkill: "灼日之矢",
},
{
zone: "王者三区",
career: "射手",
hero: "鲁班7号",
firstSkill: "河豚手雷",
secondSkill: "无敌鲨嘴炮",
thirdSkill: "空中支援",
},
{
zone: "王者三区",
career: "辅助",
hero: "孙膑",
firstSkill: "时空爆弹",
secondSkill: "时之波动",
thirdSkill: "时光流逝",
},
{
zone: "王者三区",
career: "辅助",
hero: "庄周",
firstSkill: "化蝶",
secondSkill: "蝴蝶效应",
thirdSkill: "天人合一",
},
],
first_row: {
},
second_row: {
},
}),
methods: {
/**
* 合并单元格句柄方法
*/
handleSpanMethod({
row, // 行
column, // 列
rowIndex, // 行索引
columnIndex, // 列索引
}) {
const rowspanArr = this.formatRowspanAndColspan(this.tableData, 'zone')
if (columnIndex === 0) {
// console.log('row =>', row)
// console.log('column =>', column)
// console.log('rowIndex =>', rowIndex)
// console.log('columnIndex =>', columnIndex)
// console.log('\n')
return {
rowspan: rowspanArr[rowIndex],
colspan: 1
}
}
},
/**
* 合并单元格辅助
*/
formatRowspanAndColspan(tableList, keyName) {
const keyNameList = []
tableList.forEach(
t => {
keyNameList.push(t[keyName])}
)
// console.log('keyNameList =>', keyNameList)
let prev // 上一个键名的索引
let contin = 0 // 连续相同键名个数
const computedList = [] // 计算后的键名列表
for (let i = 0; i < keyNameList.length; i++) {
if (computedList.length === 0) {
computedList.push({
'key': keyNameList[i], 'val': 1 })
} else {
if (keyNameList[prev] === keyNameList[i]) {
contin++
computedList.push({
'key': keyNameList[i], 'val': 0 })
} else {
if (contin > 0) {
const index = computedList.length - 1 - contin
const key = computedList[index].key
const val = computedList[index].val
const obj = {
'key': key, 'val': val + contin}
computedList.splice(index, 1, obj)
}
computedList.push({
'key': keyNameList[i], 'val': 1 })
contin = 0
}
}
prev = i // 将 i 赋值给 prev,以便当下一次循环时获取上一个键值的索引
}
if (contin > 0) {
const index = computedList.length - 1 - contin
const key = computedList[index].key
const val = computedList[index].val
const obj = {
'key': key, 'val': val + contin}
computedList.splice(index, 1, obj)
}
// console.log('computedList =>', computedList)
const finalList = []
computedList.forEach(
t => {
finalList.push(t.val)}
)
// console.log('finalList =>', finalList)
return finalList
},
},
};
</script>
<style lang="less" scoped>
.merge-cell {
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.merge-cell-navbar {
position: relative;
width: 100%;
height: 100px;
background-color: #686868;
text-align: center;
line-height: 100px;
color: #fff;
font-size: 25px;
}
.merge-cell-content {
position: relative;
flex: 1;
padding: 100px;
overflow: hidden;
.merge-cell-content-container {
position: relative;
width: 100%;
height: 100%;
overflow: auto;
}
}
:deep(.el-table) {
td .cell {
padding: 2.5px 0;
color: #686868;
font-size: 13px;
}
.first_row td:nth-child(1),
.second_row td:nth-child(1),
.first_row.second_row td:nth-child(2) {
background: #f5f7fa !important;
}
}
/* ^ 设置Table表格边框颜色 */
:deep(.el-table--border) {
&::before {
background-color: #b6d1ff;
}
&::after {
background-color: #b6d1ff;
}
.el-table__cell {
border-color: #b6d1ff;
}
.el-table__inner-wrapper {
&::before {
background-color: #b6d1ff;
}
&::after {
background-color: #b6d1ff;
}
.el-table__border-left-patch {
background-color: #b6d1ff;
}
}
}
/* / 设置Table表格边框颜色 */
}
</style>