使用 HTML + JavaScript 实现组织架构图

文章目录

一、组织架构图

在企业管理系统或团队协作平台中,组织结构图是一种重要的可视化工具,能够清晰地展示公司的层级关系和人员分布。本文将详细介绍如何使用 HTML、CSS 和 JavaScript 从零开始构建一个具有交互功能的组织结构图。

二、效果演示

本项目呈现了一个典型的公司组织架构,从上级开始逐层向下展示各个部门及员工职位。用户可以通过点击节点上的 +/- 按钮来展开或收起子节点,也可以通过顶部的"展开/收起全部"按钮一键控制所有节点的状态。默认情况下,部分节点被设置为收起状态以避免图表过于庞大,用户可以根据需要自由展开查看详细信息。

三、系统分析

1.页面结构

系统主要包括以下几个功能区域:

1.1 操作区域

该区域位于主内容上方,提供全局操作按钮,目前只有一个"展开/收起全部"的功能按钮,用户可以一键控制所有节点的展开状态。

html 复制代码
<div class="actions">
  <button onclick="toggleAll()">展开/收起全部</button>
</div>

1.2 组织结构图区域

这是实际渲染组织结构图的核心区域,使用 #orgChart 作为根节点容器。

html 复制代码
<div class="org-chart-container">
  <div class="org-chart" id="orgChart"></div>
</div>

2 核心功能实现

2.1 数据结构定义

组织结构图的数据源是一个嵌套的 json 数据,每个节点包含 id、name、position 和 children 属性。这种树形数据结构非常适合表示层级关系,并且易于遍历和渲染。

javascript 复制代码
var orgData = {
  id: 1,
  name: "张伟",
  position: "CEO",
  children: [
    {
      id: 2,
      name: "李娜",
      position: "技术总监",
      children: [
        // ... 更多子节点
      ]
    },
    // ... 更多同级节点
  ]
};

2.2 节点渲染逻辑

使用递归方式渲染整个组织结构图,通过递归遍历数据结构,为每个节点创建对应的DOM元素,并根据是否有子节点添加相应的CSS类名。

javascript 复制代码
function renderOrgChart(data, container) {
  // 创建当前节点
  var nodeEl = document.createElement('div');
  nodeEl.className = 'node';
  nodeEl.dataset.id = data.id;

  // 如果有子节点,添加标识类
  if (data.children && data.children.length > 0) {
    nodeEl.classList.add('has-children');
  }

  // 设置节点内容
  nodeEl.innerHTML = `
      <div class="title">${data.name}</div>
      <div class="position">${data.position}</div>
      <div class="toggle-btn"></div>
    `;
  // 为切换按钮添加点击事件
  if (data.children && data.children.length > 0) {
    const toggleBtn = nodeEl.querySelector('.toggle-btn');
    toggleBtn.addEventListener('click', function(e) {
      e.stopPropagation();
      nodeEl.classList.toggle('collapsed');
    });
  }

  container.appendChild(nodeEl);

  // 如果有子节点,创建子节点容器
  if (data.children && data.children.length > 0) {
    var childrenEl = document.createElement('div');
    childrenEl.className = 'children';

    data.children.forEach((child, index) => {
      var childWrapper = document.createElement('div');
      childWrapper.className = 'child';

      // 添加特殊类名用于样式控制
      if (data.children.length === 1) {
        childWrapper.classList.add('only-child');
      } else if (index === 0) {
        childWrapper.classList.add('first-child');
      } else if (index === data.children.length - 1) {
        childWrapper.classList.add('last-child');
      }

      renderOrgChart(child, childWrapper);
      childrenEl.appendChild(childWrapper);
    });

    container.appendChild(childrenEl);
  }

  // 默认收起部分节点
  if (data.id === 2 || data.id === 4) {
    nodeEl.classList.add('collapsed');
  }
}

2.3节点展开/收起功能

通过给节点添加/移除 .collapsed 类来控制子节点的显示与隐藏。CSS 中利用相邻兄弟选择器来隐藏被收起节点的子级容器;同时,通过伪元素 ::after 在节点下方显示 +/- 按钮,提升用户体验。(相关事件在"节点渲染逻辑"中"为切换按钮添加点击事件"绑定)

2.4 全部展开/收起

首先判断当前是否所有节点都处于收起状态,然后统一执行相反的操作。

