elementUI-Tree-懒加载树的选中与回填

对于数据量较大的节点树,用elementUI的Tree自带的懒加载模式可以较为方便的进行展示。但同时使用懒加载与选中功能时,选中节点的回填就会比较麻烦。这里举一个完整的实例,同时记录一下回填需要注意的地方。

选中与保存
举1个可以保存选中节点的懒加载树例子:

效果:(注意看一下节点1-2)
在这里插入图片描述
具体代码:
HTML:
该例中用数组value保存已选节点、expandedNodes保存已展开节点(用于实现尽量保存下层节点、以及后期添加回填功能)。 levelProps是自行根据源数据结构定义的配置选项,本例中为
{label: ‘dicName’,
isLeaf: ‘isLeaf’,}。
该例中以id为节点的key,保存在value中的是对象数组,只是展示时为方便只展示节点名称。

<div>已选节点:{{value.map(item=> {return item.dicName})}}</div>
<div>已展开过(拿到过其下级)的节点:{{expandedNodes.map(item=> {return item.dicName})}}</div>
<el-tree
         ref="dicTree"
         :props="levelProps"
         highlight-current
         node-key="id"
         :load="getNodes"
         accordion
         show-checkbox
         lazy
         @node-expand="keepExpandedNode"
         @check="handleCheckChange">
 </el-tree>

//获取树节点
getNodes(node, resolve) {
	//这里应是一个异步从后端拿取数据的方法,这不是重点
    if (0 === node.level) {
      setTimeout(() => {
             let myRoot=[
                 {id: 1, dicName: "A ", isLeaf: false},
                 {id: 2, dicName: "B ", isLeaf: false},
                 {id: 3, dicName: "C ", isLeaf: false}
             ];
             resolve(myRoot);
         }, 1000); 
    } else {
      setTimeout(() => {
           let levelData=[
               {id: node.level*10+1, dicName: `${node.level}-1`, isLeaf: false, parentId: node.data.id},
               {id: node.level*10+2, dicName: `${node.level}-2`, isLeaf: false, parentId: node.data.id},
               {id: node.level*10+3, dicName: `${node.level}-3`, isLeaf: false, parentId: node.data.id}
           ];
           resolve(levelData);
           this.$nextTick(() => {
               //已选节点变成展开节点时、选值自动替换为下层节点
               if (node.checked) {
                   let list = this.$refs.dicTree.getCheckedNodes();
                   list = list.filter(mItem => {
                       return this.expandedNodes.every(item => {
                           return item.id !== mItem.id
                       })
                   });
                   this.value = list;
               }
           });
       }, 500);
    }
},
//保存展开过的节点
keepExpandedNode(nodeData, node, tree) {
    let newFlag = this.expandedNodes.every(item => {
        return item.id !== nodeData.id
    });
    if (newFlag && !nodeData.isLeaf) {
        this.expandedNodes.push(this.cloneObj(nodeData))
    }
}
//点击多选框->整理已选选项并保存
handleCheckChange(node, tree) {
	let list = this.cloneObj(tree.checkedNodes);  //tree.checkedNodes-未展开过的节点的子节点无法获取到
	list = list.filter(mItem => {
	    return this.expandedNodes.every(item => {
	        return item.id !== mItem.id
	    })
	});
	this.value = list;
},

这样一来,选中的节点对象就即时地保存到了value里。也可以处理后只保留id,因为回填时只需要用来做节点key的那个字段就够了。

回填
如果我们把value里的值保存下来,希望在下次重新渲染树时回填,事情就变得复杂起来了。
element-tree自带属性*“default-checked-keys”——“默认勾选的节点的 key 的数组”*,非懒加载的普通树可以直接把保存的key放到这里。
但是对于懒加载树,父节点并不知道你是否选过它的子节点,而对于未加载完数据的树枝的回填会出现很多bug。这里有几个本人踩坑重写多次后总结的要点:

<template>
  <div>
    <div>已选节点:{{
        value.map(item => {
          return item.dicName
        })
      }}
    </div>
    <div>已展开过(拿到过其下级)的节点:{{
        expandedNodes.map(item => {
          return item.dicName
        })
      }}
    </div>
    <el-tree
        ref="dicTree"
        :props="levelProps"
        highlight-current
        node-key="id"
        :load="getNodes"
        accordion
        show-checkbox
        @check="handleCheckChange"
        @node-expand="keepExpandedNode"
        :default-checked-keys="defaultCheckedNodes"
        :default-expanded-keys="defaultExpandedNodes"
        lazy>
    </el-tree>
  </div>
</template>

