You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现非二叉树(企业组织架构)的上下左右导航功能?

解决组织架构图非二叉树导航问题

别再用坐标遍历了,这种方式天生不匹配嵌套树结构。换个思路:基于节点的层级关系和兄弟节点顺序实现导航,核心是先构建节点关系映射表,再通过映射快速定位目标节点。以下是具体落地方案:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:06:01