Tree组件数据结构相互转换 flat <=> nested

2019-04-29

需求背景:
在做pms的菜单权限这一块内容时,需要从一个完整的菜单树中勾选要分配的菜单选项,把数据保存到数据库里,需要用时再取出数据渲染成菜单树。但是我们知道tree组件在勾选时通过getCheckeNodes()/getCheckedKeys()之类的方法拿到的是数组,要想再还原成菜单树就很困难了,各种循环遍历想想都头疼。所以,我们需要这样一种数据结构:既能保存还原菜单树的所有数据,又方便操作。

“flat is better than nested”,把tree转换成arr,根据pid可以方便的还原树结构,根据id可以方便的过滤处理

tree组件使用的数据是嵌套式结构
nested结构

data_tree: [{
        id: 1,
        label: '一级 1',
        children: [{
          id: 4,
          label: '二级 1-1',
          children: [{
            id: 9,
            label: '三级 1-1-1'
          }, {
            id: 10,
            label: '三级 1-1-2'
          }]
        }]
      }, {
        id: 2,
        label: '一级 2',
        children: [{
          id: 5,
          label: '二级 2-1'
        }, {
          id: 6,
          label: '二级 2-2'
        }]
      }, {
        id: 3,
        label: '一级 3',
        children: [{
          id: 7,
          label: '二级 3-1'
        }, {
          id: 8,
          label: '二级 3-2'
        }]
      }]

扁平化的arr格式会更好操作
flat结构

data_arr: [
        { id: 1, label: '一级 1', flag: false, pid: 0 },
        { id: 6, label: '二级 1-1', flag: false, pid: 1 },
        { id: 12, label: '三级 1-1-1', flag: false, pid: 6 },
        { id: 14, label: '四级 1-1-1-1', flag: false, pid: 12 },
        { id: 15, label: '五级 1-1-1-1-1', flag: false, pid: 14 },
        { id: 7, label: '二级 1-2', flag: false, pid: 1 },
        { id: 13, label: '三级 1-2-1', flag: false, pid: 7 },
        { id: 2, label: '一级 2', flag: false, pid: 0 },
        { id: 8, label: '二级 2-1', flag: false, pid: 2 },
        { id: 9, label: '二级 2-2', flag: false, pid: 2 },
        { id: 3, label: '一级 3', flag: false, pid: 0 },
        { id: 10, label: '二级 3-1', flag: false, pid: 3 },
        { id: 11, label: '二级 3-2', flag: true, pid: 3 }
      ]

Tree2Arr

把tree格式转换成arr格式的方法

treeToList(tree) {
      var queen = []
      var out = []
      queen = queen.concat(tree)
      while (queen.length) {
        var first = queen.shift()
        if (first.children) {
          first.children.forEach(item => {
            item.pid = first.id
          })
          queen = queen.concat(first.children)
          delete first['children']
        }

        out.push(first)
      }
      return out
    }

根据指定的节点id向上找到所有节点

 // 根据指定当前id向上找到所有父节点
    familyTree(arr, cid) {
      var temp = []
      var forFn = function(arr, cid) {
        for (var i = 0; i < arr.length; i++) {
          var item = arr[i]
          if (item.id === cid) {
            temp.push(item)
            forFn(arr, item.pid)
          }
        }
      }
      forFn(arr, cid)
      return temp
    },

找到某一父节点下的所有子节点

    // 找到某一父节点下的所有子节点
    sonsTree(arr, id) {
      var temp = []
      var lev = 0
      var forFn = function(arr, id, lev) {
        for (var i = 0; i < arr.length; i++) {
          var item = arr[i]
          if (item.pid === id) {
            item.lev = lev
            temp.push(item)
            forFn(arr, item.id, lev + 1)
          }
        }
      }
      forFn(arr, id, lev)
      return temp
    }

根据id从arr中递归过滤出当前节点至顶级节点数据,并保存到新arr中,将来用于渲染成新的tree

data(){
	return {
		treeCheckedArr: []
	}
}

treeChecked(cid) {
      this.data_arr.forEach(item => {
        if (item.id === cid) {
          this.checked_arr.splice(0, 0, item)
          if (item.pid) {
            this.treeCheckedArr(item.pid)
          }
        }
      })
    }

Arr2Tree

把arr格式转换成tree格式的方法

	// 将数据写成树结构
    listToTree(data) {
      // 删除 所有 children,以防止多次调用
      data.forEach(function(item) {
        delete item.children
      })

      // 将数据存储为 以 id 为 KEY 的 map 索引数据列
      var map = {}
      data.forEach(function(item) {
        map[item.id] = item
      })
      //        console.log(map);

      var val = []
      data.forEach(function(item) {
        // 以当前遍历项,的pid,去map对象中找到索引的id
        var parent = map[item.pid]

        // 如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
        if (parent) {
          (parent.children || (parent.children = [])).push(item)
        } else {
          // 如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级
          val.push(item)
        }
      })
      return val
    }

arr转tree时,恢复选中状态的方法(如果有之前getCheckedKeys获得的key数组,可以省略下面步骤)

getCheckedKeys(data) {
      data.forEach(item => {
        // 判断flag是否为true,并且有无子集,element-tree中父级设置选中的话,下面的子集也就全选中了,所有得排除掉
        if (item.flag === true && !item.children) {
          this.checkedKeys.push(item.id)
        }
        if (item.children && item.children.length) {
          // 如果存在子集,递归调用该方法
          this.getCheckedKeys(item.children)
        }
      })
    }

标题:Tree组件数据结构相互转换 flat <=> nested
作者:fish2018
地址:http://www.devopser.org/articles/2019/04/29/1556524023536.html