javascript 复制代码
function toggleAll() {
  var nodes = document.querySelectorAll('.node.has-children');
  var isAllCollapsed = Array.from(nodes).every(node => node.classList.contains('collapsed'));

  nodes.forEach(node => {
    if (isAllCollapsed) {
      node.classList.remove('collapsed');
    } else {
      node.classList.add('collapsed');
    }
  });
}

四、扩展建议

  • 节点编辑功能:支持双击节点进入编辑状态,修改人员信息
  • 拖拽排序:引入拖拽库,实现组织架构的可视化调整
  • 导出功能:支持将组织结构图导出为图片或PDF格式
  • 主题定制:提供更多颜色方案和样式选项,满足不同企业的品牌形象需求

五、完整代码

git地址:https://gitee.com/ironpro/hjdemo/blob/master/orgchart/index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>组织结构图</title>
  <style>
      * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
      }
      body {
          background-color: #f5f5f5;
          min-height: 100vh;
          padding: 20px;
      }
      .container {
          max-width: 980px;
          margin: 0 auto;
          background: white;
          border-radius: 15px;
          box-shadow: 0 20px 40px rgba(0,0,0,0.1);
          overflow: hidden;
      }
      .header {
          background: #45a049;
          color: white;
          padding: 20px;
          text-align: center;
      }
      .header h1 {
          font-size: 24px;
          font-weight: 500;
      }
      .main {
          padding: 20px;
      }
      .org-chart-container {
          display: flex;
          justify-content: center;
          overflow: auto;
          padding: 20px 0;
      }
      .org-chart {
          display: flex;
          flex-direction: column;
          align-items: center;
          position: relative;
      }

      .node {
          background-color: white;
          border: 2px solid #4CAF50;
          border-radius: 8px;
          padding: 15px;
          width: 120px;
          text-align: center;
          box-shadow: 0 2px 5px rgba(0,0,0,0.1);
          position: relative;
          cursor: pointer;
          transition: all 0.3s ease;
          display: inline-block;
      }

      .node:hover {
          box-shadow: 0 4px 10px rgba(0,0,0,0.5);
      }

      .node .title {
          font-weight: bold;
          color: #333;
          margin-bottom: 5px;
      }

      .node .position {
          color: #666;
          font-size: 14px;
      }

      .children {
          display: flex;
          justify-content: center;
          margin-top: 40px;
          position: relative;
      }

      .children::before {
          content: '';
          position: absolute;
          top: -40px;
          left: 50%;
          width: 2px;
          height: 20px;
          background-color: #4CAF50;
      }

      .child {
          position: relative;
          padding: 0 10px;
          text-align: center;
      }

      .child::before {
          content: '';
          position: absolute;
          top: -20px;
          left: 50%;
          width: 2px;
          height: 20px;
          background-color: #4CAF50;
      }

      .child::after {
          content: '';
          position: absolute;
          top: -20px;
          left: 0;
          width: 100%;
          height: 2px;
          background-color: #4CAF50;
      }

      .first-child::after {
          width: 50%;
          left: 50%;
      }

      .last-child::after {
          width: 50%;
      }

      .only-child::after {
          width: 0;
      }

      .actions {
          text-align: center;
      }

      button {
          background-color: #4CAF50;
          color: white;
          border: none;
          padding: 10px 15px;
          border-radius: 4px;
          cursor: pointer;
          margin: 0 5px;
          font-size: 14px;
      }

      button:hover {
          background-color: #45a049;
      }

      .node.collapsed + .children {
          display: none;
      }
      .node .toggle-btn::after {
          content: '-';
          position: absolute;
          bottom: -10px;
          left: 50%;
          transform: translateX(-50%);
          width: 20px;
          height: 20px;
          background-color: #4CAF50;
          color: white;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          font-weight: bold;
          font-size: 16px;
          z-index: 9;
          cursor: pointer;
      }
      .node.collapsed .toggle-btn::after {
          content: '+';
      }
      .node:not(.has-children) .toggle-btn {
          display: none;
      }
  </style>
</head>
<body>
<div class="container">
  <div class="header">
    <h1>组织结构图</h1>
  </div>
  <div class="main">
    <div class="actions">
      <button onclick="toggleAll()">展开/收起全部</button>
    </div>
    <div class="org-chart-container">
      <div class="org-chart" id="orgChart"></div>
    </div>
  </div>
