element-ui中的el-table合并单元格

描述:

在写项目的时候有时候会经常遇到把行和列合并起来的情况,因为有些数据是重复渲染的,不合并行列会使表格看起来非常的混乱,如下:

而我们想要的数据是下面这种情况,将重复的行进行合并,使表格看起来简单明了,如下:


解决方案:

一:合并行

1:html部分

所谓的合并行就是将多行相同的数据变成一行来显示,页面的布局比较简单

html 复制代码
<template>
    <div class="table">
        <el-table 
            :data="tableData" 
            :span-method="objectSpanMethod" 
            border 
            style="width: 100%">
            <el-table-column prop="time" label="时间"></el-table-column>
            <el-table-column prop="grade" label="年级"></el-table-column>
            <el-table-column prop="name" label="姓名"></el-table-column>
            <el-table-column prop="subjects" label="科目"></el-table-column>
            <el-table-column prop="score" label="成绩"></el-table-column>
        </el-table>
    </div>
</template>

2:模拟data数据

span-methodel-table上属性,其值是一个函数,objectSpanMethod方法是用来处理合并行的返回值,tableData数据如下

javascript 复制代码
tableData: [
 
  {time:'2020-08-10',grade:'三年二班',name: '小明',subjects:'语文',score: 80 },
 
  {time:'2020-08-10',grade:'三年二班',name: '小明',subjects:'数学',score: 80 },
 
  {time:'2020-08-10',grade:'三年一班',name: '小雷',subjects:'语文',score: 70 },
 
  {time:'2020-08-10',grade:'三年一班',name: '小雷',subjects:'数学',score: 80 },
 
  {time:'2020-08-11',grade:'三年三班',name: '小花',subjects:'语文',score: 60 },
 
  {time:'2020-08-11',grade:'三年三班',name: '小花',subjects:'数学',score: 60 },
 
],
 
mergeObj: {}, // 用来记录需要合并行的下标
 
tableProps: ['time', 'grade', 'name', 'subjects', 'score'] // 表格中的列名

3:梳理数据以及方法调用

首先需要对数据就行处理,就是比较当前行与上一行的值是否相等(如果是第一行数据,直接将值赋值为1)

watch中监听表格中的数据,当不为空的的时候,调用数据初始化数据的方法,如下:

javascript 复制代码
watch:{
    "tableData":function (newVal,oldVal){
      console.log("nnnnnnnnnnnn",newVal)
      console.log("oooooooooooo",oldVal)
      if(newVal.length>0){
        this.getSpanArr(this.tableData);
      }
    }
  },
javascript 复制代码
// getSpanArr方法
getSpanArr(data) {
    this.tableProps.forEach((key, index1) => {
        let count = 0; // 用来记录需要合并行的起始位置
        this.mergeObj[key] = []; // 记录每一列的合并信息
        data.forEach((item, index) => {
            // index == 0表示数据为第一行,直接 push 一个 1
            if(index === 0) {
                this.mergeObj[key].push(1); 
            } else {
                /*判断当前行是否与上一行其值相等 
                  如果相等 在 count 记录的位置其值 +1
                  表示当前行需要合并 并push 一个 0 作为占位
                */  
                if(item[key] === data[index - 1][key]) { 
                    this.mergeObj[key][count] += 1;
                    this.mergeObj[key].push(0);
                } else {
                    // 如果当前行和上一行其值不相等 
                    count = index; // 记录当前位置 
                    this.mergeObj[key].push(1); // 重新push 一个 1
                }
            }
        })
    })
}

数据处理好之后就可以调用objectSpanMethod方法了,如下:

javascript 复制代码
// objectSpanMethod方法
    /*默认接受四个值
        ----row==当前行的数据
        ----column==当前列的数据
        ----rowIndex==行的下标
        ----columnIndex==列的下标
    */
