15.树形虚拟列表实现(支持10000+以上的数据)el-tree(1万+数据页面卡死)

1.问题使用el-tree渲染的树形结构,当数据超过一万条以上的时候页面卡死

2.解决方法:

使用vue-easy-tree来实现树形虚拟列表,注意:vue-easy-tree需要设置高度

3.代码如下

<template>
    <div class="ve-tree" style="height:calc(100vh - 20px)">
    <!-- 不使用虚拟滚动时只需去掉height参数即可 -->
      <vue-easy-tree
        ref="veTree"
        node-key="id"
        show-checkbox
        height="calc(100vh - 20px)"
        :data="treeData"
        :props="props"
      ></vue-easy-tree>
    </div>
  </template>
   
  <script>
  
import VueEasyTree from "@wchbrad/vue-easy-tree";
// 样式文件,可以根据需要自定义样式或主题(使用这个样式需要装sass-loader以及node-sass)
import "@wchbrad/vue-easy-tree/src/assets/index.scss"

  export default {
    components: {
        VueEasyTree
    },
    data() {
      return {
        props: {
          label: "name",
          children: "children"
        },
        treeData: []
      };
    },
   
    created() {
      const data = [],
        root = 8,
        children = 3,
        base = 1000;
      for (let i = 0; i < root; i++) {
        data.push({
          id: `${i}`,
          name: `test-${i}`,
          children: []
        });
        for (let j = 0; j < children; j++) {
          data[i].children.push({
            id: `${i}-${j}`,
            name: `test-${i}-${j}`,
            children: []
          });
          for (let k = 0; k < base; k++) {
            data[i].children[j].children.push({
              id: `${i}-${j}-${k}`,
              name: `test-${i}-${j}-${k}`
            });
          }
        }
      }
      this.treeData = data;
    }
  };
  </script>

4. 使用方法,首先安装依赖

yarn add @wchbrad/vue-easy-tree

如果不引入样式文件可以不安装(sass-loader以及node-sass)
node-sass:4.14.1
sass-loader:8.0.2
(自己安装的时候失败了,所以选择不引入样式文件)

5.组件引入

import VueEasyTree from "@wchbrad/vue-easy-tree";
// 样式文件,可以根据需要自定义样式或主题
import "@wchbrad/vue-easy-tree/src/assets/index.scss"
 
export default {
  components: {
    VueEasyTree
  }
}

6.功能列表

 1.大数据量支持虚拟滚动
 2.基本树形数据的展示
 3.支持checkbox选择
 4.支持懒加载
 5.默认展开和默认选中
 6.禁用节点
 7.通过多种方式选中节点和获取选中的节点信息
 8.支持自定义节点内容
 9.支持节点过滤
 10.非虚拟滚动下,支持手风琴模式
 11.非懒加载时,支持节点拖拽

支持与element-ui完全相同的主题样式更换,提供与element-ui相同的图标供选用
如果使用element-ui的默认属性代码为

<template>
    <div class="tree-comp">
        <div class="input-box">
            <el-input size="mini" suffix-icon="el-icon-search" clearable v-model="filterInputValue"
                @change="onFilter" placeholder="请输入检索内容">
            </el-input>
        </div>

        <div class="ve-tree" style="height:520px">
            <!-- 不使用虚拟滚动时只需去掉height参数即可 -->
            <vue-easy-tree
                v-for="(treeItem, index) in treeOption" :key="index"
                ref="treeComp"
                node-key="id"
                show-checkbox
                height="520px"
                :data="treeItem.treeData"
                :props="props"
                :filter-node-method="filterNode"
                :highlight-current="true"
                :default-checked-keys="allNodeIds"
                :default-expanded-keys="defaultExpandedKeys[index]"
                :check-on-click-node="true"
                v-bind="treeItem.defaultProps"
                v-on="treeItem.defaultActions"
                @check-change="handleCheckChange"
            ></vue-easy-tree>
        </div>
    </div>
</template>

