树形结构基础知识
1.节点类型
在数据库中表示树形结构
在java类中表示树形结构
1.基本方式
在 Menu 类中使用 List
2 菜单维护:页面显示树形结构
1 目标
将数据库中查询得到的数据到页面上显示出来。
2.逆向工程
generatorConfig.xml
<table tableName="t_menu" domainObjectName="Menu" />
1.给实体类做些调整
// 存储子节点的集合,初始化为了避免空指针异常
private List<Menu> children = new ArrayList<Menu>();
// 控制节点设置为true是否默认为打卡
private Boolean open = true;
public List<Menu> getChildren() {
return children;
}
public void setChildren(List<Menu> children) {
this.children = children;
}
public Menu() {
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Menu [id=" + id + ", pid=" + pid + ", name=" + name + ", url=" + url + ", icon=" + icon + ", children="
+ children + ", open=" + open + "]";
}
public Menu(Integer id, Integer pid, String name, String url, String icon, List<Menu> children, Boolean open) {
super();
this.id = id;
this.pid = pid;
this.name = name;
this.url = url;
this.icon = icon;
this.children = children;
this.open = open;
}
2.将数据在 Java 代码中组装成树形结构
serviceIml.java
`handler.java
@ResponseBody
@RequestMapping("/menu/get/whole/tree.json")
public ResultEntity<Menu> getWholeTree(){
// 1.查询全部的Menu对象
List<Menu> menuList = menuService.getAll();
// 2.声明一个变量用来存储中找到哦啊的根节点
Menu root = null;
// 3.创建一个Map对象来存储id和Menu对象对应关系便于查找父节点
Map<Integer, Menu> menuMap = new HashMap<Integer, Menu>();
// 4 .遍历menuList填充menuMap
for(Menu menu:menuList) {
Integer id = menu.getId();
menuMap.put(id, menu);
}
// 5.再次遍历menuList,查找根节点,组装父子节点
for(Menu menu:menuList) {
// 6.获取当前menu对象的pid属性值
Integer pid = menu.getPid();
// 7 .如果pid为null,判断为根节点
if (pid==null) {
root = menu;
// 8.如果当前节点为根节点,肯定没有父节点,不必往下执行
continue ;
}
// 9.如果pid不为null,说明当前节点有父节点,,那么可以根据pid到menuMap查找对应的menu对象
Menu father = menuMap.get(pid);
// 10 .将当前节点存入父节点children集合
father.getChildren().add(menu);
}
// 经过上面运行,根节点包含了整个树形结构,返回根节点就是返回整个树
return ResultEntity.successWithData(root);
}
3.前端页面的引入
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ZH-cn">
<%@include file="/WEB-INF/include-head.jsp" %>
<link rel="stylesheet" href="ztree/zTreeStyle.css">
<script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
<script type="text/javascript" src="crowd/my-menu.js"></script>
<script type="text/javascript">
<body>
<%@include file="/WEB-INF/include-nav.jsp" %>
<div class="container-fluid">
<div class="row">
<%@include file="/WEB-INF/include-sidebar.jsp" %>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<div class="panel panel-default">
<div class="panel-heading">
<i class="glyphicon glyphicon-th-list"></i> 权限菜单列表
<div style="float: right; cursor: pointer;" data-toggle="modal"
data-target="#myModal">
<i class="glyphicon glyphicon-question-sign"></i>
</div>
</div>
<div class="panel-body">
<!-- 这个ul标签是zTree动态生成的节点所依附的静态节点 -->
<ul id="treeDemo" class="ztree"></ul>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
生成树形结构
发送ajax请求找到刚刚所写的hanler方法去处理,然后返回到页面,引入ztree的css及js,必须在juery之后放,否则无法生效
$(function(){
// 1.准备生成树形结构的JSON数据,数据的来源是发送Ajax请求得到的
$.ajax({
"url":"menu/get/whole/tree.json",
"type":"post",
"dataType":"json",
"success":function(response){
var result = response.result;
if(result =="SUCCESS"){
// 2从响应体中获得数据
var zNodes = response.data;
// 3.创建JSON对象用于存储zTree所做的设置
var setting = {
"view": {
// 添加数据库里的class显示的图标
"addDiyDom":myAddDiyDom,
// 添加鼠标移除上按钮组显示 "addHoverDom":myAddHoverDom,
"removeHoverDom":
},
"data":{
"key":{
"url":"huahua"
}
}
};
// 4.进行树形初始化结构
$.fn.zTree.init($("#treeDemo"),setting,zNodes);
}
if(result =="FAILED"){
layer.msg(response.message);
}
}
});
});
引入一个外部的js声明一些函数
// 修改默认图标
function myAddDiyDom(treeId,treeNode){
// treeId是整个树形结构附看的ul标签的id
console.log("treeId="+treeId);
// 当前树形节点的全部的数据,包括从后端得到的menu对象的全部属性
console.log(treeNode);
// zTree生成id的规则,例子:treeDemo_7_ico,解析:ul标签的id_当前节点的序号_功能
// 提示:"ul标签的id_当前节点的序号"部分可以通过访问treeNode的Id属性得到
// 根据id的生成规则拼接span的标签的id
var spanId = treeNode.tId +"_ico";
// 根据控制图标的span标签的id找到这个span标签
// 删除旧的class
// 增加新的class
$("#"+spanId).removeClass().addClass(treeNode.icon);
}
// 在鼠标移入节点范围内添加按钮组
function myAddHoverDom(treeId,treeNode){
// 按钮组的标签结果:<span><a><i></i></a><a><i></i></a></span>
// 按钮出现的位置:节点中treeDemo_n_a超链接的后面
// 为了在需要移除按钮的时候能够精确定位到按钮所在的span,需要给span设置有规律的id
var btnGroupId = treeNode.tId + "_btnGrp";
// 判断是否添加过按钮组
if($("#"+btnGroupId).length>0){
return ;
}
// 准备各个按钮的HTML标签
var addBtn = "<a id='"+treeNode.id+"' class='addBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title='添加子节点'> <i class='fa fa-fw fa-plus rbg '></i></a>";
var removeBtn = "<a id='"+treeNode.id+"' class='removeBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title='删除节点'> <i class='fa fa-fw fa-times rbg '></i></a>";
var editBtn = "<a id='"+treeNode.id+"' class='editBtn btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title='修改节点'> <i class='fa fa-fw fa-edit rbg '></i></a>";
// 获取当前节点的级别及判断
var level = treeNode.level;
// 声明变量存储按钮的代码
var btnHTML ="";
// 判断当前的级别,不同级别有不同的按钮规则,0,只能添加子节点
if(level == 0){
btnHTML = addBtn;
}
// 1.改名添加删除(仅仅在没有子节点)可以修改
if(level == 1){
btnHTML = addBtn +" "+editBtn;
//判断是否有子节点
var length = treeNode.children.length;
if(length=0){
btnHTML = btnHTML+" "+removeBtn;
}
}
// 2.编辑删除修改
if(level == 2){
btnHTML =editBtn+" "+removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
// 执行在超链接后面附加span元素的操作
$("#"+anchorId).after("<span id ='"+btnGroupId+"'>"+btnHTML+"</span>")
}
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId,treeNode){
var btnGroupId = treeNode.tId + "_btnGrp";
// 移除对应的元素
$("#"+btnGroupId).remove();
}
3菜单维护:添加子节点
给节点添加子节点,然后保存到数据库中并刷新当前树形结构的显示
加入点击后显示增加的模态框(通过juery的on函数通过按钮的class来绑定)
给点击保运绑定函数,然后获得模态框中的数据,以及点击时的id,要将他作为新增的父节点所以要保存到,设置为全局变量,然后得到数据发送ajax请求,后端处理,页面得到响应数据,进行树形结构的重新加载,然后关闭模态框,以及清空刚刚添加节点的模态框中的数据。
前端
$(function(){
// 显示分页结构
generateTree();
//给添加子节点按钮绑定点击响应函数
$("#treeDemo").on("click",".addBtn",function(){
// 将当前节点的id,作为新节点的pid
window.pid = this.id;
// 打开模态框
$("#menuAddModal").modal("show");
return false ;
});
// 给模态框中的保存按钮绑定响应函数
$("#menuSaveBtn").click(function(){
// 收集表单项中的数据
var name = $.trim($("#menuAddModal [name=name]").val());
var url = $.trim($("#menuAddModal [name=url]").val());
// 单选按钮要定位到被选中的那一个
var icon = $("#menuAddModal [name=icon]:checked").val();
// 发送ajax请求保存数据
$.ajax({
"url":"menu/save.json",
"type":"post",
"data":{
"pid":window.pid,
"name":name,
"url":url,
"icon":icon
},
"dataType":"json",
"success":function(response){
var result = response.result;
if(result=="SUCCESS"){
layer.msg("操作成功!");
generateTree();
}else{
layer.msg("操作成功!"+response.message);
}
},
"error":function(response){
layer.msg(response.status+""+response.statusText);
}
});
// 关闭模态框
$("#menuAddModal").modal("hide");
// 清空表单
$("#menuResetBtn").click();
});
});
handler方法
@ResponseBody
@RequestMapping("menu/save.json")
public ResultEntity<String> saveMenu(Menu menu){
menuService.saveMenu(menu);
return ResultEntity.successWithoutData();
}
3菜单维护:更新子节点
查找id,回显表单,进行update,pid还是原来pid,不显示,然后依旧用原来的pid
//给更新按钮绑定点击响应函数
$("#treeDemo").on("click",".editBtn",function(){
// 将当前节点的id,保存到全局变量
window.id = this.id;
// 打开模态框
$("#menuEditModal").modal("show");
// 获取zTree对象
var zTreeObj = $.fn.zTree.getZTreeObj("treeDemo");
// 根据id查找整个属性的值
// 搜索节点的属性名
var key = "id";
// 搜索节点的属性值
var value= window.id;
var currentNode = zTreeObj.getNodeByParam(key,value);
// 回显表单数据
$("#menuEditModal [name=name]").val(currentNode.name);
$("#menuEditModal [name=url]").val(currentNode.url);
// radio回显的本质就是把value的属性和currentNode.icon一致的radio选中
$("#menuEditModal [name=icon]").val([currentNode.icon]);
return false ;
});
$("#menuEditBtn").click(function(){
var name = $("#menuEditModal [name=name]").val();
var url = $("#menuEditModal [name=url]").val();
// radio回显的本质就是把value的属性和currentNode.icon一致的radio选中
var icon = $("#menuEditModal [name=icon]:checked").val();
$.ajax({
"url":"menu/update.json",
"type":"post",
"data":{
"id":window.id,
"name":name,
"url":url,
"icon":icon
},
"dataType":"json",
"success":function(response){
var result = response.result;
if(result=="SUCCESS"){
layer.msg("操作成功!");
generateTree();
}else{
layer.msg("操作成功!"+response.message);
}
},
"error":function(response){
layer.msg(response.status+""+response.statusText);
}
});
// 关闭模态框
$("#menuEditModal").modal("hide");
});
删除节点
删除与修改比较类似,只需要显示要删除的name就可以,然后在handler方法中,传入要删除的id就可以啦
$("#treeDemo").on("click",".removeBtn",function(){
// 将当前节点的id,保存到全局变量
window.id = this.id;
// 打开模态框
$("#menuConfirmModal").modal("show");
// 获取zTree对象
var zTreeObj = $.fn.zTree.getZTreeObj("treeDemo");
// 根据id查找整个属性的值
// 搜索节点的属性名
var key = "id";
// 搜索节点的属性值
var value= window.id;
var currentNode = zTreeObj.getNodeByParam(key,value);
// 回显表单数据
$("#removeNodeSpan").html("【<i class='"+currentNode.icon+"'></i>"+currentNode.name+"】");
return false;
});
$("#confirmBtn").click(function(){
$.ajax({
"url":"menu/remove.json",
"type":"post",
"data":{
"id":window.id
},
"dataType":"json",
"success":function(response){
var result = response.result;
if(result=="SUCCESS"){
layer.msg("操作成功!");
generateTree();
}else{
layer.msg("操作成功!"+response.message);
}
},
"error":function(response){
layer.msg(response.status+""+response.statusText);
}
});
// 关闭模态框
$("#menuConfirmModal").modal("hide");
});
// handler
@ResponseBody
@RequestMapping("menu/remove.json")
public ResultEntity<String> removeMenu(@RequestParam("id")Integer id){
menuService.removeMenu(id);
return ResultEntity.successWithoutData();
}
最终效果