使用HTML+JavaScript實現(xiàn)具有交互功能的組織架構(gòu)圖
一、組織架構(gòu)圖
在企業(yè)管理系統(tǒng)或團(tuán)隊協(xié)作平臺中,組織結(jié)構(gòu)圖是一種重要的可視化工具,能夠清晰地展示公司的層級關(guān)系和人員分布。本文將詳細(xì)介紹如何使用 HTML、CSS 和 JavaScript 從零開始構(gòu)建一個具有交互功能的組織結(jié)構(gòu)圖。
二、效果演示
本項目呈現(xiàn)了一個典型的公司組織架構(gòu),從上級開始逐層向下展示各個部門及員工職位。用戶可以通過點擊節(jié)點上的 +/- 按鈕來展開或收起子節(jié)點,也可以通過頂部的“展開/收起全部”按鈕一鍵控制所有節(jié)點的狀態(tài)。默認(rèn)情況下,部分節(jié)點被設(shè)置為收起狀態(tài)以避免圖表過于龐大,用戶可以根據(jù)需要自由展開查看詳細(xì)信息。



三、系統(tǒng)分析
1.頁面結(jié)構(gòu)
系統(tǒng)主要包括以下幾個功能區(qū)域:
1.1 操作區(qū)域
該區(qū)域位于主內(nèi)容上方,提供全局操作按鈕,目前只有一個“展開/收起全部”的功能按鈕,用戶可以一鍵控制所有節(jié)點的展開狀態(tài)。
<div class="actions"> <button onclick="toggleAll()">展開/收起全部</button> </div>
1.2 組織結(jié)構(gòu)圖區(qū)域
這是實際渲染組織結(jié)構(gòu)圖的核心區(qū)域,使用 #orgChart 作為根節(jié)點容器。
<div class="org-chart-container"> <div class="org-chart" id="orgChart"></div> </div>
2 核心功能實現(xiàn)
2.1 數(shù)據(jù)結(jié)構(gòu)定義
組織結(jié)構(gòu)圖的數(shù)據(jù)源是一個嵌套的 json 數(shù)據(jù),每個節(jié)點包含 id、name、position 和 children 屬性。這種樹形數(shù)據(jù)結(jié)構(gòu)非常適合表示層級關(guān)系,并且易于遍歷和渲染。
var orgData = {
id: 1,
name: "張偉",
position: "CEO",
children: [
{
id: 2,
name: "李娜",
position: "技術(shù)總監(jiān)",
children: [
// ... 更多子節(jié)點
]
},
// ... 更多同級節(jié)點
]
};2.2 節(jié)點渲染邏輯
使用遞歸方式渲染整個組織結(jié)構(gòu)圖,通過遞歸遍歷數(shù)據(jù)結(jié)構(gòu),為每個節(jié)點創(chuàng)建對應(yīng)的DOM元素,并根據(jù)是否有子節(jié)點添加相應(yīng)的CSS類名。
function renderOrgChart(data, container) {
// 創(chuàng)建當(dāng)前節(jié)點
var nodeEl = document.createElement('div');
nodeEl.className = 'node';
nodeEl.dataset.id = data.id;
// 如果有子節(jié)點,添加標(biāo)識類
if (data.children && data.children.length > 0) {
nodeEl.classList.add('has-children');
}
// 設(shè)置節(jié)點內(nèi)容
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);
// 如果有子節(jié)點,創(chuàng)建子節(jié)點容器
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);
}
// 默認(rèn)收起部分節(jié)點
if (data.id === 2 || data.id === 4) {
nodeEl.classList.add('collapsed');
}
}
2.3節(jié)點展開/收起功能
通過給節(jié)點添加/移除 .collapsed 類來控制子節(jié)點的顯示與隱藏。CSS 中利用相鄰兄弟選擇器來隱藏被收起節(jié)點的子級容器;同時,通過偽元素 ::after 在節(jié)點下方顯示 +/- 按鈕,提升用戶體驗。(相關(guān)事件在“節(jié)點渲染邏輯”中“為切換按鈕添加點擊事件”綁定)
2.4 全部展開/收起
首先判斷當(dāng)前是否所有節(jié)點都處于收起狀態(tài),然后統(tǒng)一執(zhí)行相反的操作。
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');
}
});
}
四、擴(kuò)展建議
- 節(jié)點編輯功能:支持雙擊節(jié)點進(jìn)入編輯狀態(tài),修改人員信息
- 拖拽排序:引入拖拽庫,實現(xiàn)組織架構(gòu)的可視化調(diào)整
- 導(dǎo)出功能:支持將組織結(jié)構(gòu)圖導(dǎo)出為圖片或PDF格式
- 主題定制:提供更多顏色方案和樣式選項,滿足不同企業(yè)的品牌形象需求
五、完整代碼
git地址:https://gitee.com/ironpro/hjdemo/blob/master/orgchart/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>組織結(jié)構(gòu)圖</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>組織結(jié)構(gòu)圖</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: "技術(shù)總監(jiān)",
children: [
{
id: 4,
name: "王強(qiáng)",
position: "前端主管",
children: [
{ id: 7, name: "陳晨", position: "前端工程師" },
{ id: 8, name: "劉芳", position: "UI設(shè)計師" }
]
},
{
id: 5,
name: "趙磊",
position: "后端主管",
children: [
{ id: 9, name: "孫麗", position: "Java工程師" },
{ id: 10, name: "周鵬", position: "數(shù)據(jù)庫專家" }
]
}
]
},
{
id: 3,
name: "馬華",
position: "市場總監(jiān)",
children: [
{
id: 6,
name: "吳敏",
position: "市場經(jīng)理",
children: [
{ id: 11, name: "鄭濤", position: "市場專員" },
{ id: 12, name: "黃娟", position: "品牌專員" }
]
}
]
}
]
};
// 渲染組織結(jié)構(gòu)圖
function renderOrgChart(data, container) {
// 創(chuàng)建當(dāng)前節(jié)點
var nodeEl = document.createElement('div');
nodeEl.className = 'node';
nodeEl.dataset.id = data.id;
// 如果有子節(jié)點,添加標(biāo)識類
if (data.children && data.children.length > 0) {
nodeEl.classList.add('has-children');
}
// 設(shè)置節(jié)點內(nèi)容
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);
// 如果有子節(jié)點,創(chuàng)建子節(jié)點容器
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);
}
// 默認(rèn)收起部分節(jié)點
if (data.id === 2 || data.id === 4) {
nodeEl.classList.add('collapsed');
}
}
// 初始化組織結(jié)構(gòu)圖
function initOrgChart() {
var container = document.getElementById('orgChart');
container.innerHTML = '';
renderOrgChart(orgData, container);
}
// 展開/收起全部節(jié)點
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>
總結(jié)
到此這篇關(guān)于使用HTML+JavaScript實現(xiàn)具有交互功能的組織架構(gòu)圖的文章就介紹到這了,更多相關(guān)JS組織架構(gòu)圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript獲取指定區(qū)間范圍隨機(jī)數(shù)的方法
下面小編就為大家?guī)硪黄猨avascript獲取指定區(qū)間范圍隨機(jī)數(shù)的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
使用Promise封裝小程序wx.request的實現(xiàn)方法
這篇文章主要介紹了使用Promise封裝小程序wx.request的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
詳解Webpack + ES6 最新環(huán)境搭建與配置
這篇文章主要介紹了詳解Webpack + ES6 最新環(huán)境搭建與配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
Javascript點擊按鈕隨機(jī)改變數(shù)字與其顏色
這篇文章主要介紹了Javascript點擊按鈕隨機(jī)改變數(shù)字和其字體的顏色,實現(xiàn)后的效果很不錯,具有一定的參考價值,有需要的可以參考借鑒,下面來一起看看。2016-09-09
JavaScript中concat復(fù)制數(shù)組方法淺析
在本篇文章里小編給大家總結(jié)了關(guān)于JavaScript中concat復(fù)制數(shù)組方法知識點,有需要的朋友們可以學(xué)習(xí)下。2019-01-01
JS實現(xiàn)點擊復(fù)選框變更DIV顯示狀態(tài)的示例代碼
下面小編就為大家分享一篇JS實現(xiàn)點擊復(fù)選框變更DIV顯示狀態(tài)的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12