// 默认接受四个值 { 当前行的值, 当前列的值, 行的下标, 列的下标 }
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
    // 判断列的属性
    if(this.tableProps.indexOf(column.property) !== -1) { 
        // 判断其值是不是为0 
        if(this.mergeObj[column.property][rowIndex]) { 
            return [this.mergeObj[column.property][rowIndex], 1]
        } else {
            // 如果为0则为需要合并的行
            return [0, 0]; 
        }
        // 只有 第一列 第二列 第三列 合并行
        // if(columnIndex===1||columnIndex===2||columnIndex===3){
        //   // 判断列的属性
        //   if(this.tableProps.indexOf(column.property) !== -1) {
        //     // 判断其值是不是为0 
        //     if(this.mergeObj[column.property][rowIndex]) { 
        //       return {
        //         rowspan: this.mergeObj[column.property][rowIndex],
        //         colspan: 1
        //       };
        //     } else {
        //       // 如果为0则为需要合并的行
        //       return {
        //         rowspan: 0,
        //         colspan: 0
        //       }; 
        //     }
        //   }
        // }
    }
}

4:效果图

合并后的结果就是我们想要的形式:

5:合并行的完整代码

javascript 复制代码
<template>
    <div class="table">
        <el-table 
            :data="tableData" 
            :span-method="objectSpanMethod" 
            border style="width: 100%">
            <el-table-column prop="time" label="时间"></el-table-column>
            <el-table-column prop="grade" label="年级"></el-table-column>
            <el-table-column prop="name" label="姓名"></el-table-column>
            <el-table-column prop="subjects" label="科目"></el-table-column>
            <el-table-column prop="score" label="成绩"></el-table-column>
        </el-table>
    </div>
</template>
 
<script>
export default {
    name: 'Table',
    data() {
        return {
            tableData: [
                { 
                    time: '2020-08-10', grade: '三年二班', 
                    name: '小明', subjects: '语文', score: 80 
                },
                { 
                    time: '2020-08-10', grade: '三年二班',
                    name: '小明', subjects: '数学', score: 80 
                },
                { 
                    time: '2020-08-10', grade: '三年一班',
                    name: '小雷', subjects: '语文', score: 70 
                },
                { 
                    time: '2020-08-10', grade: '三年一班', 
                    name: '小雷', subjects: '数学', score: 80 
                },
                { 
                    time: '2020-08-11', grade: '三年三班', 
                    name: '小花', subjects: '语文', score: 60 
                }, 
                { 
                    time: '2020-08-11', grade: '三年三班', 
                    name: '小花', subjects: '数学', score: 60 
                }, 
 
            ],
            mergeObj: {},
            mergeArr: ['time', 'grade', 'name', 'subjects', 'score'],
        };
    },
    watch:{
      "tableData":function (newVal,oldVal){
        console.log("nnnnnnnnnnnn",newVal)
        console.log("oooooooooooo",oldVal)
        if(newVal.length>0){
          this.getSpanArr(this.tableData);
        }
      }
    },
    methods: {
        getSpanArr(data) {
            this.mergeArr.forEach((key, index1) => {
                let count = 0; // 用来记录需要合并行的起始位置
                this.mergeObj[key] = []; // 记录每一列的合并信息
                data.forEach((item, index) => {
                    // index == 0表示数据为第一行,直接 push 一个 1
                    if(index === 0) {
                        this.mergeObj[key].push(1); 
                    } else {
                        /*判断当前行是否与上一行其值相等
                          如果相等 在 count 记录的位置其值 +1
                          表示当前行需要合并 并push 一个 0 作为占位 
                        */   
                        if(item[key] === data[index - 1][key]) { 
                            this.mergeObj[key][count] += 1;
                            this.mergeObj[key].push(0);
                        } else {
                            // 如果当前行和上一行其值不相等 
                            count = index; // 记录当前位置 
                            this.mergeObj[key].push(1); // 重新push 一个 1
                        }
                    }
                })
            })
        },
        // 默认接受四个值 { 当前行的值, 当前列的值, 行的下标, 列的下标 }
        objectSpanMethod({ row, column, rowIndex, columnIndex }) {
            // 判断列的属性
            if(this.mergeArr.indexOf(column.property) !== -1) { 
                // 判断其值是不是为0 
                if(this.mergeObj[column.property][rowIndex]) { 
                    return [this.mergeObj[column.property][rowIndex], 1]
                } else {
                    // 如果为0则为需要合并的行
                    return [0, 0]; 
                }
            }
        }
    },
    
};
</script>
 
