最近写了一个项目 要动态合并单元格 搞得很头疼 在网上查找了资料 借鉴一部分 自己把东西改了一部分 有一块地方不会改问了同事,同事教了我 感谢他的帮助 正好我也分享一下 发布这篇文章 也是记录一下以后再遇到这个问题再回顾 好了不墨迹了 上代码!!!!
<template>
<div>
<!-- input输入框输入的值都是字符串类型,例如你输入了1实际上是字符串1而不是数字1,因此需要一个类型转换 -->
<el-table
:data="tableData"
:span-method="objectSpanMethod"
border
ref="table"
>
<el-table-column prop="" label="">
<!-- <template slot-scope="scope">
<el-input
v-model.number="scope.row.outlock"
@input="changeOutLook(scope.$index, scope.row)"
>{{ scope.row.id}}
</el-input>
</template> -->
</el-table-column>
<el-table-column prop="" label="">
<!-- <template slot-scope="scope">
<el-input
v-model.number="scope.row.outLook"
@input="changeOutLook(scope.$index, scope.row)"
>{{ scope.row.id}}
</el-input>
</template> -->
</el-table-column>
<el-table-column prop="id" label="" width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.id">{{ scope.row.id }} </el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="" width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.name">{{ scope.row.name }} </el-input>
</template>
</el-table-column>
<el-table-column prop="ranktime" label="" width="250">
<template slot-scope="scope">
<el-date-picker
v-model="scope.row.ranktime"
type="date"
placeholder="选择日期"
>
</el-date-picker>
</template>
</el-table-column>
<el-table-column label="" align="center" prop="">
<el-table-column label="" align="center" prop="">
<template slot-scope="scope">
<el-input v-model="scope.row.relevantpeople"
>{{ scope.row.relevantpeople }}
</el-input>
</template>
</el-table-column>
<el-table-column label="" align="center" prop="">
<template slot-scope="scope">
<el-input v-model="scope.row.peoplejob"
>{{ scope.row.peoplejob }}
</el-input>
</template>
</el-table-column>
<el-table-column
label=""
align="center"
prop="completetime"
width="250"
>
<template slot-scope="scope">
<el-date-picker
v-model="scope.row."
type="date"
placeholder="选择日期"
>
</el-date-picker>
</template>
</el-table-column>
</el-table-column>
<!-- P2 -->
<el-table-column label="操作" width="180" align="center">
<el-table-column prop="" width="90" label="新增" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleAdd(scope.$index, scope.row)"
icon="el-icon-plus"
>新增</el-button
>
</template>
</el-table-column>
<el-table-column prop="" width="90" label="删除" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleDelete(scope.$index, scope.row)"
icon="el-icon-minus"
>删除</el-button
>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script>
import throttle from "lodash/throttle";
export default {
name: "TestTable",
data() {
return {
tableData: [
{
PERSON_TYPE: "",
outLook: "",
id: 1,
name: "test1",
ranktime: "",
completetime: "",
relevantpeople: "",
peoplejob: "",
gender: 2,
},
{
PERSON_TYPE: "",
outLook: "",
id: 2,
completetime: "",
peoplejob: "",
name: "test2",
ranktime: "",
relevantpeople: "",
gender: 2,
},
{
PERSON_TYPE: "",
outLook: "",
peoplejob: "",
id: 3,
completetime: "",
name: "test3",
relevantpeople: "",
ranktime: "",
gender: 2,
},
{
PERSON_TYPE: "",
outLook: "",
id: 4,
peoplejob: "",
completetime: "",
name: "test3",
relevantpeople: "",
ranktime: "",
gender: 2,
},
],
// 相同行数名的索引的值
mergerRowIndex: [],
// 第一列的的索引
mergerFirRowIndex: [],
//第0列的索引
mergerFirRowIndexzero: [],
rowspanPERSON: [],
rowspanoutLook: [],
};
},
methods: {
changeOutLook: throttle(function (index, row) {
if (index < this.mergerFirRowIndex[0]) {
for (var i = 0; i < this.mergerFirRowIndex[0]; i++) {
this.tableData[i].outLook = row.outLook;
}
return;
}
if (index < this.mergerFirRowIndexzero[0]) {
for (var i = 0; i < this.mergerFirRowIndexzero[0]; i++) {
this.tableData[i].PERSON_TYPE = row.PERSON_TYPE;
}
return;
}
var start_index = this.mergerFirRowIndex.indexOf(row);
for (
var i = this.mergerFirRowIndex[start_index];
i < this.mergerFirRowIndex[start_index + 1];
i++
) {
this.tableData[i].outLook = row.outLook;
}
for (
var i = this.mergerFirRowIndex[start_index];
i < this.mergerFirRowIndex[start_index + 1];
i++
) {
this.tableData[i].PERSON_TYPE = row.PERSON_TYPE;
}
}, 1000),
/*
合并后的表格,有一个坑,就是,当用户修改合并的那一行的数据,只有第一行会被修改,跟他合并的其他行数据都不会修改
因此我们需要自己定义一个函数统一将他们修改
这里需要补充一个知识点:节流与防抖,这里有个资料讲的很详情大家可以去参考一下
https://muyiy.cn/blog/7/7.2.html#underscore-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
*/
changeId: throttle(function (index, row) {
if (index < this.mergerRowIndex[0]) {
for (var i = 0; i < this.mergerRowIndex[0]; i++) {
this.tableData[i].id = row.id;
}
return; //这个return的作用是当只改变0到this.mergerRowIndex[0]的数据后,后续的不用改那么就中断函数,提升性能
}
//如果输入的值是大于this.mergerRowIndex的第一项的,因此要记录具体操作的是那一行的数据,然后改变它直到下一行的数据
var start_index = this.mergerRowIndex.indexOf(index);
for (
var i = this.mergerRowIndex[start_index];
i < this.mergerRowIndex[start_index + 1];
i++
) {
this.tableData[i].id = row.id;
}
console.log(this.tableData);
}, 1000),
// 提交表格数据,判断关键字段有无重复,这里用id做判断还有排名做举例
submintTable() {
var idArr = [];
// 判断相同id下其对象中是否存在rank重复情况的标志
var uniqueRankFlag = false;
this.mergerRowIndex.forEach((ele, index) => {
// 作用是将相同id的对象压入到一个数组中,
var arr = _.filter(this.tableData, {
id: this.tableData[this.mergerRowIndex[index] - 1].id,
});
console.log(arr);
// 这一步的操作是,判断拥有相同id的对象,其rank是否存在重复的情况
var uniqueRank = arr.every((ele) => {
return _.filter(arr, { rank: ele.rank }).length == 1;
});
console.log(uniqueRank);
// 如果uniqueRank == false说明,同一id下的对象中存在rank相同的
if (uniqueRank == false) uniqueRankFlag = true;
/*
有一点需要注意
forEach 和 map
区别
forEach 执行后返回 undefined
map 执行后返回新数组
共同点
只能遍历数组并参数都一样
不改变原函数(引用类型除外)
无法中断循环;return 只是结束本地循环,进入下一次循环
break 或 continue 都将会报错
*/
//这里我们将相同id的数组,取出每一项,放入到一个新数组中,然后用es6 set方法去重后判断
//原来的数组和去重后的数组长度是否一致,如果一致说明,tabledata中的id没有重复反之
idArr.push(arr[0].id);
});
//判断id是不是唯一,定义一个数组去重函数,利用es6 set方法去重,然后再比较与之前的长度是否相同,该方法会将数组中重复的给去除,返回不重复的数组
function uniqueId(arr) {
return Array.from(new Set(arr));
}
var newIdArr = uniqueId(idArr);
console.log(newIdArr);
console.log(idArr);
if (newIdArr.length != idArr.length) {
alert("该数据表格存在同名id");
return;
}
// 同一个id下,判断rank是否存在相同
if (uniqueRankFlag) {
alert("同一个id下,存在rank相同的数据,请查看");
return;
}
},
// 一键勾选左边的单项框或者右边的单项框
changePer() {
console.log(this.radio);
this.tableData.forEach((ele) => (ele.gender = this.radio));
//有些人希望头部的按钮点击了不要亮,就可以让this.radio = null 来解决这个事情
this.radio = null;
},
//表格增加多一个对象
addObecjt() {
console.log(this.tableData);
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].id == "" || this.tableData[i].outLook == "") {
/*
这里的id不能为空,因为如果存在俩个及以上为空,会合并函数,所以要确保合并的项有了属性后才能让用户再增加多一个数据
*/
alert("表格id和outLook不能为空");
return;
}
}
this.tableData.push({
PERSON_TYPE: "",
outLook: "",
id: "",
name: null,
rank: null,
gender: 2,
});
//增加完数据后,更新一次合并的数据详情
this.getMergerRowIndex();
},
//表格减少一个对象,直接删除表格末尾的数据即可
cutObject() {
this.tableData.pop();
this.getMergerRowIndex();
},
//合并格函数
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
return {
rowspan: this.rowspanPERSON[rowIndex], // tabledata数组rowIndex下标中对应rowspanPERSON数组中的值大于0,表示这个单元格要向下合并,值多少表示合并多少单元格
colspan: this.rowspanPERSON[rowIndex] ? 1 : 0, // tabledata数组rowIndex下标中对应rowspanPERSON数组中的值为0,表示这个单元格被合并了
};
}
if (columnIndex === 1) {
return {
rowspan: this.rowspanoutLook[rowIndex], // tabledata数组rowIndex下标中对应rowspanoutLook数组中的值大于0,表示这个单元格要向下合并,值多少表示合并多少单元格
colspan: this.rowspanoutLook[rowIndex] ? 1 : 0, // tabledata数组rowIndex下标中对应rowspanoutLook数组中的值为0,表示这个单元格被合并了
};
}
},
// 增加一行
handleAdd(index, row) {
console.log(this.tableData);
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].id == "" || this.tableData[i].outLook == "") {
/*
这里的id不能为空,因为如果存在俩个及以上为空,会合并函数,所以要确保合并的项有了属性后才能让用户再增加多一个数据
*/
console.log("表格id和outLook不能为空");
return;
}
}
for (var i = 0; i < this.mergerFirRowIndex.length; i++) {
if (index < this.mergerFirRowIndex[i]) {
for (var x = i; x < this.mergerFirRowIndex.length; x++) {
this.mergerFirRowIndex[x] += 1;
}
break;
}
}
// 合并的行数往后推一;例如这里的是 2 3 5 如果index是3 那么就让其变成2 4 6,
//并且在其后面添加一个数据,可以使用splice(xx,0,{}) 这个方法能在指定位置加数据
for (var i = 0; i < this.mergerRowIndex.length; i++) {
if (index < this.mergerRowIndex[i]) {
for (var x = i; x < this.mergerRowIndex.length; x++) {
this.mergerRowIndex[x] += 1;
}
// this.mergeRowindexFirst += 1;
this.tableData.splice(index + 1, 0, {
PERSON_TYPE: row.PERSON_TYPE,
outLook: row.outLook,
name: "",
id: row.id,
rank: null,
});
/* 这里为什么要加break:因为要中断循环,循环的目的是找到其用户点击的是哪一行,然后那一行之后的所有数据
都加一,找到了之后即可以直接跳出循环了,否则其后面的所有序列都加一,放在外第439行玩什么不行?
因为放在439行,会导致最外层的for循环只执行一次,就被中断了,不能执行后续的判断
*/
break;
}
}
this.getRowspan(this.tableData);
console.log("新增:", this.tableData);
// console.log(this.mergerRowIndex);
// console.log(this.mergerFirRowIndex)
},
// 减少一行
handleDelete(index, row) {
console.log("sub");
for (var i = 0; i < this.mergerFirRowIndex.length; i++) {
if (index < this.mergerFirRowIndex[i]) {
for (var x = i; x < this.mergerFirRowIndex.length; x++) {
this.mergerFirRowIndex[x] -= 1;
}
if (
this.mergerFirRowIndex[x] == 0 ||
this.mergerFirRowIndex[x] == this.mergerFirRowIndex[x - 1]
) {
this.mergerFirRowIndex.splice(x, 1);
}
}
this.tableData.splice(index, 1);
break;
}
for (var i = 0; i < this.mergerRowIndex.length; i++) {
if (index < this.mergerRowIndex[i]) {
for (var x = i; x < this.mergerRowIndex.length; x++) {
this.mergerRowIndex[x] -= 1;
/*
这里做判断是:当点击某一个行数时,后续的索引都要减一,但是可能存在一个数组存在俩个相同的数
这就会导致错位了,因此要把这个重复的数给去除 即this.mergerRowIndex[x] == this.mergerRowIndex[x - 1]
而:this.mergerRowIndex[x] == 0 其主要作用是当用户重复删除index=0的那一行时,就会导致
合并的行数都不断的减一,直到this.mergerRowIndex某一项变为0,如果变为0就应该把其从this.mergerRowIndex中去除
*/
if (
this.mergerRowIndex[x] == 0 ||
this.mergerRowIndex[x] == this.mergerRowIndex[x - 1]
) {
this.mergerRowIndex.splice(x, 1);
}
}
this.tableData.splice(index, 1);
break;
}
}
this.getRowspan(this.tableData);
console.log(this.tableData);
},
// 查验有无相同行数名的对象,如果知道不同行数名的位置,则记住其位置并且压入
getMergerRowIndex() {
// 每次调用这个函数,就需要把之前压入到函数内的值给清空
this.mergerRowIndex = [];
this.mergerFirRowIndex = [];
console.log(this.tableData);
for (let i = 1; i < this.tableData.length; i++) {
if (this.tableData[i].id != this.tableData[i - 1].id) {
this.mergerRowIndex.push(i);
}
if (this.tableData[i].outLook != this.tableData[i - 1].outLook) {
this.mergerFirRowIndex.push(i);
}
}
// 并且还要压入表格的的长度
this.mergerRowIndex.push(this.tableData.length);
this.mergerFirRowIndex.push(this.tableData.length);
console.log(this.mergerRowIndex, this.mergerFirRowIndex);
this.getRowspan(this.tableData);
},
getRowspan(tabledata) {
let item = 0, // 在for循环中表示需要合并单元格的数组的下标。从0开始:表示从第一次循环开始。
outLookitem = 0;
this.rowspanPERSON = []; // 该数组存放,
//table单元格是否合并,数组长度与tabledata数组长度相同
//(该数组中:0代表对应的tbale那条数据单元格被合并, 0以上表示向下合并的单元格数量)
this.rowspanoutLook = []; // 该数组存放,
//table单元格是否合并,数组长度与tabledata数组长度相同
//(该数组中:0代表对应的tbale那条数据单元格被合并, 0以上表示向下合并的单元格数量)
for (let index = 0; index < tabledata.length; index++) {
if (index === 0) {
// 第一次循环, 单元格不允许被合并,所以赋值1,这是table中的第一行
this.rowspanPERSON.push(1);
this.rowspanoutLook.push(1);
} else {
// 从第二行开始,判断tabledata[index]数据与上一条数据中字段PERSON_TYPE值是否相同
if (
tabledata[index].PERSON_TYPE === tabledata[index - 1].PERSON_TYPE
) {
// 如果相同,该单元格要被合并,rowspanPERSON中item下标的值加一(item表示相同字段值的第一出现)
this.rowspanPERSON[item] += 1;
// rowspanPERSON数组新增一个值为0的元素,表示,这个单元格被合并了。
this.rowspanPERSON.push(0);
} else {
// 不相同,表示该单元格不应被合并,rowspanPERSON数组新增1,该单元格被保留
this.rowspanPERSON.push(1);
// item被重新赋值,表示该单元格的值在for循环中第一次出现
item = index;
}
if (tabledata[index].outLook === tabledata[index - 1].outLook) {
this.rowspanoutLook[outLookitem] += 1;
this.rowspanoutLook.push(0);
} else {
this.rowspanoutLook.push(1);
outLookitem = index;
}
}
}
},
},
created() {
this.getMergerRowIndex();
},
};
</script>
实现的效果图,我就不展现了,大概就是这样的一个逻辑,希望能帮助到大家,写这篇文章也是以后当自己不会了可以翻下再看看
加油!!!
eg:一个很笨又热爱学习的人