Element——el-tree懒加载

本文章项目项目全程使用Vue2和Element2!

懒加载:点击节点时才进行该层数据的获取。

注意:使用了懒加载之后,一般情况下就可以不用绑定:data

基础使用

懒加载需要再指定一个lazy和懒加载数据的方法:load

<template>
	<el-tree :props="props" :load="loadNode" lazy></el-tree>
</template>
<script>
  export default {
    data() {
      return {
        props: { // 映射配置
          label: 'name', // 将获取数组中的name作为显示节点(label)进行展示
          children: 'zones', // 将获取数组中的zones作为子节点(children)的展示
          isLeaf: 'leaf' // 将获取数组中的leaf作为判断是否是叶子节点(即没有子节点的最底层节点)
        },
      };
    },
    methods: {
      loadNode(node, resolve) { // 懒加载数据时载入的方法,只会执行一次
        if (node.level === 0) { // 初始的级数(最顶层)
          return resolve([{ name: 'region' }]); // 最顶层数据渲染为region
        }
        if (node.level > 1) return resolve([]);

        setTimeout(() => {
          const data = [{
            name: 'leaf',
            leaf: true
          }, {
            name: 'zone'
          }];
          resolve(data);
        }, 500);
      }
    }
  };
</script>

懒加载的方法会得到两个参数,一个是获取的当前节点(node)的信息(包括它的层级数据等);另一个是一个重新渲染当前节点下子节点的方法(resolve),它接收一个数组,该数组也会按照props中的映射关系,进行显示。

注意:懒加载的方法(:load)在初始载入时执行一次,而后每次点击节点前面的箭头获取子节点的时候再次触发;即使初始载入的数据有变换也不会再触发,点击展开子节点后再次点击收缩节点时也不会再触发!!

由于懒加载是一级一级往下获取,所以对每一级来说都要使用resolve来渲染它显示的子节点,如果该节点下没有显示的内容,它则会一直转圈,这个时候需要设置resolve返回一个空数组,这样如果它没有获取到子节点的内容则会在转圈之后显示为空(并去掉前面的向下展开的箭头),不会一直转圈:

return resolve([]);

现实场景:可以在通过node取到每一层的id,根据这个id调用接口得到数据。再通过resolve进行回显,回显的数据为这个id的子节点

二次封装

场景由于用到树形控件的地方很多,而且需要显示的数据都不一样,所以将树形控件再封装一层,然后根据外部组件传来的不同参数,进行树形图的不同显示。

思路:通过监听外部传入数据的变化,重新渲染树,完成不同数据的显示;但是:load只会初始加载一次并获取当前绑定树上node,如果后面监听数据的时候再次调用loadTree是获取不到它的node和resolve,所以会导致渲染失败。这个时候可以通过:data显示数据,当我们树上有节点时,就可以正常触发:load进行子节点的懒加载了。具体实现如下:

<template>
  <div class="org-tree">
    <el-tree :data="orgList" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" @node-click="nodeClick">
    </el-tree>
  </div>
</template>

<script>
    export default {
        name: 'Tree',
        props: {
            logicParams: { // 外部组件传入的参数
                type: Object
            }
        },
        watch: {
            logicParams: {
                handler(newVal) {
                    this.logicParams = newVal;
                    if (this.circleI >0) { // 限制首次加载时只显示loadTree加载的树,而不是:data和loadTree加载的都有
                        this.resetNode();
                    }
                    this.circleI++;
                },
                immediate: true,
                deep: true
            }
        },
        data() {
            return {
                defaultProps: {
                    children: 'children',
                    label: 'name',
                },
                circleI: 0,
                orgList: []
            }
        },
        methods: {
            // 懒加载加载方法,首次加载树的时候会被触发
            loadTree(node, resolve) {
              listByTree(this.logicParams).then(res => {
                // this.rootNode = node;
                // this.rootResolve = resolve;
                let rootMainResolve = resolve;
                let treedata = [];
                if(node.level == 0) {
                  return resolve([{ name: res.data[0].name }])
                }
                if (node.level == 1 ) {
                  treedata.push(res.data)
                  return resolve(...treedata)
                };
                if (node.data.isParent && node.data.pId != '') {
                  this.getChild(node.data, node.data.pId, rootMainResolve)
                }
                else  {
                  return resolve([]) // 防止不停转圈
                }
              })
            },
            getChild(data, type, resolve) { // 每个节点使用同一个接口获取子节点,只是传入的参数不同,将其抽出来
              this.logicChildDataParam.id =data.id;
              this.logicChildDataParam.type = type+ ";;;";
              listByTree(this.logicChildDataParam).then(res => {
                return resolve(res.data); 
              })
            },
            // 重新渲染树的根节点
            resetNode() {
              listByTree(this.logicParams).then(res => {
                  this.orgList = [res.data[0]]
              })
            },
        }
    }