<style lang="stylus" scoped>
.table 
    height 100vh
    width 100%
    padding 40px
    box-sizing border-box
    /deep/ .el-table__body tr:hover > td
        background-color: #fff;
</style>

二:合并行列

1:模拟data数据

span-methodel-table上属性,其值是一个函数,objectSpanMethod方法是用来处理合并行的返回值,tableData数据如下

javascript 复制代码
tableData: [
    { 
        time: '2020-08-10', grade: '三年二班', 
        name: '小明', subjects: '语文', score: 80 
    },
    { 
        time: '2020-08-10', grade: '三年二班', 
        name: '小明', subjects: '数学', score: 80 
    }, 
    { 
        time: '2020-08-10', grade: '总成绩', 
        name: '总成绩', subjects: '总成绩', score: 160 
    },
    { 
        time: '2020-08-10', grade: '三年一班', 
        name: '小雷', subjects: '语文', score: 70 
    },
    { 
        time: '2020-08-10', grade: '三年一班', 
        name: '小雷', subjects: '数学', score: 80 
    },
    { 
        time: '2020-08-10', grade: '总成绩', 
        name: '总成绩', subjects: '总成绩', score: 150 
    }, 
    { 
        time: '2020-08-11', grade: '三年三班', 
        name: '小花', subjects: '语文', score: 60 
    }, 
    { 
        time: '2020-08-11', grade: '三年三班', 
        name: '小花', subjects: '数学', score: 60 
    }, 
    { 
        time: '2020-08-11', grade: '总成绩', 
        name: '总成绩', subjects: '总成绩', score: 120 
    }
],

2:对比当一次的图片

可以看到上面的数据多了一行总成绩,现在的数据在页面显示效果如下:

3:html调整

可以看到总成绩的三个列并没有合并,并不是我们想要的效果,所以需要换一种思路来处理数据

页面的布局也有所调整,如下:

javascript 复制代码
<template>
    <div class="table">
        <el-table 
            :data="tableData" 
            :span-method="objectSpanMethods" 
            border style="width: 100%">
            <template v-for="cols in colConfigs">
                <!-- 无需合并的列 -->
                <el-table-column
                    v-if="cols.type === 'label' && !cols.children"
                    :key="cols.prop"
                    :prop="cols.prop"
                    :label="cols.label"
                >
                </el-table-column>
                <!-- 需要合并的列 -->
                <template v-else-if="cols.type === 'label' && cols.children">
                    <el-table-column
                        v-for="children in cols.children"
                        :key="children.prop"
                        :prop="children.prop"
                        :label="children.label"
                    />
                </template>
            </template>
        </el-table>
    </div>
</template>

4:在data中声明的变量

javascript 复制代码
// 表格的信息 需要合并的需要放在 children 中
colConfigs: [
    {
        type: 'label',
        children: [
            { prop: 'time', label: '时间' },
            { prop: 'grade', label: '年级' },
            { prop: 'name', label: '姓名' },
            { prop: 'subjects', label: '科目' },
            { prop: 'score', label: '成绩' }
        ]
    }
],
// 需要合并的行列信息
mergeCols: [
    { index: 0, name: 'time' },
    { index: 1, name: 'grade' },
    { index: 2, name: 'name' },
    { index: 3, name: 'subjects' },
    { index: 4, name: 'score' },
],
// 用来记录每一个单元格的下标
tableMergeIndex: [],

5:梳理数据以及方法调用

javascript 复制代码
watch:{
    "tableData":function (newVal,oldVal){
      console.log("nnnnnnnnnnnn",newVal)
      console.log("oooooooooooo",oldVal)
      if(this.mergeCols.length > 0) {
        this.newTableMergeData();
      }
    }
  },
