import _ from 'lodash'; import type { Key } from 'react'; /** * 树形数组扁平,并构建父子关系 * @param {Array} tree 树形数组 * @param {String} parentKey 要构建的父级id字段 * @param {String} selfKey 当前id字段 * @returns {Array} */ export const tree2FlatArray = ( tree: any[], parentKey: string = 'parentId', selfKey: string = 'id', ) => { const run = (l: any[], pId?: any) => { return l.reduce((acc: any[], cur: any) => { const { children, ...item } = cur; item[parentKey] = pId; return [...acc, item, ...run(children || [], item[selfKey])]; }, []); }; return run(tree, null); }; /** * 扁平数组转树形数组 * @param {Array} flat 扁平数组 * @param {String} parentKey 父级id字段 * @param {String} selfKey 当前id字段 */ export const flatArray2Tree = ( flat: any[], parentKey: string = 'parentId', selfKey: string = 'id', ) => { const map = _.keyBy(flat, selfKey); Object.keys(map).forEach((id) => { const item = map[id]; const pId = item[parentKey]; if (pId) { const parent = map[pId]; if (parent) { parent.children = parent.children || []; parent.children.push(item); } } }); return Object.values(map).filter((item) => !item[parentKey]); }; /** 无根的扁平数组转为树结构 */ export const rootlessFlat2Tree = ( flat: any[], parentKey: string = 'parentId', selfKey: string = 'id', ) => { const map = _.keyBy(flat, selfKey); const rootlessIds: string[] = []; Object.keys(map).forEach((id) => { const item = map[id]; const pId = item[parentKey]; if (pId) { const parent = map[pId]; if (parent) { parent.children = parent.children || []; parent.children.push(item); } else { rootlessIds.push(pId); } } }); return Object.values(map).filter((v) => { const pId = v[parentKey]; return !pId || rootlessIds.includes(pId); }); }; /** * 获取树形数组叶子节点 * @param {Array} tree 树形数组 * @param {Function} leafHandler 叶子节点过滤函数,不传则保留所有叶子节点 */ export const treeLeafNode = ( tree: T[], leafHandler?: (item: Omit) => boolean, ) => { const run = (l: T[], res: Omit[]) => { l?.forEach((n) => { const { children, ...item } = n; if (Array.isArray(children) && children.length) { run(children, res); } else if (leafHandler) { if (leafHandler(item)) res.push(item); } else { res.push(item); } }); return res; }; return run(tree, []); }; /** * 获取节点在树中的父ID链(路径) * @param tree 树形结构数组 * @param nodeId 目标节点ID * @param key id字段名称 * @returns 父ID链数组(从根节点到目标节点的路径ID) */ export const parentIdChain = (tree: any[], nodeId: Key, key: string = 'id'): Key[] => { const run = (nodes: any[], path: Key[]): Key[] | null => { for (const node of nodes) { const { children } = node; const id = node[key]; // 当前路径包括当前节点 const currentPath = [...path, id]; // 找到目标节点 if (id === nodeId) return currentPath; // 递归搜索子节点 if (Array.isArray(children) && children.length) { const result = run(children, currentPath); if (result) return result; } } return null; // 未找到 }; return run(tree, []) || []; }; /** 为树形数组创建ID */ export const treeCreateId = (tree: any[], key: string = 'id'): any[] => { const run = (t: any[]) => { t?.forEach((item) => { item[key] = item[key] ?? -Number(_.uniqueId()) - 100; if (Array.isArray(item.children) && item.children.length) { run(item.children); } }); return t; }; return run(tree) || []; }; /** * 根据叶子节点构建树,只保留叶子节点的直接祖先节点 * @param {Key[]} leafKeys 叶子节点的key * @param {any[]} tree 树结构 * @param {string} key id字段名称 */ export const leafBuildTree = (leafKeys: Key[], tree: any[], key: string = 'id') => { const flats = tree2FlatArray(tree, '_parentId', key); const leafChain = leafKeys .reduce((pre, cur) => { const chain = parentIdChain(tree, cur, key); const all = [...pre, ...chain]; return [...new Set(all)]; }, []) .map((v) => flats.find((item: any) => item[key] === v)); return flatArray2Tree(leafChain, '_parentId', key); };