</div>
<script>
  var orgData = {
    id: 1,
    name: "张伟",
    position: "CEO",
    children: [
      {
        id: 2,
        name: "李娜",
        position: "技术总监",
        children: [
          {
            id: 4,
            name: "王强",
            position: "前端主管",
            children: [
              { id: 7, name: "陈晨", position: "前端工程师" },
              { id: 8, name: "刘芳", position: "UI设计师" }
            ]
          },
          {
            id: 5,
            name: "赵磊",
            position: "后端主管",
            children: [
              { id: 9, name: "孙丽", position: "Java工程师" },
              { id: 10, name: "周鹏", position: "数据库专家" }
            ]
          }
        ]
      },
      {
        id: 3,
        name: "马华",
        position: "市场总监",
        children: [
          {
            id: 6,
            name: "吴敏",
            position: "市场经理",
            children: [
              { id: 11, name: "郑涛", position: "市场专员" },
              { id: 12, name: "黄娟", position: "品牌专员" }
            ]
          }
        ]
      }
    ]
  };

  // 渲染组织结构图
  function renderOrgChart(data, container) {
    // 创建当前节点
    var nodeEl = document.createElement('div');
    nodeEl.className = 'node';
    nodeEl.dataset.id = data.id;

    // 如果有子节点,添加标识类
    if (data.children && data.children.length > 0) {
      nodeEl.classList.add('has-children');
    }

    // 设置节点内容
    nodeEl.innerHTML = `
        <div class="title">${data.name}</div>
        <div class="position">${data.position}</div>
        <div class="toggle-btn"></div>
      `;
    // 为切换按钮添加点击事件
    if (data.children && data.children.length > 0) {
      const toggleBtn = nodeEl.querySelector('.toggle-btn');
      toggleBtn.addEventListener('click', function(e) {
        e.stopPropagation();
        nodeEl.classList.toggle('collapsed');
      });
    }

    container.appendChild(nodeEl);

    // 如果有子节点,创建子节点容器
    if (data.children && data.children.length > 0) {
      var childrenEl = document.createElement('div');
      childrenEl.className = 'children';

      data.children.forEach((child, index) => {
        var childWrapper = document.createElement('div');
        childWrapper.className = 'child';

        // 添加特殊类名用于样式控制
        if (data.children.length === 1) {
          childWrapper.classList.add('only-child');
        } else if (index === 0) {
          childWrapper.classList.add('first-child');
        } else if (index === data.children.length - 1) {
          childWrapper.classList.add('last-child');
        }

        renderOrgChart(child, childWrapper);
        childrenEl.appendChild(childWrapper);
      });

      container.appendChild(childrenEl);
    }

    // 默认收起部分节点
    if (data.id === 2 || data.id === 4) {
      nodeEl.classList.add('collapsed');
    }
  }

  // 初始化组织结构图
  function initOrgChart() {
    var container = document.getElementById('orgChart');
    container.innerHTML = '';
    renderOrgChart(orgData, container);
  }

  // 展开/收起全部节点
  function toggleAll() {
    var nodes = document.querySelectorAll('.node.has-children');
    var isAllCollapsed = Array.from(nodes).every(node => node.classList.contains('collapsed'));

    nodes.forEach(node => {
      if (isAllCollapsed) {
        node.classList.remove('collapsed');
      } else {
        node.classList.add('collapsed');
      }
    });
  }

  initOrgChart();
</script>
</body>
</html>
相关推荐
军军君011 小时前
Three.js基础功能学习十六:智能黑板实现实例三
前端·javascript·css·vue.js·3d·前端框架·threejs
海上彼尚2 小时前
SVG矢量图形快速入门
前端·html5
嗷o嗷o2 小时前
Android App Functions 深入理解
前端
qq_20815408852 小时前
瑞树6代流程分析
javascript·python
UXbot2 小时前
AI原型设计工具评测:从创意到交互式Demo,5款产品全面解析
前端·ui·设计模式·ai·ai编程·原型模式
落魄江湖行2 小时前
硅基同事埋的坑,我用2小时才填平:Nuxt 4 路由踩坑:可选参数 [[id]] 与 [id] 的区别
前端
一勺菠萝丶2 小时前
管理后台使用手册在线预览与首次登录引导弹窗实现
java·前端·数据库
军军君012 小时前
Three.js基础功能学习十四:智能黑板实现实例一
前端·javascript·css·typescript·前端框架·threejs·智能黑板
小村儿2 小时前
连载05-Claude Skill 不是抄模板:真正管用的 Skill,都是从实战里提炼出来的
前端·后端·ai编程