javascript 复制代码
// newTableMergeData方法
newTableMergeData() {
    for (let i = 0; i < this.tableData.length; i++) {
        for (let j = 0; j < this.mergeCols.length; j++) {
            // 初始化行、列坐标信息
            let rowIndex = 1;
            let columnIndex = 1;
            // 比较横坐标左方的第一个元素
            if (j > 0 && this.tableData[i][this.mergeCols[j]['name']] 
                === this.tableData[i][this.mergeCols[j - 1]['name']]) {
                columnIndex = 0;
            }
            // 比较纵坐标上方的第一个元素
            if (i > 0 && this.tableData[i][this.mergeCols[j]['name']] 
                === this.tableData[i - 1][this.mergeCols[j]['name']]) {
                rowIndex = 0;
            }
            // 比较横坐标右方元素
            if (columnIndex > 0) {
                columnIndex = this.onColIndex(
                    this.tableData[i], j, j + 1, 1, this.mergeCols.length
                );
            }
            // 比较纵坐标下方元素
            if (rowIndex > 0) {
                rowIndex = this.onRowIndex(
                    this.tableData, i, i + 1, 1, this.mergeCols[j]['name']
                );
            }
            let key = this.mergeCols[j]['index'] + '_' + i;
            this.tableMergeIndex[key] = [rowIndex, columnIndex];
        }
    }
},
/**
  * 计算列坐标信息
  * data 单元格所在行数据
  * index 当前下标
  * nextIndex 下一个元素坐标
  * count 相同内容的数量
  * maxLength 当前行的列总数
  */
onColIndex(data, index, nextIndex, count, maxLength) {
    // 比较当前单元格中的数据与同一行之后的单元格是否相同
    if (nextIndex < maxLength && data[this.mergeCols[index]['name']] 
        === data[this.mergeCols[nextIndex]['name']]) {
        return this.onColIndex(data, index, ++nextIndex, ++count, maxLength);
    }
    return count;
},
/**
  * 计算行坐标信息
  * data 表格总数据
  * index 当前下标
  * nextIndex 下一个元素坐标
  * count 相同内容的数量
  * name 数据的key
  */
onRowIndex(data, index, nextIndex, count, name) {
    // 比较当前单元格中的数据与同一列之后的单元格是否相同
    if (nextIndex < data.length && data[index][name] 
        === data[nextIndex][name]) {
        return this.onRowIndex(data, index, ++nextIndex, ++count, name);
    }
    return count;
}

数据处理好之后就可以调用objectSpanMethods方法了,如下:

javascript 复制代码
objectSpanMethods({ row, column, rowIndex, columnIndex }) {
    let key = columnIndex + '_' + rowIndex;
    if (this.tableMergeIndex[key]) {
        return this.tableMergeIndex[key];
    }
}

6:效果图

7:合并行列的完整代

javascript 复制代码
<template>
    <div class="table">
        <el-table 
            :data="tableData" 
            :span-method="objectSpanMethods" 
            border style="width: 100%">
            <template v-for="cols in colConfigs">
                <!-- 无需合并的列 -->
                <el-table-column
                    v-if="cols.type === 'label' && !cols.children"
                    :key="cols.prop"
                    :prop="cols.prop"
                    :label="cols.label"
                >
                </el-table-column>
                <!-- 需要合并的列 -->
                <template v-else-if="cols.type === 'label' && cols.children">
                    <el-table-column
                        v-for="children in cols.children"
                        :key="children.prop"
                        :prop="children.prop"
                        :label="children.label"
                    />
                </template>
            </template>
        </el-table>
    </div>
</template>
 