<script>
export default {
  name: "index.vue",
  data() {
    return {
      levelProps: {
        label: 'dicName',
        isLeaf: 'isLeaf'
      },
      defaultCheckedNodes:[],
      defaultExpandedNodes:[],
      expandedNodes: [{id: 1, dicName: 'A', isLeaf: false},
        {id: 12, dicName: 1 - 2, isLeaf: false, parentId: 1}], // 保存已展开节点
      value: [{id: 11, dicName: '1-1', isLeaf: false, parentId: 1},
        {id: 21, dicName: '2-1', isLeaf: false, parentId: 12},
        {id: 22, dicName: 2 - 2, isLeaf: false, parentId: 12},
        {id: 23, dicName: 2 - 3, isLeaf: false, parentId: 12}]
    };
  },
  methods: {
    //深度复制对象
    cloneObj(obj) {
      let newObj = {};
      if (typeof obj === "object") {
        if (obj instanceof Array) {
          newObj = [];
        }
        for (var key in obj) {
          let val = obj[key];
          newObj[key] =
              typeof val === "object" ? this.cloneObj(val) : val;
        }
        return newObj;
      } else {
        return obj;
      }
    },
//带查重的深复制(仅适用于单层数组)
    cloneWithCheck(Arr) {
      let newArr = [];
      if (Arr.length > 0) {
        newArr.push(this.cloneObj(Arr[0]));
        for (let i = 1; i < Arr.length; i++) {
          let newFlag = newArr.every(item => {
            return item.id !== Arr[i].id
          });
          if (newFlag) {
            newArr.push(this.cloneObj(Arr[i]));
          }
        }
      }
      return newArr;
    },
//获取树节点
    getNodes(node, resolve) {
      if (0 === node.level) {
        ///如果需要多次回填,该初始化必需
        this.meaningExpandedNum = 0;
        this.defaultCheckedNodes = [];
        ///回填展开节点(这里我略去了从后端拿数据回填到this.expandedNodes里的代码。实际上这里如果能直接拿数据放到defaultExpandedNodes里也行)
        let expandedNodes = this.cloneWithCheck(this.expandedNodes);
        this.defaultExpandedNodes = expandedNodes.map(item => {
          return item.id
        });
        setTimeout(() => {
          let myRoot = [
            {id: 1, dicName: "A ", isLeaf: false},
            {id: 2, dicName: "B ", isLeaf: false},
            {id: 3, dicName: "C ", isLeaf: false}
          ];
          resolve(myRoot);
          ///注意回填要在树渲染后才生效
          this.$nextTick(() => {
            ///没展开过节点,则直接在根节点层级回填
            ///(这里我略去了在页面开始渲染前就从后端拿数据回填到this.value里的代码。实际上这里如果能直接拿数据放到defaultCheckedNodes里也行)
            if (0 === this.defaultExpandedNodes.length) {
              this.defaultCheckedNodes = this.value.map(item => {
                return item.id
              });
            }
          });
        }, 500);
      } else {
        setTimeout(() => {
          let levelData = [
            {id: node.level * 10 + 1, dicName: `${node.level}-1`, isLeaf: false, parentId: node.data.id},
            {id: node.level * 10 + 2, dicName: `${node.level}-2`, isLeaf: false, parentId: node.data.id},
            {id: node.level * 10 + 3, dicName: `${node.level}-3`, isLeaf: false, parentId: node.data.id}
          ];
          resolve(levelData);
          this.$nextTick(() => {
            ///已选节点变成展开节点时、选值自动替换为下层节点;这是为了下次回填做的准备,回填过程中用不到
            if (node.checked) {
              let list = this.$refs.dicTree.getCheckedNodes();
              list = list.filter(mItem => {
                return this.expandedNodes.every(item => {
                  return item.id !== mItem.id
                })
              });
              this.value = list;
            }
            ///回填时保证在全部渲染后再回填(选中节点的回填时机是核心难点)
            this.meaningExpandedNum++;
            if (this.meaningExpandedNum === this.defaultExpandedNodes.length) {
              ///(这里我略去了在页面开始渲染前就从后端拿数据回填到this.value里的代码。实际上这里如果能直接拿数据放到defaultCheckedNodes里也行)
              this.defaultCheckedNodes = this.value.map(item => {
                return item.id
              });
            }
          });
        }, 500);
      }
    },
    //保存展开过的节点
    keepExpandedNode(nodeData, node, tree) {
      let newFlag = this.expandedNodes.every(item => {
        return item.id !== nodeData.id
      });
      if (newFlag && !nodeData.isLeaf) {
        this.expandedNodes.push(this.cloneObj(nodeData))
      }
    },
//点击多选框->整理已选选项并保存
    handleCheckChange(node, tree) {
      let list = this.cloneObj(tree.checkedNodes);  //tree.checkedNodes-未展开过的节点的子节点无法获取到
      list = list.filter(mItem => {
        return this.expandedNodes.every(item => {
          return item.id !== mItem.id
        })
      });
      this.value = list;
    },


  },
  created() {
  }
}
</script>

<style scoped>

</style>

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱技术的大仙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值