如何实现非二叉树(企业组织架构)的上下左右导航功能?
解决组织架构图非二叉树导航问题
别再用坐标遍历了,这种方式天生不匹配嵌套树结构。换个思路:基于节点的层级关系和兄弟节点顺序实现导航,核心是先构建节点关系映射表,再通过映射快速定位目标节点。以下是具体落地方案:
1. 先构建节点关系映射
遍历一次树结构,生成三个关键映射表,帮你快速找到任意节点的父节点、兄弟节点和自身信息:
// 先定义节点类型 interface IHierarchyNode { name: string; position: string; email: string; id: string; children: IHierarchyNode[]; } // 映射表结构 interface NodeMaps { nodeMap: Record<string, IHierarchyNode>; // 按id存节点 parentMap: Record<string, IHierarchyNode | null>; // 节点的父节点 siblingsMap: Record<string, IHierarchyNode[]>; // 节点的兄弟节点列表 } // 递归遍历生成映射 function buildNodeMaps(nodes: IHierarchyNode[], parent: IHierarchyNode | null = null): NodeMaps { const nodeMap: Record<string, IHierarchyNode> = {}; const parentMap: Record<string, IHierarchyNode | null> = {}; const siblingsMap: Record<string, IHierarchyNode[]> = {}; nodes.forEach(node => { nodeMap[node.id] = node; parentMap[node.id] = parent; siblingsMap[node.id] = nodes; // 当前节点的兄弟就是父节点的children数组 // 递归处理子节点 const childMaps = buildNodeMaps(node.children, node); Object.assign(nodeMap, childMaps.nodeMap); Object.assign(parentMap, childMaps.parentMap); Object.assign(siblingsMap, childMaps.siblingsMap); }); return { nodeMap, parentMap, siblingsMap }; } // 初始化映射(用你的data数据) const { nodeMap, parentMap, siblingsMap } = buildNodeMaps(data);
2. 实现导航逻辑函数
基于映射表,编写符合你需求的navigateHierarchy函数:
let currentNodeId = "5555"; // 默认选中根节点Sarah function navigateHierarchy(key: 'downKey' | 'upKey' | 'rightKey' | 'leftKey'): string | null { const currentNode = nodeMap[currentNodeId]; if (!currentNode) return null; switch (key) { // 向下:进入当前节点的第一个子节点 case 'downKey': if (currentNode.children.length > 0) { currentNodeId = currentNode.children[0].id; return currentNodeId; } break; // 向上:返回父节点 case 'upKey': const parent = parentMap[currentNodeId]; if (parent) { currentNodeId = parent.id; return currentNodeId; } break; // 向右:切换到下一个兄弟节点 case 'rightKey': const siblings = siblingsMap[currentNodeId]; const currentIndex = siblings.findIndex(n => n.id === currentNodeId); if (currentIndex !== -1 && currentIndex < siblings.length - 1) { currentNodeId = siblings[currentIndex + 1].id; return currentNodeId; } break; // 向左:切换到上一个兄弟节点 case 'leftKey': const leftSiblings = siblingsMap[currentNodeId]; const leftIndex = leftSiblings.findIndex(n => n.id === currentNodeId); if (leftIndex > 0) { currentNodeId = leftSiblings[leftIndex - 1].id; return currentNodeId; } break; } return currentNodeId; // 没有可导航节点时返回当前id }
测试你给出的示例路径:
navigateHierarchy('downKey') // 5556(Michael Davis) navigateHierarchy('rightKey') // 5560(Robert Smith) navigateHierarchy('downKey') // 5561(Emily Wilson) navigateHierarchy('rightKey') // 5562(Daniel Brown) navigateHierarchy('rightKey') // 5563(Sophie Turner)
完全符合预期效果。
3. 在React组件中集成导航
维护选中状态,绑定键盘事件和按钮点击,同时自定义节点实现高亮:
import { useState, useEffect } from 'react'; import { Tree, TreeNode } from 'react-organizational-chart'; const OrgChart = () => { const [currentNodeId, setCurrentNodeId] = useState("5555"); const { nodeMap, parentMap, siblingsMap } = buildNodeMaps(data); // 监听键盘按键 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { let key: 'downKey' | 'upKey' | 'rightKey' | 'leftKey' | null = null; switch (e.key) { case 'ArrowDown': key = 'downKey'; break; case 'ArrowUp': key = 'upKey'; break; case 'ArrowRight': key = 'rightKey'; break; case 'ArrowLeft': key = 'leftKey'; break; default: return; } e.preventDefault(); navigate(key); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [currentNodeId]); // 组件内部的导航方法,更新选中状态 const navigate = (key: 'downKey' | 'upKey' | 'rightKey' | 'leftKey') => { const currentNode = nodeMap[currentNodeId]; if (!currentNode) return; let newId = currentNodeId; switch (key) { case 'downKey': if (currentNode.children.length) newId = currentNode.children[0].id; break; case 'upKey': const parent = parentMap[currentNodeId]; if (parent) newId = parent.id; break; case 'rightKey': const siblings = siblingsMap[currentNodeId]; const idx = siblings.findIndex(n => n.id === currentNodeId); if (idx < siblings.length - 1) newId = siblings[idx + 1].id; break; case 'leftKey': const leftSibs = siblingsMap[currentNodeId]; const leftIdx = leftSibs.findIndex(n => n.id === currentNodeId); if (leftIdx > 0) newId = leftSibs[leftIdx - 1].id; break; } setCurrentNodeId(newId); }; // 自定义节点组件,支持高亮选中状态 const CustomNode = ({ node }: { node: IHierarchyNode }) => { const isSelected = node.id === currentNodeId; return ( <div className={`org-node ${isSelected ? 'selected' : ''}`} style={{ padding: '12px', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: isSelected ? '#e3f2fd' : 'white', cursor: 'pointer' }} > <div>{node.name}</div> <div style={{ fontSize: '12px', color: '#666' }}>{node.position}</div> </div> ); }; // 递归渲染树节点 const renderTreeNodes = (nodes: IHierarchyNode[]) => { return nodes.map(node => ( <TreeNode key={node.id} label={<CustomNode node={node} />}> {node.children.length > 0 && renderTreeNodes(node.children)} </TreeNode> )); }; return ( <div> {/* 导航控制按钮 */} <div style={{ marginBottom: '20px', gap: '8px', display: 'flex' }}> <button onClick={() => navigate('upKey')}>↑ 返回上一级</button> <button onClick={() => navigate('downKey')}>↓ 进入下一级</button> <button onClick={() => navigate('leftKey')}>← 上一个兄弟</button> <button onClick={() => navigate('rightKey')}>→ 下一个兄弟</button> </div> {/* 组织架构图 */} <Tree lineWidth={'2px'} lineColor={'#ccc'}> {renderTreeNodes(data)} </Tree> </div> ); }; export default OrgChart;
方案优势
- 完全适配非二叉树:不管每个节点有多少子节点/兄弟节点,都能正确导航
- 性能高效:映射表只初始化一次,导航操作基本是O(1)级别
- 逻辑清晰:容易扩展额外功能(比如循环导航、跳转至特定节点等)
内容的提问来源于stack exchange,提问作者daniel blythe
相关产品推荐
相关产品推荐

