element table 或者List 大量数据渲染导致页面卡
2022-08-02 本文已影响0人
冰落寞成
一、要实现的效果
效果图.png一次加载大量数据:table 表格需要有check 勾选,并且还要分组,分组也能进行勾选;一次性渲染的话,会出现页面卡顿现象,这样对用户来讲是非常不友好的
二、分析渲染卡顿原因和解决方案
2.1、分析卡顿原因
打开浏览器的DevTools ,选中“performance”,可以看到页面卡顿主要是由于渲染慢导致的
1659518981837.png
1659519178874.png
2. 2、解决大量Demo渲染导致页面卡顿
使用方法:虚拟加载
思路:因为数据量一次过来了2000+ ,渲染demo 需要浪费很大的时间;首先想到是在视图范围内:先渲染10条数据,当用户使用滚轮/ 滚动条滑动的时候,当数据到达视图范围内再渲染
2.3、使用虚拟滚动后页面渲染
效果还是很明显的
1659520045500.png
1659520306185.png
2.4、虚拟滚动条函数
/**
* 处理大批量数据渲染慢
* @param {*} pNode 要滚动容器父节点,一般固定高度
* @param {*} node 要滚动的容器
* @param {*} rowHeight 每行row 的高度
* @param {*} startIndex 开始下标
* @param {*} length 要渲染的总行数
*/
export const virtualScroll = ({ pNode, node, rowHeight = 10, startIndex = 0, length = 100 } = {}, callback) => {
// console.log('pNode', pNode)
let pNodeHeight = pNode.offsetHeight || 500 // 默认500 高度
let emptyNode = document.createElement('div') // 添加一个空node,用于pNode的滚动条
emptyNode.style.height = `${length * rowHeight}px`
emptyNode.style.width = '0px'
pNode.appendChild(emptyNode)
pNode.style.display = 'flex'
let scrollTop = 0 // 默认滚动条高度
/**
* 滚动监听
*/
pNode.addEventListener('scroll', (e) => {
scrollTop = pNode.scrollTop || 0
if (scrollTop + pNodeHeight >= emptyNode.offsetHeight) { // 滚动条到底部了
scrollTop = emptyNode.offsetHeight - pNodeHeight
}
node.style.marginTop = scrollTop + 'px'
startIndex = Math.floor(scrollTop / rowHeight) // 计算开始下标
// console.log('eeescoll', e)
// return { startIndex: startIndex || 0 }
let indexObj = { startIndex: startIndex }
callback(indexObj)
})
// console.log('startIndex', startIndex)
// return { startIndex: startIndex || 0 }
}
三、实现table表“全选/反选,单选”和分组的“全选、反选”交互效果
3.1、table 实现合并单元格进行数据分组
element 官方例子:https://element.eleme.cn/#/zh-CN/component/table
3.2、自定义check组件,支持半选中状态,和禁止状态
<template>
<div class="check-container">
<el-checkbox :indeterminate="isIndeterminate" v-model="checked" @change="handleCheck" :disabled="disabled">{{label}}</el-checkbox>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: () => ''
},
indeterminateStatus: {
type: Boolean,
default: () => false
},
isChecked: {
type: Boolean,
default: () => false
},
disabled: {
type: Boolean,
default: () => false
}
},
watch: {
indeterminateStatus: {
handler: function (val) {
this.isIndeterminate = val
}
},
isChecked: {
handler: function (val) {
this.checked = val
}
}
},
data () {
return {
checked: false,
isIndeterminate: false
}
},
mounted () {
this.checked = this.isChecked
this.isIndeterminate = this.indeterminateStatus
},
methods: {
handleCheck () {
this.$emit('handleCheck', this.checked)
}
}
}
</script>
3.3 分组引入check 组件,table 全选,单选使用check 组件,不实用 type="check"
分组引入check
<el-table-column
type="districtname"
prop="districtname"
label="区域名称"
align="center"
>
<template #default="scope">
<template v-if="cameraGroupObj[scope.row.districtid]">
<TableCheck
ref="setTableCheck"
:label="scope.row.districtname"
:isChecked="cameraGroupObj[scope.row.districtid].isChecked"
:indeterminateStatus="cameraGroupObj[scope.row.districtid].indeterminateStatus"
@handleCheck="handelPolymerizationCell($event,scope.row)"/>
<div class="point-item">
共有
<span class="point">{{cameraGroupObj[scope.row.districtid].list.length||0}}</span>
个点位</div>
<div class="point-item">
已选中
<span class="point">{{cameraGroupObj[scope.row.districtid].selectedList.length||0}}</span>
个点位</div>
</template>
</template>
</el-table-column>
table 全选引入
<el-table-column
label="全选"
width="55"
>
<template slot="header" slot-scope="scope">
<TableCheck
:isChecked="checkAllObject.isChecked"
:indeterminateStatus="checkAllObject.indeterminateStatus"
@handleCheck="handleAllSelect($event,scope)"/>
</template>
<template #default="scope">
<template>
<TableCheck
:disabled="!scope.row.online"
:isChecked="cameraselectedIds.indexOf(scope.row.cameraid) > -1"
@handleCheck="handleSingleSelect($event,scope.row)"/>
</template>
</template>
</el-table-column>
3.4 table 分段渲染
cameraselectedIds: 选中的list数据id
this.cameraLists: 实际的list数据,partCameraLists :部分要显示的数据
computed: {
partCameraLists () { // 要渲染的部分摄像头数据
return this.cameraLists.slice(this.startIndex, this.startIndex + this.rowSize)
}
},
3.5 虚拟滚动引入
/**
* 虚拟滚动
*/
handleVirtualScroll () {
this.tableBodyWrapper = this.$refs.multipleTable.$refs.bodyWrapper
this.reallyTable = this.tableBodyWrapper.firstElementChild
virtualScroll({ pNode: this.tableBodyWrapper, node: this.reallyTable, rowHeight: 53, length:
this.cameraLists.length }, (res) => {
this.startIndex = res.startIndex
})
},
3.6 选中效果联动
1、table 全选/ 反选, 单选,先用原始this.cameraLists 更新 cameraselectedIds 的数据
2、同时设置分子check 的选中状态
3、分组全选、反选,,更新cameraselectedIds 的数据,同时更新table 的check
/**
* 全选/反选
*/
handleAllSelect (bool, val) {
if (bool) { // 全选
this.checkedQuipmentList = this.cameraLists.filter(item => {
return item.online
})
console.log('ss', this.checkedQuipmentList.length, this.checkedQuipmentList[0])
} else { // 反选
this.checkedQuipmentList = []
}
this.updateSelectedCameraData({ arr: this.checkedQuipmentList }) // 更新选中的id
},
// 选择摄像头(单选)
handleSingleSelect (bool, val) {
if (bool) { // 选中
this.checkedQuipmentList = [...this.checkedQuipmentList, ...[val]]
} else { // 取消选中
let index = this.cameraselectedIds.indexOf(val.cameraid)
this.checkedQuipmentList.splice(index, 1)
}
this.updateSelectedCameraData({ arr: this.checkedQuipmentList, singleVal: val }) // 更新选中的id
},
/**
* 更新选中的摄像头的信息
* bool:true->更新store 里选中的摄像头信息,false 不更新
*/
updateSelectedCameraData ({ arr, bool = true, singleVal = {} } = {}) {
this.cameraselectedIds = arr.map(item => { // 更新选中摄像头的ID
return item.cameraid
})
if (bool) { // 判断是否更新store
this.setCameraList(this.checkedQuipmentList) // 更新store 里选中的摄像头信息
}
this.updateCheckStatus() // 选中状态
this.updateGroupStatus(singleVal) // 更新分组选中状态
},
/**
* 更新全选状态和区域选择和订单选择的状态
*/
updateCheckStatus () {
if (this.cameraselectedIds.length === 0) { // 全不选
this.checkAllObject.indeterminateStatus = false
this.checkAllObject.isChecked = false
} else {
if (this.cameraselectedIds.length === this.cameraLists.length) { // 全选
this.checkAllObject.indeterminateStatus = false
this.checkAllObject.isChecked = true
} else { // 部分选中
this.checkAllObject.indeterminateStatus = true
this.checkAllObject.isChecked = false
}
}
},
/**
* 更新分组选中状态
*/
updateGroupStatus (singleVal) {
// console.log('this.cameraselectedIds.length', this.cameraselectedIds.length, this.cameraLists.length)
if (this.cameraselectedIds.length === 0) { // 全不选
for (let i in this.cameraGroupObj) {
this.cameraGroupObj[i].isChecked = false
this.cameraGroupObj[i].indeterminateStatus = false
}
} else {
if (this.cameraselectedIds.length === this.cameraLists.length) { // 全选
for (let i in this.cameraGroupObj) {
this.cameraGroupObj[i].isChecked = true
this.cameraGroupObj[i].indeterminateStatus = false
}
} else { // 部分选中
// console.log('cameraid' in singleVal)
if ('cameraid' in singleVal) { // 点击单个选择
this.updateGroupStatusCheck(singleVal)
} else { // 默认初始化时
// console.log('--', this.checkedQuipmentList)
for (let i of this.checkedQuipmentList) {
// console.log('i', i)
this.updateGroupStatusCheck(i)
}
}
}
}
},
/**
* 部分选中时更新分组选中状态
*/
updateGroupStatusCheck (singleVal) {
let list = []; let selectedGroup = []
let key = this.activeName === 'area' ? singleVal.districtid : singleVal.id
list = this.cameraGroupObj[key].list
selectedGroup = this.checkedQuipmentList.filter(item => {
return item.districtid === singleVal.districtid
})
this.cameraGroupObj[key].selectedList = selectedGroup
// console.log('--checkGroup', key, selectedGroup.length, this.checkedQuipmentList.length)
if (selectedGroup.length === 0) { // 全不选
this.cameraGroupObj[key].isChecked = false
this.cameraGroupObj[key].indeterminateStatus = false
} else if (selectedGroup.length === list.length) { // 全选
this.cameraGroupObj[key].isChecked = true
this.cameraGroupObj[key].indeterminateStatus = false
} else { // 部分选中
this.cameraGroupObj[key].isChecked = true
this.cameraGroupObj[key].indeterminateStatus = true
console.log('部分选中hou', this.cameraGroupObj[key], key)
}
},