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

ReactJS基于antd将对象数组转为无限层级可折叠树节点

无限层级可折叠树节点实现方案

需求规则

  • 基于扁平对象数组的Level(节点层级,从0开始计数,支持无限嵌套)、SubField(节点路径)字段构建嵌套树结构
  • 节点展示文本取ServiceHeaderBlockTitle字段值
  • 每个节点支持三类操作:编辑标题、新增子层级、删除当前层级
  • 当节点IsServiceBlock属性为true时,禁止在该节点下新增子层级
  • 所有带下级节点的父节点支持展开/折叠交互

预期层级结构示例

/*
-Level0(SubField("/")
--Level1(SubField"/1/")
--Level1(SubField"/2/")
----Level2(SubField"/2/1")
------Level3(SubField"/2/1/1")
------Level3(SubField"/2/1/2")
--------Level3(SubField"/2/1/2/1")
--Level1(SubField"/3/")
----Level2(SubField"/3/1")
----Level2(SubField"/3/2")
----Level2(SubField"/3/3")
--Level1(SubField"/4/")
----Level2(SubField"/4/1")
----Level2(SubField"/4/2")

... and so on
*/

第一步:扁平数组转嵌套树工具方法

核心逻辑通过SubField路径匹配父节点,无需额外依赖parentId字段:

function buildTree(flatList) {
  // 按Level升序排序,保证父节点优先处理
  const sortedList = [...flatList].sort((a, b) => a.Level - b.Level);
  const tree = [];
  // 哈希表存储所有节点,key为SubField,实现O(1)查找
  const nodeMap = new Map();

  sortedList.forEach(item => {
    const node = {
      ...item,
      children: [],
      expanded: true // 默认展开状态,可按需修改
    };
    nodeMap.set(item.SubField, node);

    if (item.Level === 0) {
      // 根节点直接入树
      tree.push(node);
    } else {
      // 计算父节点路径:移除当前路径最后一段,例如/2/1/的父路径为/2/
      const pathParts = item.SubField.split('/').filter(Boolean);
      pathParts.pop();
      const parentPath = '/' + (pathParts.length ? pathParts.join('/') + '/' : '');
      const parentNode = nodeMap.get(parentPath);
      parentNode?.children.push(node);
    }
  });

  return tree;
}

第二步:可折叠树核心交互逻辑(原生JS实现,框架可直接迁移逻辑)

// 递归渲染单个节点
function renderNode(node) {
  const nodeEl = document.createElement('div');
  nodeEl.className = 'tree-node';
  nodeEl.style.paddingLeft = `${node.Level * 20}px`;

  // 折叠/展开按钮
  const toggleBtn = document.createElement('span');
  toggleBtn.className = 'toggle-btn';
  if (node.children.length > 0) {
    toggleBtn.textContent = node.expanded ? '▼' : '▶';
    toggleBtn.addEventListener('click', () => {
      node.expanded = !node.expanded;
      renderTree();
    });
  } else {
    toggleBtn.textContent = '○';
  }
  nodeEl.appendChild(toggleBtn);

  // 节点标题
  const titleEl = document.createElement('span');
  titleEl.className = 'node-title';
  titleEl.textContent = node.ServiceHeaderBlockTitle;
  nodeEl.appendChild(titleEl);

  // 操作按钮组
  const actionGroup = document.createElement('span');
  actionGroup.className = 'action-group';

  // 编辑按钮
  const editBtn = document.createElement('button');
  editBtn.textContent = '编辑';
  editBtn.addEventListener('click', () => {
    const newTitle = prompt('请输入新标题', node.ServiceHeaderBlockTitle);
    if (newTitle?.trim()) {
      node.ServiceHeaderBlockTitle = newTitle.trim();
      renderTree();
    }
  });
  actionGroup.appendChild(editBtn);

  // 新增子节点按钮:IsServiceBlock为true时不渲染
  if (!node.IsServiceBlock) {
    const addBtn = document.createElement('button');
    addBtn.textContent = '新增子级';
    addBtn.addEventListener('click', () => {
      const newTitle = prompt('请输入新节点标题');
      if (newTitle?.trim()) {
        // 生成新节点的路径、层级属性
        const childIndex = node.children.length + 1;
        const newSubField = `${node.SubField}${childIndex}/`;
        const newNode = {
          ServiceHeaderBlockId: Date.now(), // 临时ID,对接后端时替换为接口返回值
          ServiceHeaderBlockTitle: newTitle.trim(),
          SubField: newSubField,
          Level: node.Level + 1,
          IsServiceBlock: false,
          ResourceDescriptionId: null,
          IsActive: true,
          children: [],
          expanded: true
        };
        node.children.push(newNode);
        renderTree();
      }
    });
    actionGroup.appendChild(addBtn);
  }

  // 删除按钮:根节点不允许删除
  if (node.Level !== 0) {
    const delBtn = document.createElement('button');
    delBtn.textContent = '删除';
    delBtn.addEventListener('click', () => {
      if (confirm(`确定要删除节点「${node.ServiceHeaderBlockTitle}」及其所有子节点吗?`)) {
        const pathParts = node.SubField.split('/').filter(Boolean);
        pathParts.pop();
        const parentPath = '/' + (pathParts.length ? pathParts.join('/') + '/' : '');
        const parentNode = findNodeByPath(treeData, parentPath);
        if (parentNode) {
          parentNode.children = parentNode.children.filter(child => child.ServiceHeaderBlockId !== node.ServiceHeaderBlockId);
          renderTree();
        }
      }
    });
    actionGroup.appendChild(delBtn);
  }

  nodeEl.appendChild(actionGroup);

  // 节点展开时递归渲染子节点
  const childrenContainer = document.createElement('div');
  childrenContainer.className = 'children-container';
  if (node.expanded && node.children.length > 0) {
    node.children.forEach(child => {
      childrenContainer.appendChild(renderNode(child));
    });
  }
  nodeEl.appendChild(childrenContainer);

  return nodeEl;
}