<script>
import VueEasyTree from "@wchbrad/vue-easy-tree";
const debounce = function debounce(fn, delay) {
    let timer = null;
    return function () {
        clearTimeout(timer);
        let args = arguments;
        let that = this;
        timer = setTimeout(function () {
            fn.apply(that, args);
        }, delay);
    };
};
export default {
    name: 'TreeComp',
    props: {
        treeOption: { type: Array },
        selection: {
            type: String,
            default: 'multiple'
        }
    },
    components: {
        VueEasyTree
    },
    data() {
        return {
            filterInputValue: '',  // 过滤搜素值
            curChoosedData: {} ,    // 当前节点数据
            filterParentNodeId: [],
            selectedCount: 0,
            defaultExpandedKeys: [],
            allNodeIds: [],
            props: {
                label: "name",
                children: "children"
            },
            treeData: []
        }
    },
    watch: {
        curChoosedData(val){
            this.$emit('curChoosedDataChange', val);
        },
    },
    computed: {
        isSingle() {
            return this.selection === 'single'
        }
    },
    created(){
        const data = [],
        root = 8,
        children = 3,
        base = 9000;
      for (let i = 0; i < root; i++) {
        data.push({
          id: `${i}`,
          name: `test-${i}`,
          children: []
        });
        for (let j = 0; j < children; j++) {
          data[i].children.push({
            id: `${i}-${j}`,
            name: `test-${i}-${j}`,
            children: []
          });
          for (let k = 0; k < base; k++) {
            data[i].children[j].children.push({
              id: `${i}-${j}-${k}`,
              name: `test-${i}-${j}-${k}`
            });
          }
        }
      }
      this.treeData = data;
    },
    mounted() {
        this.getSelectedCount()
    },
    methods: {
        expandedLevel(num = 1){
            const treeCompRef = this.$refs.treeComp;
            this.treeOption.forEach((item)=>{
                item.treeData = this.$lodash.cloneDeep(item.treeData)
            })
            if(treeCompRef && treeCompRef.length > 0) {
                for (const [index,item] of treeCompRef.entries()) {
                    let checkedKeys = item.getCheckedKeys()
                    let treeData = item.data
                    this.defaultExpandedKeys[index] = this.expandedReduce(treeData, num)
                    item.setCheckedKeys(checkedKeys)
                }
            }
        },
        //递归获取展开层级的id
        expandedReduce(list,deep = 1){
           return deep > 0 ? list.reduce((val ,next)=>{
                   return next.children? val.concat(next.id).concat(this.expandedReduce(next.children,deep-1)) : val.concat(next.id)
               },[]) : []
        },
        // 过滤值改变触发filterNode
        onFilter(filterVal) {
            const treeCompRef = this.$refs.treeComp;
            if(treeCompRef && treeCompRef.length >0){
                for (let item of treeCompRef) {
                    this.filterParentNodeId = [];
                    item.filter(filterVal);
                }
            }
        },

        // 筛选树节点
        filterNode(value, data) {
            if (!value) return true;
            let filterValue = value.split(',');
            let flag = false;
            filterValue.forEach((item) => {
                if (data.name.indexOf(item) !== -1 || this.filterParentNodeId.includes(data.parentId)) {
                    this.filterParentNodeId.push(data.id);
                     flag = true;
                } 
            });
            return flag;
        },
        handleCheckChange:function (data, checked) {
            if (this.isSingle) {
                this.singleCheck(data,checked)
            }
            this.getSelectedCount()
        },
        singleCheck:debounce(function (data,checked){
            this.$nextTick(()=>{
                if (checked) {
                    this.$refs.treeComp[0].setCheckedKeys([data.id]);
                }
            })
        },100),
        getSelectedCount: debounce(function () {
            this.selectedCount = 0
            const treeCompRef = this.$refs.treeComp;
            if(treeCompRef && treeCompRef.length >0){
                for (const item of treeCompRef) {
                    let selectedNodes = item.getCheckedNodes()
                    let selectedChildrenNodes = selectedNodes.filter((node) => {
                        // !Object.prototype.hasOwnProperty.call(node, 'children')
                        // return node.children.length === 0
                        return !node.children || node.children.length === 0
                    })
                    this.selectedCount += selectedChildrenNodes.length
                }
            }
            this.$emit('getSelectedCount', this.selectedCount)
        },300)
    }
}
</script>
<style scoped>
    .tree-comp {
        display: flex;
        flex-direction: column;
        overflow: hidden;
        width: 100%;
        height: 100%;
    }

    .tree-comp .input-box >>> .el-input__inner {
        border: none;
        border-radius: 0;
        border-bottom: 1px solid #A8AED3;
        height: 32px;
        line-height: 32px;
    }

    .tree-comp .input-box >>> .el-input__suffix-inner {
        line-height: 32px;
        font-size: 16px;
    }

   .tree-comp .el-tree {
        /* flex: 1; */
        max-height: 100%;
        overflow-y: auto;
    }

    .tree-node {
        flex: 1;
        height: 30px;
        line-height: 30px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        /*background: #fff;*/
        z-index: 2;
    }
    .tree-self {
        width: 100%;
        overflow: scroll;
    }
    .tree-self >>> .el-tree-node {
        min-width: 100%;
        display: table;
    }
</style>

7.效果

在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值