简单树形菜单

引言

在网页开发中,树形菜单是一种非常实用的,它可以清晰地展示具有层级关系的数据,并且能够方便用户进行导航和操作。

整体思路

整个项目主要分为三个部分:HTML 结构搭建、CSS 样式设计和 JavaScript 交互逻辑实现。通过 XMLHttpRequest 对象从 JSON 文件中获取数据,将数据转换为树形结构,然后将树形结构渲染为 HTML 元素,并添加点击事件处理逻辑,实现菜单的展开 / 收起和内容的切换。

效果展示

点击不同的节点会有不同的页面显示,并且菜单是在页面的左侧,引入的iframe是在页面的右侧。

代码总览

<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>树形结构示例</title>
		<style>
			body {
				display: flex;
				margin: 0;
				padding: 0;
			}

			.left {
				width: 220px;
				background-color: #98D5FF;
				display: flex;
				flex-direction: column;
				overflow-y: auto;
				/* height: 100vh; */
			}

			.content {
				display: flex;
				align-items: center;
				padding: 10px;
				height: 35px;
				cursor: pointer;
			}

			.content img {
				margin-right: 8px;
			}

			.right {
				background-color: seagreen;
				flex-grow: 1;
				height: 100vh;
			}

			.box {
				display: none;
				margin-left: 20px;
			}

			.imgBot {
				margin-left: 100px;
			}

			.imgLeft {
				margin-right: 10px;
			}

			iframe {
				width: 100%;
				height: 100%;
			}
		</style>
	</head>
	<body>
		<script>
			let data;
			let xhr = new XMLHttpRequest();
			xhr.open('get', './js/trees.json', true);
			xhr.send();
			//回调函数,(放一些比较依赖data的代码)确保数据请求成功后在进行数据渲染
			xhr.onreadystatechange = function() {
				if (xhr.readyState == 4 && xhr.status == 200) {
					let text = xhr.responseText;
					data = JSON.parse(text);
					// 开始构建:调用parseTree函数,pid为0开始构建树结构,并将结果存到treeData里
					let treeData = parseTree(data, 0);
					// 渲染树形结构并插入到body中(相当于重新写了一下body)
					document.body.innerHTML = renderTree(treeData);
					// 创建右侧 iframe(插入的页面)
					//1.创建一个div
					let iframeDiv = document.createElement('div');
					//2.给这个div起名
					iframeDiv.className = 'right';
					// 3.把iframe插入到这个div中,后续 通过点击的树形节点更新src
					iframeDiv.innerHTML = '<iframe id="contentFrame" src="" frameborder="0"></iframe>';
					//4.把创建好的div插入到body
					document.body.appendChild(iframeDiv);

				}
			};


			//递归构建树形结构的函数
			function parseTree(data, pid) {
				// 用来存储树状的数据
				let tree = [];
				for (let i in data) {
					//如果数据的 pid 属性等于传入的 pid(父级的id)
					//⭐子集的pid和父级的id是保持一致的,这样才能完成递归
					if (data[i].pid === pid) {
						tree.push(data[i]);
						//递归调用parseTree,传data和当前数据的id作为pid(上面函数里是data和pid),为了查找当前数据的子集然后用child(子集)方法插入到当前数据后。
						tree[tree.length - 1].child = parseTree(data, data[i].id);
					}
				}
				// 返回构建的树形结构
				return tree;
			}



			//转化函数(HTML结构)
			function renderTree(data) {
				// 这个div是左边菜单的底板
				let str = '<div class="left">';
				for (let i in data) {
					//如果当前数据有子节点的话,就为该数据生成一个包含子节点的html
					if (data[i].child.length > 0) {
						str +=
							`<div class="textBox">
								<div class="content">
									<img class="imgLeft" src="./img/圣诞礼物.png" width="20px" />
									${data[i].name}
									<img class="imgBot" src="./img/向下箭头.png" width="20px" height="20px"/>
								</div>
                        <div class="box" style="display:none;">`;
						//递归调用renderTree函数,生成当前节点的子节点(只有在有子节点的条件下才递归,因为要去检查子节点有没有子节点,没有的话就结束)
						str += renderTree(data[i].child);
						//拼结束标签
						str += '</div></div>';
						//如果当前数据没有子节点的话,就添加普通元素
					} else {
						str +=
							`<div class="textBox">
								<div class="content">
									<img class="imgLeft" src="./img/圣诞礼物.png" width="20px" />
									${data[i].name}
								</div>
							</div>`;
					}
				}
				str += '</div>';
				return str;
			}
			

			// 监听点击事件(给body上),切换子节点显示/隐藏,并更新 iframe 的 src
			document.body.addEventListener('click', function(e) {
				//获取点击的目标元素(全局的)
				let target = e.target;
				//向上查找父元素,找到有content元素
				while (target && !target.classList.contains('content')) {
					target = target.parentElement;
				}
				//如果找到了
				if (target) {
					// 通过 target.nextElementSibling 获取 target 元素的下一个兄弟元素(即紧接着 .content 元素之后的元素)。
					let sibling = target.nextElementSibling;
					// 如果该兄弟元素有box元素
					if (sibling && sibling.classList.contains('box')) {
						// 展开或收起子节点
						sibling.style.display = sibling.style.display === 'none' ? 'block' : 'none';
					}

					// 点击叶子节点,更新 iframe
					//通过target.textContent获取到点击的目标元素文本
					let name = target.textContent.trim();
					let item = null;
					//1.通过循环在data中找与name相匹配的数据
					for (let i = 0; i < data.length; i++) {
						// 2.如果相匹配
						if (data[i].name === name) {
							// 3.就存到item里
							item = data[i];
							break;
						}
					}

					// 如果找到匹配的数据且path路径不为空,则更新iframe的src
					if (item && item.path) {
						document.getElementById('contentFrame').src = item.path;
					}
				}
			});
		</script>
	</body>