<script>
export default {
    name: 'Table',
    data() {
        return {
            tableData: [
                { 
                    time: '2020-08-10', grade: '三年二班', 
                    name: '小明', subjects: '语文', score: 80 
                },
                { 
                    time: '2020-08-10', grade: '三年二班', 
                    name: '小明', subjects: '数学', score: 80 
                }, 
                { 
                    time: '2020-08-10', grade: '总成绩', 
                    name: '总成绩', subjects: '总成绩', score: 160 
                },
                { 
                    time: '2020-08-10', grade: '三年一班', 
                    name: '小雷', subjects: '语文', score: 70 
                },
                { 
                    time: '2020-08-10', grade: '三年一班', 
                    name: '小雷', subjects: '数学', score: 80 
                },
                { 
                    time: '2020-08-10', grade: '总成绩', 
                    name: '总成绩', subjects: '总成绩', score: 150 
                }, 
                { 
                    time: '2020-08-11', grade: '三年三班', 
                    name: '小花', subjects: '语文', score: 60 
                }, 
                { 
                    time: '2020-08-11', grade: '三年三班', 
                    name: '小花', subjects: '数学', score: 60 
                }, 
                { 
                    time: '2020-08-11', grade: '总成绩', 
                    name: '总成绩', subjects: '总成绩', score: 120 
                }
            ],
            // 表格的信息 需要合并的需要放在 children 中
            colConfigs: [
                {
                    type: 'label',
                    children: [
                        { prop: 'time', label: '时间' },
                        { prop: 'grade', label: '年级' },
                        { prop: 'name', label: '姓名' },
                        { prop: 'subjects', label: '科目' },
                        { prop: 'score', label: '成绩' }
                    ]
                },
                // { type: 'label', prop: 'age', label: '年龄' }
            ],
            // 需要合并的行列信息 index必须是table表格对应的下标 不能随意修改
            mergeCols: [
                { index: 0, name: 'time' },
                { index: 1, name: 'grade' },
                { index: 2, name: 'name' },
                { index: 3, name: 'subjects' },
                { index: 4, name: 'score' },
                // { index: 5, name: 'age' }
            ],
            // 用来记录每一个单元格的下标
            tableMergeIndex: [],
        };
    },
    methods: {
        objectSpanMethods({ row, column, rowIndex, columnIndex }) {
            let key = columnIndex + '_' + rowIndex;
            if (this.tableMergeIndex[key]) {
                return this.tableMergeIndex[key];
            }
        },
        newTableMergeData() {
            for (let i = 0; i < this.tableData.length; i++) {
                for (let j = 0; j < this.mergeCols.length; j++) {
                    // 初始化行、列坐标信息
                    let rowIndex = 1;
                    let columnIndex = 1;
                    // 比较横坐标左方的第一个元素
                    if (j > 0 && this.tableData[i][this.mergeCols[j]['name']] 
                        === this.tableData[i][this.mergeCols[j - 1]['name']]
                    ) {
                        columnIndex = 0;
                    }
                    // 比较纵坐标上方的第一个元素
                    if (i > 0 && this.tableData[i][this.mergeCols[j]['name']] 
                        === this.tableData[i - 1][this.mergeCols[j]['name']]
                    ) {
                        rowIndex = 0;
                    }
                    // 比较横坐标右方元素
                    if (columnIndex > 0) {
                        columnIndex = this.onColIndex(
                            this.tableData[i],j,j+1,1,this.mergeCols.length
                        );
                    }
                    // 比较纵坐标下方元素
                    if (rowIndex > 0) {
                        rowIndex = this.onRowIndex(
                            this.tableData,i,i+1,1,this.mergeCols[j]['name']
                        );
                    }
                    let key = this.mergeCols[j]['index'] + '_' + i;
                    this.tableMergeIndex[key] = [rowIndex, columnIndex];
                }
            }
        },
        /**
         * 计算列坐标信息
         * data 单元格所在行数据
         * index 当前下标
         * nextIndex 下一个元素坐标
         * count 相同内容的数量
         * maxLength 当前行的列总数
         */
        onColIndex(data, index, nextIndex, count, maxLength) {
            // 比较当前单元格中的数据与同一行之后的单元格是否相同
            if (nextIndex < maxLength && data[this.mergeCols[index]['name']] 
                === data[this.mergeCols[nextIndex]['name']]) {
                return this.onColIndex(
                    data, index, ++nextIndex, ++count, maxLength
                    );
            }
            return count;
        },
        /**
         * 计算行坐标信息
         * data 表格总数据
         * index 当前下标
         * nextIndex 下一个元素坐标
         * count 相同内容的数量
         * name 数据的key
         */
        onRowIndex(data, index, nextIndex, count, name) {
            // 比较当前单元格中的数据与同一列之后的单元格是否相同
            if (nextIndex < data.length && data[index][name] 
                === data[nextIndex][name]) {
                return this.onRowIndex(data,index,++nextIndex,++count,name);
            }
            return count;
        }
    },
    mounted() {
        if(this.mergeCols.length > 0) {
            this.newTableMergeData();
        }
    }
};
</script>
 
