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
地址:https://www.devopser.org/articles/2019/04/29/1556524023536.html