功能点

element table 或者List 大量数据渲染导致页面卡

2022-08-02  本文已影响0人  冰落寞成

一、要实现的效果

效果图.png

一次加载大量数据:table 表格需要有check 勾选,并且还要分组,分组也能进行勾选;一次性渲染的话,会出现页面卡顿现象,这样对用户来讲是非常不友好的

二、分析渲染卡顿原因和解决方案

2.1、分析卡顿原因

打开浏览器的DevTools ,选中“performance”,可以看到页面卡顿主要是由于渲染慢导致的


1659518981837.png
1659519178874.png

2. 2、解决大量Demo渲染导致页面卡顿

使用方法:虚拟加载
思路:因为数据量一次过来了2000+ ,渲染demo 需要浪费很大的时间;首先想到是在视图范围内:先渲染10条数据,当用户使用滚轮/ 滚动条滑动的时候,当数据到达视图范围内再渲染

1659519895831.png

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)
      }
    },
上一篇 下一篇

猜你喜欢

热点阅读