树形数据-业务开发方法
经常在开发中使用的一些方法!!!
列表转换树结构
/**
* 列表转换树结构
* @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) {
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;
}
}
遍历树数据节点,查找符合条件的节点
/**
* 遍历树数据节点,查找符合条件的节点
* @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);
}
树形结构数据去除空的children
/**
* 树形结构数据去除空的children
* @param {*} [] tree 树形结构数组
* @param {*} field 子节点数组标识
*/
export function treeDchildren(tree = [], field = 'children') {
return tree.map((item) => {
if (item[field]?.length > 0) {
treeDchildren(item[field], field);
} else {
delete item[field];
}
return item;
});
}