type TreeNodeId = string | number; type TreeNode = { id: TreeNodeId; parentId: TreeNodeId; sort?: number | null; children?: TreeNode[] | null; }; export function buildMenuTree(list: T[]) { const nodeMap = new Map(); const roots: T[] = []; list.forEach(item => { nodeMap.set(item.id, { ...item, children: [] }); }); nodeMap.forEach(node => { if (isRootParentId(node.parentId)) { roots.push(node); return; } const parent = nodeMap.get(node.parentId); if (!parent) { roots.push(node); return; } parent.children = [...(parent.children ?? []), node]; }); return sortMenuTree(roots); } export function collectDescendantIds>(nodes: T[], targetId: T['id']) { const target = findTreeNode(nodes, targetId); if (!target?.children?.length) { return []; } const ids: T['id'][] = []; walkTree(target.children, item => { ids.push(item.id as T['id']); }); return ids; } function sortMenuTree(nodes: T[]) { const sortedNodes = [...nodes].sort((prev, next) => Number(prev.sort ?? 0) - Number(next.sort ?? 0)); sortedNodes.forEach(node => { if (node.children?.length) { node.children = sortMenuTree(node.children as T[]); } }); return sortedNodes; } function findTreeNode>(nodes: T[], targetId: T['id']): T | null { for (const node of nodes) { if (node.id === targetId) { return node; } if (node.children?.length) { const target = findTreeNode(node.children as unknown as T[], targetId); if (target) { return target; } } } return null; } function isRootParentId(parentId: TreeNodeId) { return parentId === 0 || parentId === '0'; } function walkTree>(nodes: T[], callback: (node: T) => void) { for (const node of nodes) { callback(node); if (node.children?.length) { walkTree(node.children as unknown as T[], callback); } } }