这天写项目,项目里面有个地方用表格呈现数据,虽然element-ui自己的表格组件自带滚动条,但是需要显示的数据有几十万,搞不定,翻页吧,每页多少多少。成了。
但是呢,这个表格有个批量操作的功能,用的组件自带的属性,只能对这个表格数据作用,也就是说我翻页了,刷新了这个组件当前绑定的数组,复选项就没了。这肯定不行嘛,翻页就忘了前几页的选项那不是没了。
所以现在这个情况就是需要记住以前选的对象,简单,搞个数组记录全部数据(以下统称allArr),然后本来本页数据就有个数组在记录选中的数据(以下统称nowArr),每次翻页的时候,allArr.concat(nowArr)一下,成了。然后是解决返回上一页的时候依旧高亮曾经选过的数据。这个稍微想了下(太菜了),那就在返回这一页的获取数据里写个匹配吧,循环这一页和全部数据,比对唯一标识,发现一样的就高亮它,并且加到nowArr(因为返回来这一页的这个数组清空了)。
for (let i = 0; i < this.tableData.length; i++) {
for (let j = 0; j < this.allOutPutInfo.length; j++) {
if (this.tableData[i].web_link === this.allOutPutInfo[j].web_link) {
this.$refs.multipleTable.toggleRowSelection(this.tableData[i])
this.nowArr[this.nowArr.length] = this.tableData[i]
}
}
}
恩,大概就是这样子。
因为用到了element-ui的组件,所以设置高亮状态的时候用的自带的方法 toggleRowSelection()
,一开始也没注意,看到这个方法,看懂了就用了,保存之后本地测试就出问题了。
一开始点了几个复选项,翻页测试了一下,不错,有效果了,然后又试了试反复翻页,恩?怎么多翻几页高亮的选项就没了?立即开始console测试大法。
for (let i = 0; i < this.tableData.length; i++) {
for (let j = 0; j < this.allOutPutInfo.length; j++) {
if (this.tableData[i].web_link === this.allOutPutInfo[j].web_link) {
this.$refs.multipleTable.toggleRowSelection(this.tableData[i])
console.log(this.tableData[i]) // 1
this.nowArr[this.nowArr.length] = this.tableData[i]
console.log(this.nowArr) // 2
}
}
}
于是发现诡异的事情,明明第一句打印出来是正常的,只是打印了匹配的对象,然而后面第二个打印出来nowArr却有重复的选项,长度也更长。?诡异,当时我是真的觉得诡异,同一个if语句里,出现了两种不同的运行结果。
后来把this.nowArr[this.nowArr.length] = this.tableData[i]
这一句删了继续测试。。。第二个打印就正常了。。 。=。=??exm?
然后我突然想起table组件的一个事件 selection-change
,这是原来控制本页复选项的事件。绑定的方法是这样的:
handleSelectionChange (val) {
this.nowArr = val
},
每次当页的选项变动时,就把变动后的对象数组传给nowArr,因为一开始“选项变动”这个事件我不会用代码触发,所以这个方法我也还是写在那里的,就是当前页面选复选的时候用。然后。。toggleRowSelection()
,这个组件自带的方法居然能触发组件的selection-change
事件,我透,也就是说原来那样写就会触发这个方法自动添加一次已经被toggleRowSelection()
标记为高亮的对象,然后我又手动添加了一次。。就重复了,重复之后多次匹配,把高亮状态反复翻转,所以就看到了,多翻几页,复选就没了的情况。
恩,保存,本地测试启动,恩?怎么翻几页又没了?我透哦。
检查了一下代码,发现了一个问题,在每次翻页的时候我都会添加一次这页所有选中的对象,然而,多次来回翻的时候,就会有重复的对象添加到allArr里面,然后在匹配的时候就会又重复翻转状态。
行,找到问题,就是每次在allArr那里去个重嘛。
然后我就用以前去重的经验写了对这个对象数组的去重,我知道以前不是写的对象数组,所以还特地改了点:
this.allOutPutInfo = this.nowArr.concat(this.allOutPutInfo).filter((value, index, arr) => {
return JSON.stringify(arr).indexOf(JSON.stringify(value)) === JSON.stringify(arr).lastIndexOf(JSON.stringify(value))
})
恩,完美,我觉得没有问题了(以前就是这么去重的,成功了的),还特地用JSON.stringify
把对象数组转成了字符串数组。但是在filter的运行过程中arr是不会改变的,所以这样写就把重复的全部去掉了,没有留下一个。又改:
this.allOutPutInfo = this.nowArr.concat(this.allOutPutInfo).filter((value, index, arr) => {
return JSON.stringify(arr).indexOf(stringify(value)) === index
})
恩,如果匹配的到的value的位置不等于自己本身的位置,就去掉。看了看,应该没问题了。保存,测试。又没了。。
就很无语,不知道哪个地方出了毛病。。然后去查看JSON.stringify的解释,emm,自己傻逼了,这个函数对对象数组用并不能把对象变成JSON字符串,是应该用map对每一个对象这么用,我把这个函数套在数组外面。。人傻了:
this.allOutPutInfo = this.nowArr.concat(this.allOutPutInfo).filter((value, index, arr) => {
return arr.map(value_ => JSON.stringify(value_)).indexOf(JSON.stringify(value)) === index
})
好了,最后终于搞定了,所有bug来自自己傻逼对函数认识的不到位。
2019/11/15 更新
看了评论加上自己又看了一下文档,重写了一下项目复选框的逻辑,现在变得更加简洁和高效了。
主要是两个事件的运用,element-ui里面table自带的:
事件 | 说明 | 参数 |
---|---|---|
select | 当用户手动勾选数据行的 Checkbox 时触发的事件 | selection row |
selection-all | 当用户手动勾选全选 Checkbox 时触发的事件 | selection |
就是用这两个事件,可以将上面全部的代码整合为下面两个函数:
/*
* <el-table
* @select="handleSingleSelect"
* @select-all="handleAllSelection">
* </el-table>
*/
// 每当选取当页全部数据时触发
handleAllSelection (selection) {
// 判断此次选取是选中还是取消(selection参数装的是此次选中数据构成的数组)
if (selection.length !== 0) { // 若不为0,则此次选中为全部标记,加入allArr中
// 用Set去重,考虑到Set不能对对象去重,做了点改变
let set = new Set(this.allOutputInfo.concat(selection).map(value => JSON.stringify(value)))
this.allOutputInfo = [...set].map(value => JSON.parse(value))
} else {
// 如果不是,那么就把本页所有的数据从allArr排除,因为此次是取消选中
this.allOutputInfo = this.allOutputInfo.concat(this.tableData).filter((value, index, arr) => {
// 这里直接indexOf对象翻页就不行了,翻页之后对同一个对象的引用不再一致
let tempArr = arr.map(value_ => JSON.stringify(value_))
let tempValue = JSON.stringify(value)
// 如果前后判断位置不一样就剔除
return tempArr.indexOf(tempValue) === tempArr.lastIndexOf(tempValue)
})
}
},
// 选中单个数据的时候
handleSingleSelect (selection, row) {
// 首先判断这个数据是否已经在allArr中,如果在,此次操作就是取消标记,反之标记
let theRowIndexInAllInfo = this.allOutputInfo.map(value => JSON.stringify(value)).indexOf(JSON.stringify(row))
if (theRowIndexInAllInfo === -1) {
// 不在直接添加就好了,这个方法可以免于去重
this.allOutputInfo = this.allOutputInfo.concat(row)
} else {
// 直接删除对应位置的数据
this.allOutputInfo.splice(theRowIndexInAllInfo, 1)
}
}
这里在重新高亮本页数据的时候依然选择原来的双重循环方法,因为table没有方法是传入对象数组然后高亮里面全部对象的,只有单个高亮table数据的方法。
总结
在实现某个功能的时候多看看文档,以免漏掉一些重要的现成工具而导致实现功能的难度大增。
记录选中的数据可以用 Set 来表示,这样就不会出现重复的情况
output 是一个词,驼峰命名 allOutputInfo 这样更好
vue 是数据驱动双向绑定的
所以你可以
用一个Array表示 当前页的原始数据 sourceData
再用一个Set表示选中的 selectedData
接下来你就可以写一个 computed 计算属性
tableData() {
// 根据 sourceData 和 selectedData 计算出提供给 table 的数据
//(显示数据和是否选中)
}
然后tableData 传给 < el-table >
每次选中或者取消选中某一个 row,你就用 Set 的 add , remove 方法,操作 selectedData
当selectedData变化是,tableData会重新计算值,并重新render表格
另外如果是写js ts的话,可以尝试使用 lodash,是个好东西
杨老板赏光,惶恐(雾),为什么会有去重这个说法,是我写的table事件选中的时候是直接返回一个选中对象的数组,所以都是直接concat到all里面,才有了concat的说法,看了杨老板的回复,又回去看了下element-ui的文档,发现table是有返回单个选中对象的事件的,只是当时没有仔细看。。
杨老板牛逼=。=
多写写,我会经常来看的😝
=。=ck
另外,我推荐使用 React(逃
不