/**
* 树结构数据处理函数
* @module $ui/utils/tree
*/
import {cloneDeep} from '$ui/utils/util'
/**
* 列表转换树结构
* @param {Object[]} list 列表数据,约定字段名称: id/parentId/children
* @param {*} [parentId=null] 父节点的值
* @param {string} [idKey=id] id字段名称
* @param {string} [parentIdKey=parentId] parentId字段名称
* @param {boolean} [withRoot=false] 有根节点id时是否保留返回根节点
* @return {Array}
*/
export function create(list = [], parentId = null, idKey = 'id', parentIdKey = 'parentId', withRoot = false) {
const _list = cloneDeep(list)
const temp = new Map(), tree = []
_list.forEach(item => {
temp.set(item[idKey], {...item})
})
for (const item of temp.values()) {
if (item[parentIdKey] === parentId || (!parentId && !item[parentIdKey])) {
tree.push(item)
} else {
const parent = temp.get(item[parentIdKey])
if (parent) {
if (!parent.children) {
parent.children = []
}
parent.children.push(item)
}
}
}
if (parentId && withRoot) {
const target = list.find((item) => {
return item[idKey] === parentId
})
if (target) {
target.children = tree
return [target]
} else {
return tree
}
} else {
return tree
}
}
/**
* 树结构数据相辅助函数
* @module $ui/utils/tree
*/
/**
* 遍历树数据节点,查找符合条件的节点
* @param {Array|Object} data 数据树,如 {id:1, children:[{id:2}]}
* @param {Boolean} isFindOne 是否只找最先符合条件的一个
* @param {Function} fn 查找回调函数,回调参数:item 节点,index节点当前兄弟节点中的索引,data 查找的数据树,函数返回true表示符合条件
* @param {string} [field=children] 子级字段名称
* @returns {Array|Object} 查找结果,isFindOne为true时返回Object, false时返回Array
*/
export function find(data = [], isFindOne, fn, field = 'children') {
let result = []
data = Array.isArray(data) ? data : [data]
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i],
checked = fn(item, i, data),
children = item[field]
if (checked) {
result.push(item)
if (isFindOne) break
}
if (children) {
const child = find(children, isFindOne, fn, field)
if (child) result = result.concat(child)
}
}
return isFindOne ? result[0] || null : result
}
/**
* 查找节点在树结构数组的路径
* @param {Array|Object} data 树数据数组, 如 {id:1, children:[{id:2}]}
* @param {Function} fn 查找回调函数,回调参数:item 节点,index节点当前兄弟节点中的索引,data 查找的数据树,函数返回true表示符合条件
* @param {string} [field=children] 子级字段名称
* @return {Array} 节点路径数组
*/
export function findPath(data, fn, field = 'children') {
const path = []
function find(array, parent) {
parent && path.push(parent)
for (let i = 0, len = array.length; i < len; i++) {
const item = array[i],
checked = fn(item, i, array),
children = item[field]
// 找到,记录路径,退出循环
if (checked) {
path.push(item)
return true
}
if (children && children.length > 0) {
// 在子级找到,退出循环,自己没有,删除记录的父级
if (find(children, item)) {
return true
} else {
path.pop()
}
}
}
}
find([].concat(data))
return path
}
const _flat = function(tree, map = {}, idProp = 'id', childrenProp = 'children') {
tree.forEach((item) => {
map[item[idProp]] = item
if (item[childrenProp]) {
_flat(item.children, map)
}
})
}
/**
* 将树状数据摊平为一维数组
* @param {Array} tree 树数据数组, 如 [{id:1, children:[{id:2}]}]
* @param {string} [idProp=id] 节点唯一编号字段名称
* @param {string} [childrenProp=children] 子级字段名称
* @return {Array} 节点路径数组
*/
export function treeRevert(tree, idProp = 'id', childrenProp = 'children') {
const map = {}
_flat(tree, map, idProp, childrenProp)
return Object.values(map)
}