// 辅助方法:根据路径查找对应节点
function findNodeByPath(nodes, path) {
  for (const node of nodes) {
    if (node.SubField === path) return node;
    if (node.children.length) {
      const found = findNodeByPath(node.children, path);
      if (found) return found;
    }
  }
  return null;
}

// 全局树数据、渲染入口
let treeData = [];
function renderTree(containerSelector = '#tree-container') {
  const container = document.querySelector(containerSelector);
  container.innerHTML = '';
  treeData.forEach(rootNode => {
    container.appendChild(renderNode(rootNode));
  });
}

// 初始化调用:传入原始扁平数组即可生成可交互树
// treeData = buildTree(yourRawDataArray);
// renderTree();

附:原始参考数据集

[
  {
    "ServiceHeaderBlockId": 1,
    "ServiceHeaderBlockTitle": "Header",
    "SubField": "/",
    "Level": 0,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 57,
    "ServiceHeaderBlockTitle": "Project Administration",
    "SubField": "/1/",
    "Level": 1,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 58,
    "ServiceHeaderBlockTitle": "Innovation",
    "SubField": "/2/",
    "Level": 1,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 76,
    "ServiceHeaderBlockTitle": "Commercialization",
    "SubField": "/3/",
    "Level": 1,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 77,
    "ServiceHeaderBlockTitle": "Quality/Food Safety Audit",
    "SubField": "/4/",
    "Level": 1,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 78,
    "ServiceHeaderBlockTitle": "Co-Man sourcing",
    "SubField": "/3/1/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 79,
    "ServiceHeaderBlockTitle": "Co-Man Vetting",
    "SubField": "/3/2/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 80,
    "ServiceHeaderBlockTitle": "Line Trail",
    "SubField": "/3/3/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 81,
    "ServiceHeaderBlockTitle": "Gap Analysis",
    "SubField": "/4/1/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 82,
    "ServiceHeaderBlockTitle": "HACCP",
    "SubField": "/4/2/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 84,
    "ServiceHeaderBlockTitle": "Product Development",
    "SubField": "/2/2/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 59,
    "ServiceHeaderBlockTitle": "Project Charter",
    "SubField": "/1/1/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 60,
    "ServiceHeaderBlockTitle": "Internal Ideation",
    "SubField": "/1/2/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 61,
    "ServiceHeaderBlockTitle": "White Space Opportunity",
    "SubField": "/2/1/",
    "Level": 2,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 62,
    "ServiceHeaderBlockTitle": "Territory Mapping/Platform Design",
    "SubField": "/2/1/1/",
    "Level": 3,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 70,
    "ServiceHeaderBlockTitle": "Creative Council Session",
    "SubField": "/2/1/2/",
    "Level": 3,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 71,
    "ServiceHeaderBlockTitle": "Trends Report",
    "SubField": "/2/1/3/",
    "Level": 3,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 85,
    "ServiceHeaderBlockTitle": "Product Development Administration",
    "SubField": "/2/2/1/",
    "Level": 3,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 103,
    "ServiceHeaderBlockTitle": "Brand/Project Immersion",
    "SubField": "/1/1/1/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 10,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 104,
    "ServiceHeaderBlockTitle": "Project Charter Preparation",
    "SubField": "/1/1/2/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 23,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 105,
    "ServiceHeaderBlockTitle": "Timeline",
    "SubField": "/1/1/3/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 23,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 106,
    "ServiceHeaderBlockTitle": "Project Organization-Data Collection",
    "SubField": "/1/1/4/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 38,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 107,
    "ServiceHeaderBlockTitle": "Brand/Project Immersion",
    "SubField": "/1/2/1/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 10,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 108,
    "ServiceHeaderBlockTitle": "Meeting-Internal",
    "SubField": "/1/2/2/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 23,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 109,
    "ServiceHeaderBlockTitle": "Meeting Report",
    "SubField": "/1/2/3/",
    "Level": 3,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 26,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 110,
    "ServiceHeaderBlockTitle": "Brand/Project Immersion",
    "SubField": "/2/1/1/1/",
    "Level": 4,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 10,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 111,
    "ServiceHeaderBlockTitle": "Meeting-Internal",
    "SubField": "/2/1/1/2/",
    "Level": 4,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 10,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 115,
    "ServiceHeaderBlockTitle": "Creative Council Brief",
    "SubField": "/2/1/3/1/",
    "Level": 4,
    "IsServiceBlock": true,
    "ResourceDescriptionId": 10,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 86,
    "ServiceHeaderBlockTitle": "Process Technology Assessment ",
    "SubField": "/2/2/1/1/",
    "Level": 4,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 87,
    "ServiceHeaderBlockTitle": "Analogue Cutting-Internal",
    "SubField": "/2/2/1/2/",
    "Level": 4,
    "IsServiceBlock": false,
    "ResourceDescriptionId": null,
    "IsActive": true
  },
  {
    "ServiceHeaderBlockId": 114,
    "ServiceHeaderBlockTitle": "
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:42:28