el-tree踩坑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了el-tree踩坑相关的知识,希望对你有一定的参考价值。

参考技术A 新增角色时,需要给角色分配一些菜单权限,菜单是分层级的,比如父级菜单 > 子菜单。子菜单也可能嵌套多层子菜单。举个例子来说,假如 A是父菜单,B是A的子菜单,C也是A的子菜单,如果新增的时候,我只勾选了B菜单,不勾选C菜单,这个时候A菜单就是半选的状态。这个时候如果我们直接提交到后台,那么就会只把B菜单的ID提交,而不包括A菜单的ID,这样,这个角色下的账号在登录后,就只显示B菜单,而不能显示B的父级菜单A,这肯定是不行的。

当我们新增成功后,可能需要修改某个角色的菜单权限,通常我们是使用 default-checked-keys 来回显这个角色的菜单权限的。

还拿刚才的例子来说,之前我们新增的时候,我们把A菜单的ID和B这个子菜单的ID一起提交给后台了,我们点击修改的时候,获取到这个角色的菜单权限里肯定是包含A菜单的ID的,我们回显的时候,正确的情况应该是A是半选状态,B是勾选状态,而C是未勾选状态,但是 el-tree 这个时候因为A菜单的ID的存在,通过 default-checked-keys 把A菜单以及它下边的所有的子菜单都变成勾选状态,这明显是错误的。怎么解决呢?

16 el-tree 保存树的 选择状态, 展开状态

前言

需求 来自于一些实际的场景 

这里 仅仅是一个 样例来实现 这一部分需求 

这部分 处理也相对比较简单, 就直接 展示 代码了 

1. 节点 增删改查 的实现(一种基于模型的实现, 一种基于 el-tree api 的实现)

2. 保存 el-tree 的 选择状态 

3. 保存 el-tree 的 展开状态 

样例代码

如下 增删改查两种实现方式, 注释掉的是基于 el-tree 的 api 的使用 

