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

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

2.解决方法:

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

3.代码如下

javascript 复制代码
<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. 使用方法,首先安装依赖

javascript 复制代码
yarn add @wchbrad/vue-easy-tree

如果不引入样式文件可以不安装(sass-loader以及node-sass)

node-sass:4.14.1

sass-loader:8.0.2

(自己安装的时候失败了,所以选择不引入样式文件)

5.组件引入

javascript 复制代码
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的默认属性代码为

javascript 复制代码
<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.效果

相关推荐
y先森33 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy33 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891136 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端