</script>

数据回显

场景在懒加载的树上设置复选框,需要将之前添加好的懒加载选中的部分在表格的编辑中回显出来。

思路:由于懒加载的数据是一级一级获取的,所以可以利用default-expanded-keysdefault-checked-keys属性,将需要进行回显的节点在渲染树的时候就设置上去。(注意在使用这两个属性的时候)

<template>
  <div class="org-tree">
    <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" 
    show-checkbox  @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id">
    </el-tree>
  </div>
</template>
<script>
    export default {
        name: 'OrgTree',
        data() {
            return {
                defaultExpandKeys: [],
                defaultCheckedKeys: []
            }
        },
        methods: {
             // 当节点选中或取消选中的时候触发,可接受三个参数(具体见官网,本项目只用触发这个事件的时机)
            checkChange(data,state,childChecked) {
                let selectedAllList = [];
                let checkedList = [];
                // 选中所有的节点,包括半选节点(用作展开的节点)
                selectedAllList = this.$refs.tree.getCheckedNodes(false,true);
                // 选中所有全选节点,不包括半选(用作选中的节点)
                checkedList = this.$refs.tree.getCheckedNodes()// 触发父组件方法,将这两个数组传递出去,并在父组件的添加点击事件中调用添加方法,添加时需拿到这两个节点数组用作数据的回显
                this.$emit('selectorg', selectedAllList, checkedList);
            },
            loadTree(node, tree) {
                if (node.level == 0) {
                    let che = [];
                    let exp = [];
                    // 此处的checkedList和selectedAllList是通过调用编辑接口获取到的数据,为了方便理解写做与checkChange中一样,以下是伪代码
                    checkedList.forEach(el => { // 遍历选中的节点数组,拿到它们的id
                        if (el.id) {
                            che.push(el.id);
                        }
                    })
                    selectedAllList.forEach(el => { // 遍历包括半选的节点数组,拿到它们的id(可以将半选节点都筛出来,将所有半选节点作为展开的节点,如果嫌麻烦可以将全选的接节点也展开,不过这样可能会在树的数据量过多的情况下出现延迟和卡顿,影响性能)
                        if (el.id) {
                            exp.push(el.id);
                        }
                    })
                    this.defaultCheckedKeys = che; // 将得到的回显节点数组赋值给默认选中的数组
                    this.defaultExpandKeys = exp; // 将得到的展开节点数组赋值给默认展开的数组
                }
            },
        }
    }
</script>

回显问题

场景在使用懒加载进行数据回显时,当添加选中的数据里存在以下情况:一个父节点下的第一和第二个子节点同时被选中,回显时得到的默认选中的节点数组里也只有这两个节点的id,但是最终懒加载回显的数据是这个父节点下的所有子节点全都被选中(获取其他类似情况)。

分析:由于懒加载的树是异步加载的,树在判断子节点是否选中的时候可能由于选中的子节点,而导致其父节点因为关联而被计算判断出选中。

解决一:如果不需要父节点的复选框,或者父节点没有复选框,只有子节点有,或者不需要父子节点关联的情况下,可以使用check-strictly属性,断开父子之间的连接:

<template>
  <div class="org-tree">
    <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" 
    show-checkbox  @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id" :check-strictly="checkStrictly">
    </el-tree>
  </div>