<template>

  <div>
    <div style="width: 20%; float: left;">
      (\\#-_-)\\┯━┯
    </div>

    <div style="width: 20%; float: left;">
      <el-tree
        ref="treeRef"
        :data="tree.nodeList"
        show-checkbox
        node-key="id"
        :default-checked-keys="tree.defaultCheckedKeys"
        :default-expanded-keys="tree.defaultExpandedKeys"
        :check-on-click-node="true"
        :props="tree.defaultProps"
        @node-expand="handleNodeExpand"
        @node-collapse="handleNodeCollapse"
      />
    </div>

    <div style="width: 15%; float: left;">
      <span> 新增 </span>
      <el-select v-model="newNode.parentId" filterable placeholder="请选择父节点">
        <el-option v-for="item in treeNodeList" :key="item.id" :label="item.name" :value="item.id"/>
      </el-select>
      <el-input placeholder="please input id" v-model="newNode.id">
        <template slot="prepend">key</template>
      </el-input>
      <el-input placeholder="please input name" v-model="newNode.name">
        <template slot="prepend">name</template>
      </el-input>
      <el-button @click="handleNewNode">提交</el-button>
    </div>

    <div style="width: 15%; float: left;">
      <span> 更新 </span>
      <el-select v-model="updateNode.id" filterable placeholder="请选择节点">
        <el-option v-for="item in treeNodeList" :key="item.id" :label="item.name" :value="item.id"/>
      </el-select>
      <el-input placeholder="please input name" v-model="updateNode.name">
        <template slot="prepend">name</template>
      </el-input>
      <el-button @click="handleUpdateNode">提交</el-button>
    </div>

    <div style="width: 15%; float: left;">
      <span> 移除 </span>
      <el-select v-model="removeNode.id" filterable placeholder="请选择节点">
        <el-option v-for="item in treeNodeList" :key="item.id" :label="item.name" :value="item.id"/>
      </el-select>
      <el-button @click="handleRemoveNode">提交</el-button>
    </div>

  </div>

</template>

<script>

  export default 
    name: 'ElTreeCrud',
    computed: 
      treeNodeList: function () 
        let collector = []
        this.collectNodeRecursed(this.tree.nodeList, collector)
        return collector
      
    ,
    data () 
      return 
        newNode: 
          parentId: 'backend',
          id: '',
          name: '',
        ,
        updateNode: 
          id: 'backend',
          name: '',
        ,
        removeNode: 
          id: 'backend',
        ,
        tree: 
          nodeList: [
            id: 'backend',
            name: '后端',
            children: [
              
                id: 'backend1',
                name: '后端1',
                children: []
              , 
                id: 'backend2',
                name: '后端2',
                children: []
              
            ]
          , 
            id: 'frontend',
            name: '前端',
            children: [
              
                id: 'frontend1',
                name: '前端1',
                children: []
              , 
                id: 'frontend2',
                name: '前端2',
                children: []
              
            ]
          , 
            id: 'preprocess',
            name: '数据',
            children: [
              
                id: 'preprocess1',
                name: '数据1',
                children: []
              , 
                id: 'preprocess2',
                name: '数据2',
                children: []
              
            ]
          ],
          defaultExpandedKeys: [],
          defaultCheckedKeys: ['backend1', 'preprocess1'],
          defaultProps: 
            children: 'children',
            label: 'name'
          
        
      
    ,
    mounted () 
      window.addEventListener('beforeunload', e => this.beforeLeaveFunc(e))
      this.tree.defaultExpandedKeys = this.treeNodeList.map(ele => ele.id)

      // check if already stored
      // todo, if defaultCheckedKeys is not blank at data's init, then specified node will always be checked
      let treeNodeCheckedKeysStr = sessionStorage.getItem('treeNodeCheckedKeys')
      if (treeNodeCheckedKeysStr) 
        this.tree.defaultCheckedKeys = JSON.parse(treeNodeCheckedKeysStr)
        // JSON.parse(treeNodeCheckedKeysStr).forEach(ele => this.tree.defaultCheckedKeys.push(ele))
      

      let expandedKeysStr = sessionStorage.getItem('expandedKeys')
      if (expandedKeysStr) 
        this.tree.defaultExpandedKeys = JSON.parse(expandedKeysStr)
      
    ,
    created () 

    ,
    methods: 
      // beforeLeaveFunc
      beforeLeaveFunc () 
        let treeRef = this.$refs.treeRef
        let checkedKeys = treeRef.getCheckedKeys()
        sessionStorage.removeItem('treeNodeCheckedKeys')
        sessionStorage.setItem('treeNodeCheckedKeys_tmp', JSON.stringify(checkedKeys))
        if (checkedKeys.length > 0) 
          sessionStorage.setItem('treeNodeCheckedKeys', JSON.stringify(checkedKeys))
        

        // let treePropRoot = treeRef.root
        // let expandedKeys = []
        // this.expandedKeys(treePropRoot, expandedKeys)
        // sessionStorage.removeItem('expandedKeys')
        // sessionStorage.setItem('expandedKeys_tmp', JSON.stringify(expandedKeys))
        // if(expandedKeys.length > 0) 
        //   sessionStorage.setItem('expandedKeys', JSON.stringify(expandedKeys))
        // 
      ,
      // curd
      handleNewNode () 
        if (!this.newNode.id || !this.newNode.name) 
          this.$message.error('please input id and name')
          return
        
        let targetNode = this.lookUpNode(this.tree.nodeList, this.newNode.id)
        if (targetNode) 
          this.$message.error('update node\\'s with id ' + this.newNode.id + ' already exists')
          return
        

        let newNode = 
          id: this.newNode.id,
          name: this.newNode.name,
          children: []
        

        // let treeRef = this.$refs.treeRef
        // treeRef.append(newNode, treeRef.getNode(this.newNode.parentId))
        // this.$message.success(' add new node ' + this.newNode.name + ' at parentNode ' + this.lookUpNode(this.tree.nodeList, this.newNode.parentId).name + ' success')

        let parentNode = this.lookUpNode(this.tree.nodeList, this.newNode.parentId)
        parentNode.children.push(newNode)
      ,
      handleUpdateNode () 
        if (!this.updateNode.id || !this.updateNode.name) 
          this.$message.error('please input id and name')
          return
        
        let targetNode = this.lookUpNode(this.tree.nodeList, this.updateNode.id)
        if (!targetNode) 
          this.$message.error('update node\\'s with id ' + this.newNode.id + ' does not exists')
          return
        

        // let treeRef = this.$refs.treeRef
        // targetNode.name = this.updateNode.name
        // treeRef.updateKeyChildren(this.updateNode.id, targetNode)

        targetNode.name = this.updateNode.name
      ,
      handleRemoveNode () 
        // let treeRef = this.$refs.treeRef
        // let targetNode = this.lookUpNode(this.tree.nodeList, this.removeNode.id)
        // if (!targetNode) 
        //   this.$message.error('update node\\'s with id ' + this.removeNode.id + ' does not exists')
        //   return
        // 
        // treeRef.remove(targetNode)

        let parentNode = this.lookUpParentNode(this.tree.nodeList, null, this.removeNode.parentId)
        let idxOfNode = parentNode.children.map(ele => ele.id).indexOf(this.removeNode.id)
        parentNode.children.splice(idxOfNode, 1)
      ,
      handleNodeExpand (node, nodeProp, treeNode) 
        console.log('handleNodeExpand')
        let treeRef = this.$refs.treeRef
        let treePropRoot = treeRef.root

        let expandedKeys = []
        this.expandedKeys(treePropRoot, expandedKeys)
        sessionStorage.removeItem('expandedKeys')
        sessionStorage.setItem('expandedKeys_tmp', JSON.stringify(expandedKeys))
        if (expandedKeys.length > 0) 
          sessionStorage.setItem('expandedKeys', JSON.stringify(expandedKeys))
        
      ,
      handleNodeCollapse (node, nodeProp, treeNode) 
        // update current node's expand
        nodeProp.expanded = false
        this.handleNodeExpand(node, nodeProp, treeNode)
      ,
      // assist methods
      collectNodeRecursed (nodeList, collector) 
        if (!nodeList) 
          return null
        

        for (let idx in nodeList) 
          let childNode = nodeList[idx]
          collector.push(id: childNode.id, name: childNode.name)

          if (childNode.children) 
            this.collectNodeRecursed(childNode.children, collector)
          
        
      ,
      lookUpNode (nodeList, id) 
        if (!nodeList) 
          return null
        

        for (let idx in nodeList) 
          let childNode = nodeList[idx]
          if (id === childNode.id) 
            return childNode
          

          if (childNode.children) 
            let result = this.lookUpNode(childNode.children, id)
            if (result) 
              return result
            
          
        

        return null
      ,
      lookUpParentNode (nodeList, parentNode, id) 
        if (!nodeList) 
          return null
        

        for (let idx in nodeList) 
          let childNode = nodeList[idx]
          if (id === childNode.id) 
            if (!parentNode) 
              parentNode = 
                children : this.tree.nodeList
              
            
            return parentNode
          

          if (childNode.children) 
            let result = this.lookUpParentNode(childNode.children, childNode, id)
            if (result) 
              return result
            
          
        

        return null
      ,
      expandedKeys (nodeFromRef, collector) 
        if (nodeFromRef.expanded) 
          collector.push(nodeFromRef.data.id)
        
        // 如果不是 root 节点, 并且没有展开, 不继续处理
        if ((nodeFromRef.id !== 0) && (!nodeFromRef.expanded)) 
          return
        

        if (nodeFromRef.childNodes) 
          for (let idx in nodeFromRef.childNodes) 
            let childNode = nodeFromRef.childNodes[idx]
            this.expandedKeys(childNode, collector)
          
        
      
    
  

</script>

<!-- Add 'scoped' attribute to limit CSS to this component only -->
<style scoped>
</style>

选中状态, 展开状态 的持久化和初始化 

刷新页面的时候 保存选中状态 

节点展开/收缩的时候 保存展开状态 

初始化的时候 初始化 选中状态, 展开状态 

展示效果

增删改查的演示 

保存 选中状态, 展开状态 

 

我们看一下 sessionStorage 中存放的 expandedKeys, chekcedKeys 的数据, 是没有问题的

 刷新之后的状态, 可以看到这里 "后端1" 的状态是没有保存下来的, 这是另外的一个问题 

这个我们下一个文章 再来讲解, 需要 调试到 element 的代码 

以上是关于el-tree踩坑的主要内容,如果未能解决你的问题,请参考以下文章

16 el-tree 保存树的 选择状态, 展开状态

el-tree超出之后显示问题

el-tree父子节点的选中取消

el-tree 全部打开状态 不要折叠

elementUi的el-tree实现单选

element el-tree循环遍历树形结构,并动态赋值disabled属性