</html>

css 部分

body {
				display: flex;
				margin: 0;
				padding: 0;
			}

			.left {
				width: 220px;
				background-color: #98D5FF;
				display: flex;
				flex-direction: column;
				overflow-y: auto;
				/* height: 100vh; */
			}

			.content {
				display: flex;
				align-items: center;
				padding: 10px;
				height: 35px;
				cursor: pointer;
			}

			.content img {
				margin-right: 8px;
			}

			.right {
				background-color: seagreen;
				flex-grow: 1;
				height: 100vh;
			}

			.box {
				display: none;
				margin-left: 20px;
			}

			.imgBot {
				margin-left: 100px;
			}

			.imgLeft {
				margin-right: 10px;
			}

			iframe {
				width: 100%;
				height: 100%;
			}

JS部分

1.数据请求

使用 XMLHttpRequest 对象从 ./js/trees.json 文件中获取数据。当请求成功后,将数据解析为 JSON 对象,并调用 parseTree 函数将数据转换为树形结构。调用 renderTree 函数将树形结构转换为 HTML 字符串,并插入到页面的 body 元素中。创建一个包含 iframe 的 div 元素,并添加到页面的 body 元素中。

			let data;
			let xhr = new XMLHttpRequest();
			xhr.open('get', './js/trees.json', true);
			xhr.send();
			//回调函数,(放一些比较依赖data的代码)确保数据请求成功后在进行数据渲染
			xhr.onreadystatechange = function() {
				if (xhr.readyState == 4 && xhr.status == 200) {
					let text = xhr.responseText;
					data = JSON.parse(text);
					// 开始构建:调用parseTree函数,pid为0开始构建树结构,并将结果存到treeData里
					let treeData = parseTree(data, 0);
					// 渲染树形结构并插入到body中(相当于重新写了一下body)
					document.body.innerHTML = renderTree(treeData);
					// 创建右侧 iframe(插入的页面)
					//1.创建一个div
					let iframeDiv = document.createElement('div');
					//2.给这个div起名
					iframeDiv.className = 'right';
					// 3.把iframe插入到这个div中,后续 通过点击的树形节点更新src
					iframeDiv.innerHTML = '<iframe id="contentFrame" src="" frameborder="0"></iframe>';
					//4.把创建好的div插入到body
					document.body.appendChild(iframeDiv);

2.构建树形结构的函数

通过递归的方式将数据转换为树形结构,每个节点的子节点存储在 child 属性中。

function parseTree(data, pid):定义一个递归函数,用于将扁平的数据转换为树形结构。

let tree = [];:创建一个空数组 tree 用于存储树形结构的数据。

for (let i in data):遍历数据数组。

if (data[i].pid === pid):如果当前数据的 pid 属性等于传入的 pid,表示该数据是当前节点的子节点。

tree.push(data[i]);:将该数据添加到 tree 数组中。

tree[tree.length - 1].child = parseTree(data, data[i].id);:递归调用 parseTree 函数,查找当前数据的子节点,并将结果存储在 child 属性中。

function parseTree(data, pid) {
				// 用来存储树状的数据
				let tree = [];
				for (let i in data) {
					//如果数据的 pid 属性等于传入的 pid(父级的id)
					//⭐子集的pid和父级的id是保持一致的,这样才能完成递归
					if (data[i].pid === pid) {
						tree.push(data[i]);
						//递归调用parseTree,传data和当前数据的id作为pid(上面函数里是data和pid),为了查找当前数据的子集然后用child(子集)方法插入到当前数据后。
						tree[tree.length - 1].child = parseTree(data, data[i].id);
					}
				}
				// 返回构建的树形结构
				return tree;
			}

3.转化函数

根据树形结构生成 HTML 字符串,对于有子节点的菜单项,添加展开 / 收起的箭头图标。

function renderTree(data):定义一个函数,用于将树形结构转换为 HTML 字符串。

data 是树形结构的数据。

let str = '<div class="left">';:初始化一个字符串 str,用于存储 HTML 代码。

for (let i in data):遍历树形结构的数据。

if (data[i].child.length > 0):如果当前元素有子节点,则生成包含子节点的 HTML 代码。str += ...:拼接 HTML 代码,包括左侧的图片、节点名称和向下箭头图标。

str += renderTree(data[i].child);:递归调用 renderTree 函数,生成子节点的 HTML 代码。str += '</div></div>';:拼接结束标签。

else:如果当前元素没有子节点,则生成普通的 HTML 代码。

str += '</div>';:拼接结束标签。return str;:返回生成的 HTML 字符串。

function renderTree(data) {
				// 这个div是左边菜单的底板
				let str = '<div class="left">';
				for (let i in data) {
					//如果当前数据有子节点的话,就为该数据生成一个包含子节点的html
					if (data[i].child.length > 0) {
						str +=
							`<div class="textBox">
								<div class="content">
									<img class="imgLeft" src="./img/圣诞礼物.png" width="20px" />
									${data[i].name}
									<img class="imgBot" src="./img/向下箭头.png" width="20px" height="20px"/>
								</div>
                        <div class="box" style="display:none;">`;
						//递归调用renderTree函数,生成当前节点的子节点(只有在有子节点的条件下才递归,因为要去检查子节点有没有子节点,没有的话就结束)
						str += renderTree(data[i].child);
						//拼结束标签
						str += '</div></div>';
						//如果当前数据没有子节点的话,就添加普通元素
					} else {
						str +=
							`<div class="textBox">
								<div class="content">
									<img class="imgLeft" src="./img/圣诞礼物.png" width="20px" />
									${data[i].name}
								</div>
							</div>`;
					}
				}
				str += '</div>';
				return str;
			}

4.点击事件处理函数

监听 body 元素的点击事件,当点击菜单项时,切换子菜单的显示 / 隐藏状态。当点击叶子节点时,根据节点的 path 属性更新 iframe 的 src 属性,显示相应的内容。

document.body.addEventListener('click', function(e):为页面的 body 元素添加一个点击事件监听。

let target = e.target;:获取点击事件的目标元素

while (target && !target.classList.contains('content')):向上查找父元素,直到找到具有 content 类名的元素。

if (target):如果找到了具有 content 类名的元素的话就let sibling = target.nextElementSibling;:获取目标元素的下一个兄弟元素。

if (sibling && sibling.classList.contains('box')):如果兄弟元素具有 box 类名,说明它是子菜单。

sibling.style.display = sibling.style.display === 'none' ? 'block' : 'none';:切换子菜单的显示状态,实现展开和收起的效果。

let name = target.textContent.trim();:获取目标元素的文本内容,并去除首尾的空格。

let item = null;:初始化一个变量 item,用于存储匹配的元素。

for (let i = 0; i < data.length; i++):遍历 data 数组,查找与目标元素文本内容匹配的元素。

if (data[i].name === name):如果找到匹配的元素。

item = data[i];:将匹配的元素存储在 item 变量中。break;:跳出循环。

if (item && item.path):如果找到匹配的元素且该元素具有 path 属性。document.getElementById('contentFrame').src = item.path;:将 iframe 的 src 属性设置为该元素的 path 属性值,从而在 iframe 中加载对应的页面。

document.body.addEventListener('click', function(e) {
				//获取点击的目标元素(全局的)
				let target = e.target;
				//向上查找父元素,找到有content元素
				while (target && !target.classList.contains('content')) {
					target = target.parentElement;
				}
				//如果找到了
				if (target) {
					// 通过 target.nextElementSibling 获取 target 元素的下一个兄弟元素(即紧接着 .content 元素之后的元素)。
					let sibling = target.nextElementSibling;
					// 如果该兄弟元素有box元素
					if (sibling && sibling.classList.contains('box')) {
						// 展开或收起子节点
						sibling.style.display = sibling.style.display === 'none' ? 'block' : 'none';
					}

					// 点击叶子节点,更新 iframe
					//通过target.textContent获取到点击的目标元素文本
					let name = target.textContent.trim();
					let item = null;
					//1.通过循环在data中找与name相匹配的数据
					for (let i = 0; i < data.length; i++) {
						// 2.如果相匹配
						if (data[i].name === name) {
							// 3.就存到item里
							item = data[i];
							break;
						}
					}

					// 如果找到匹配的数据且path路径不为空,则更新iframe的src
					if (item && item.path) {
						document.getElementById('contentFrame').src = item.path;
					}
				}
			});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/960337.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java的循环结构

今天学习一下Java的循环结构 while循环 while是最基本的循环&#xff0c;它的结构为&#xff1a; while(布尔表达式){//循环内容 } 只要布尔表达式为true&#xff0c;循环就会一直执行下去。 我们大多数情况是会让循环停止下来的&#xff0c;我们需要一个让表达式失效的方式…

GSI快速收录服务:让你的网站内容“上架”谷歌

辛苦制作的内容无法被谷歌抓取和展示&#xff0c;导致访客无法找到你的网站&#xff0c;这是会让人丧失信心的事情。GSI快速收录服务就是为了解决这种问题而存在的。无论是新上线的页面&#xff0c;还是长期未被收录的内容&#xff0c;通过我们的技术支持&#xff0c;都能迅速被…

蓝桥杯嵌入式led模块代码及相关实验

cubeMx配置教程 PD2锁存器控制端口 打开PC8~15的接口配置为output模式 PD2低电平时候为开&#xff0c;防止别的引脚冲突 #include "my_main.h" uint8_t led_sta0x10;void LED_Disp(uint8_t dsLED) {HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);//所有LED熄…

Qt 5.14.2 学习记录 —— 이십 QFile和多线程

文章目录 1、QFile1、打开2、读写3、关闭4、程序5、其它功能 2、多线程1、演示2、锁 3、条件变量和信号量 1、QFile Qt有自己的一套文件体系&#xff0c;不过Qt也可以使用C&#xff0c;C&#xff0c;Linux的文件操作。使用Qt的文件体系和Qt自己的一些类型更好配合。 管理写入读…

MySQL分表自动化创建的实现方案(存储过程、事件调度器)

《MySQL 新年度自动分表创建项目方案》 一、项目目的 在数据库应用场景中&#xff0c;随着数据量的不断增长&#xff0c;单表存储数据可能会面临性能瓶颈&#xff0c;例如查询、插入、更新等操作的效率会逐渐降低。分表是一种有效的优化策略&#xff0c;它将数据分散存储在多…

董事会办公管理系统的需求设计和实现

该作者的原创文章目录&#xff1a; 生产制造执行MES系统的需求设计和实现 企业后勤管理系统的需求设计和实现 行政办公管理系统的需求设计和实现 人力资源管理HR系统的需求设计和实现 企业财务管理系统的需求设计和实现 董事会办公管理系统的需求设计和实现 公司组织架构…

WPF5-x名称空间

1. x名称空间2. x名称空间内容3. x名称空间内容分类 3.1. x:Name3.2. x:Key3.3. x:Class3.4. x:TypeArguments 4. 总结 1. x名称空间 “x名称空间”的x是映射XAML名称空间时给它取的名字&#xff08;取XAML的首字母&#xff09;&#xff0c;里面的成员&#xff08;如x:Class、…

STM32完全学习——RT-thread在STM32F407上移植

一、写在前面 关于源码的下载&#xff0c;以及在KEIL工程里面添加操作系统的源代码&#xff0c;这里就不再赘述了。需要注意的是RT-thread默认里面是会使用串口的&#xff0c;因此需要额外的进行串口的初始化&#xff0c;有些人可能会问&#xff0c;为什么不直接使用CubMAX直接…

K8S部署DevOps自动化运维平台

持续集成&#xff08;CI&#xff09; 持续集成强调开发人员提交了新代码之后&#xff0c;立刻自动的进行构建、&#xff08;单元&#xff09;测试。根据测试结果&#xff0c;我 们可以确定新代码和原有代码能否正确地集成在一起。持续集成过程中很重视自动化测试验证结果&#…

windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】

注意版本依赖【本实验版本如下】 Hadoop 3.1.1 spark 2.3.2 scala 2.11 1.依赖环境 1.1 java 安装java并配置环境变量【如果未安装搜索其他教程】 环境验证如下&#xff1a; C:\Users\wangning>java -version java version "1.8.0_261" Java(TM) SE Runti…

【Android】布局文件layout.xml文件使用控件属性android:layout_weight使布局较为美观,以RadioButton为例

目录 说明举例 说明 简单来说&#xff0c;android:layout_weight为当前控件按比例分配剩余空间。且单个控件该属性的具体数值不重要&#xff0c;而是多个控件的属性值之比发挥作用&#xff0c;例如有2个控件&#xff0c;各自的android:layout_weight的值设为0.5和0.5&#xff0…

新项目上传gitlab

Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…

AI智能日志分析系统

文章目录 1.combinations-intelligent-analysis-starter1.目录结构2.pom.xml3.自动配置1.IntelligentAnalysisAutoConfiguration.java2.spring.factories 2.combinations-intelligent-analysis-starter-demo1.目录结构2.pom.xml3.application.yml4.IntelligentAnalysisApplicat…

K8s运维管理平台 - xkube体验:功能较多

目录 简介Lic安装1、需要手动安装MySQL&#xff0c;**建库**2、启动命令3、[ERROR] GetNodeMetric Fail:the server is currently unable to handle the request (get nodes.metrics.k8s.io qfusion-1) 使用总结优点优化 补充1&#xff1a;layui、layuimini和beego的详细介绍1.…

MacOS安装Docker battery-historian

文章目录 需求安装battery-historian实测配置国内源相关文章 需求 分析Android电池耗电情况、唤醒、doze状态等都要用battery-historian&#xff0c; 在 MacOS 上安装 battery-historian&#xff0c;可以使用 Docker 进行安装runcare/battery-historian:latest。装完不需要做任…

VUE elTree 无子级 隐藏展开图标

这4个并没有下级节点&#xff0c;即它并不是叶子节点&#xff0c;就不需求展示前面的三角展开图标! 查阅官方文档如下描述&#xff0c;支持bool和函数回调处理&#xff0c;这里咱们选择更灵活的函数回调实现。 给el-tree结构配置一下props&#xff0c;注意&#xff01; :pr…

AWScurl笔记

摘要 AWScurl是一款专为与AWS服务交互设计的命令行工具&#xff0c;它模拟了curl的功能并添加了AWS签名版本4的支持。这一特性使得用户能够安全有效地执行带有AWS签名的请求&#xff0c;极大地提升了与AWS服务交互时的安全性和有效性。 GitHub - okigan/awscurl: curl-like acc…

JDK自带工具解析与生产问题定位指南(一)

1. 引言 Java开发工具包&#xff08;JDK&#xff09;内置了强大的诊断工具集&#xff0c;用于监控、分析和调试Java应用程序。这些工具涵盖了从进程管理、内存分析到性能监控的各个方面。本文将介绍一些最常用的Java开发工具&#xff0c;包括jps、jmap、jstat、jcmd、jstack、…

基于vscode的cppcmake调试环境配置

1. 创建项目文件 创建cpp文件及CMakeLists.txt文件 helloOpenCV.cpp #include <opencv2/opencv.hpp> int main() {// 创建图像&#xff0c;初始化为黑色cv::Mat image cv::Mat::zeros(200, 300, CV_8UC3);// 设置为纯绿色 (BGR格式&#xff1a;0, 255, 0)image.setTo…

leetcode刷题记录(一百)——121. 买卖股票的最佳时机

&#xff08;一&#xff09;问题描述 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09;121. 买卖股票的最佳时机 - 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票&#xff0c;并…