</template>
<script>
    export default {
        name: 'OrgTree',
        data() {
            return {
                checkStrictly: true, // 根据需要在可不同的位置定义
            }
        }
</script>

解决二:对选中节点的回显不使用default-checked-keys,而是利用$nextTick和setCheckedKeys设置节点的选中,此方法必须设置node-key属性:

<template>
  <div class="org-tree">
    <el-tree ref="tree" :props="defaultProps" lazy :load="loadTree" :expand-on-click-node="false" 
    show-checkbox  @check-change="checkChange" :default-expanded-keys="defaultExpandKeys" :default-checked-keys="defaultCheckedKeys" node-key="id">
    </el-tree>
  </div>
</template>
<script>
    export default {
        name: 'OrgTree',
        data() {
            return {
                defaultExpandKeys: [],
                defaultCheckedKeys: []
            }
        },
        methods: {
             // 当节点选中或取消选中的时候触发,可接受三个参数(具体见官网,本项目只用触发这个事件的时机)
            checkChange(data,state,childChecked) {
                let selectedAllList = [];
                let checkedList = [];
                // 选中所有的节点,包括半选节点(用作展开的节点)
                selectedAllList = this.$refs.tree.getCheckedNodes(false,true);
                // 选中所有全选节点,不包括半选(用作选中的节点)
                checkedList = this.$refs.tree.getCheckedNodes()// 触发父组件方法,将这两个数组传递出去,并在父组件的添加点击事件中调用添加方法,添加时需拿到这两个节点数组用作数据的回显
                this.$emit('selectorg', selectedAllList, checkedList);
            },
            loadTree(node, tree) {
                if (node.level == 0) {
                    let che = [];
                    let exp = [];
                    // 此处的checkedList和selectedAllList是通过调用编辑接口获取到的数据,为了方便理解写做与checkChange中一样,以下是伪代码
                    selectedAllList.forEach(el => { // 遍历包括半选的节点数组,拿到它们的id
                        if (el.id) {
                            exp.push(el.id);
                        }
                    })
                    this.defaultExpandKeys = exp; // 将得到的展开节点数组赋值给默认展开的数组
                    this.$nextTick(() => { // 利用$nextTick更新节点
                        checkedList.forEach(el => { // 遍历选中的节点数组,拿到它们的id
                        if (el.id) {
                            che.push(el.id);
                        }
                    	})
                        this.$refs.tree.setCheckedKeys(che); // 手动赋值节点 
                    })
                }
            },
        }
    }
</script>

注意:该方法因为在load方法中,所以每次触发load的时候(每次首次下拉节点)都会重新获取一边数据,这会导致之前可能选中的节点又被回显节点重置了。

这种情况如果在层级没有超过2级时,可以通过设置一个计数器,让这个$nextTick只执行一次,但是如果层级过多,下层还是会出现全选的情况。这个问题我暂时无法避免,所以综合考虑下来还是采用每执行一次load就执行一次$nextTick,这样可以保证更深的层级节点能正确显示,对于回显编辑来说,只要做到先下拉节点再选择,就不会出错了。

复选框显隐

场景现在需要去掉所有的叶子节点(没有子节点的节点)的复选框,默认选中了父节点则其下所有子节点都 不做判断。

思路:由于element的tree并未提供这个属性或方法,需要我们自己手动去修改element内部代码,然后再重新打包,将打好的包替换自己项目中element里的ilb文件夹:

将对应版本的element源码下载下来,安装依赖并查看项目是否启动成功:

npm install
npm run dev

运行成功后找到packages/tree/src/tree-node修改源码:

<template>
  <div class="el-tree-node">
    ... 
      <!-- 找到复选框的位置,根据node.data中的某个字段判断(我是根据isParent判断)设置复选框的显隐 -->
      <el-checkbox
        v-if="showCheckbox"
        v-model="node.checked"
        :style="{ 'display': node.data.isParent || node.parent == null ?'':'none'}"
        :indeterminate="node.indeterminate"
        :disabled="!!node.disabled"
        @click.native.stop
        @change="handleCheckChange"
      >
      </el-checkbox>
    ...
  </div>
</template>

源码修改好之后,进行打包:

npm run dist

打包完成之后会得到新的lib文件夹,将其替换自己项目中的对应位置的lib文件夹即可。

注意:直接修改自己项目中的packages里的代码是无效的,因为项目中所运行的是lib文件夹里的,packages只是方便查看内部源码!

  • 15
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现 element plus el-tree 懒加载并默认展开第二层,可以使用 el-tree 的 lazy 属性和 default-expand-all 属性。具体实现步骤如下: 1. 设置 el-tree 的 lazy 属性为 true,表示开启懒加载。 2. 在 el-tree 的 data 属性中,只设置根节点和第一层节点的数据,其他节点的数据在需要时再通过 load 方法加载。 3. 设置 el-tree 的 default-expand-all 属性为 true,表示默认展开所有节点。 4. 使用 el-tree 的 node-click 事件来监听节点的点击操作,判断是否需要加载该节点的子节点数据。 下面是一个示例代码,实现了默认展开第二层并懒加载子节点数据的 el-tree: ``` <template> <el-tree :data="data" :lazy="true" :default-expand-all="true" @node-click="handleNodeClick" > </el-tree> </template> <script> export default { data() { return { data: [{ label: '根节点', id: 1, children: [{ label: '第一层节点1', id: 2, children: [] }, { label: '第一层节点2', id: 3, children: [] }] }] }; }, methods: { handleNodeClick(node) { // 判断是否需要加载子节点数据 if (node.level === 1 && node.childNodes.length === 0) { // 模拟异步加载数据 setTimeout(() => { node.loading = false; node.children = [{ label: '第二层节点1', id: 4, children: [] }, { label: '第二层节点2', id: 5, children: [] }]; }, 1000); node.loading = true; } } } } </script> ``` 在这个示例中,el-tree 的 data 属性只设置了根节点和第一层节点的数据,其他节点的数据将在需要时再加载。而在 el-tree 的事件处理中,我们监听了 node-click 事件,当用户点击某个节点时,判断该节点是否为第二层节点,并且该节点还没有加载过子节点数据,如果是,则通过 load 方法异步加载数据,并设置 loading 状态。待数据加载完成后,再将节点的 children 属性设置为新加载的子节点数据,同时将 loading 状态设置为 false,这样就完成了懒加载的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值