<style lang="stylus" scoped>
.table 
    height 100vh
    width 100%
    padding 40px
    box-sizing border-box
    /deep/ .el-table__body tr:hover > td
        background-color: #fff;
</style>

三:兼容

如果不想合并的行需要在colConfigs中调整,如下:

前言:需要做的调整

javascript 复制代码
// 增加一个年龄属性 但是不进行合并
colConfigs: [
    {
        type: 'label',
        children: [
            { prop: 'time', label: '时间' },
            { prop: 'grade', label: '年级' },
            { prop: 'name', label: '姓名' },
            { prop: 'subjects', label: '科目' },
            { prop: 'score', label: '成绩' }
        ]
    },
    { type: 'label', prop: 'age', label: '年龄' }
]

1:效果图

2: 如果想要合并,需要在mergeCols中添加数据:

javascript 复制代码
mergeCols: [
    { index: 0, name: 'time' },
    { index: 1, name: 'grade' },
    { index: 2, name: 'name' },
    { index: 3, name: 'subjects' },
    { index: 4, name: 'score' },
    { index: 5, name: 'age' } // 添加需要合并的age列信息 注意index的值
],

3:新添加的属性合并后效果图

另:单纯的行合并

javascript 复制代码
 data{
    return {
        spanArr: [],
        position: 0,
    }
}   
 
rowspan(data) {
            this.spanArr=[];
            data.forEach((item,index) => {
                if( index === 0){
                    this.spanArr.push(1);
                    this.position = 0;
                }else{
                    if(data[index].FId === data[index-1].FId ){
                        this.spanArr[this.position] += 1;
                        this.spanArr.push(0);
                    }else{
                        this.spanArr.push(1);
                        this.position = index;
                    }
                }
            })
        },
        objectSpanMethod({ row, column, rowIndex, columnIndex }) {
            if (columnIndex === 0||columnIndex === 1) {
                const _row = this.spanArr[rowIndex];
                const _col = _row>0 ? 1 : 0;
                return {
                    rowspan: _row,
                    colspan: _col
                }
            }
        },
相关推荐
RaidenLiu7 分钟前
告别陷阱:精通Flutter Signals的生命周期、高级API与调试之道
前端·flutter·前端框架
非凡ghost7 分钟前
HWiNFO(专业系统信息检测工具)
前端·javascript·后端
非凡ghost9 分钟前
FireAlpaca(免费数字绘图软件)
前端·javascript·后端
非凡ghost16 分钟前
Sucrose Wallpaper Engine(动态壁纸管理工具)
前端·javascript·后端
拉不动的猪17 分钟前
为什么不建议项目里用延时器作为规定时间内的业务操作
前端·javascript·vue.js
该用户已不存在24 分钟前
Gemini CLI 扩展,把Nano Banana 搬到终端
前端·后端·ai编程
地方地方26 分钟前
前端踩坑记:解决图片与 Div 换行间隙的隐藏元凶
前端·javascript
小猫由里香32 分钟前
小程序打开文件(文件流、地址链接)封装
前端
Tzarevich35 分钟前
使用n8n工作流自动化生成每日科技新闻速览:告别信息过载,拥抱智能阅读
前端
掘金一周1 小时前
一个前端工程师的年度作品:从零开发媲美商业级应用的后台管理系统 | 掘金一周 10.23
前端·人工智能·后端