八 菜单维护
git checkout -b 8.0.0_permission
1. 树形结构基础知识介绍
1.1 节点类型
约定: 整个树形结构节点的层次最多只能有 3 级
1.2 在数据库中表示树形结构
1.2.1 创建菜单的数据库表
create table t_menu (
id int(11) not null auto_increment,
pid int(11),
name varchar(200),
url varchar(200),
icon varchar(200),
primary key(id)
);
1.2.2 插入数据
insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('1',NULL,'系统权限菜单','glyphicon glyphicon-th-list',NULL); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('2','1',' 控 制 面 板 ','glyphicon glyphicon-dashboard','main.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('3','1','权限管理','glyphicon glyphicon glyphicon-tasks',NULL); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('4','3',' 用 户 维 护 ','glyphicon glyphicon-user','user/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('5','3',' 角 色 维 护 ','glyphicon glyphicon-king','role/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('6','3',' 菜 单 维 护 ','glyphicon glyphicon-lock','permission/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('7','1',' 业 务 审 核 ','glyphicon glyphicon-ok',NULL); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('8','7',' 实 名 认 证 审 核 ','glyphicon glyphicon-check','auth_cert/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('9','7',' 广 告 审 核 ','glyphicon glyphicon-check','auth_adv/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('10','7',' 项 目 审 核 ','glyphicon glyphicon-check','auth_project/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('11','1',' 业 务 管 理 ','glyphicon glyphicon-th-large',NULL); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('12','11',' 资 质 维 护 ','glyphicon glyphicon-picture','cert/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('13','11',' 分 类 管 理 ','glyphicon glyphicon-equalizer','certtype/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('14','11',' 流 程 管 理 ','glyphicon glyphicon-random','process/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('15','11',' 广 告 管 理 ','glyphicon glyphicon-hdd','advert/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('16','11',' 消 息 模 板 ','glyphicon glyphicon-comment','message/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('17','11',' 项 目 分 类 ','glyphicon glyphicon-list','projectType/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('18','11',' 项 目 标 签 ','glyphicon glyphicon-tags','tag/index.htm'); insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('19','1',' 参 数 管 理 ','glyphicon glyphicon-list-alt','param/index.htm');
1.2.3 关联方式
- 子节点通过 pid 字段关键到父节点的 id 字段, 建立父子关系
- 根节点的 pid 为 null, 无父节点
1.3 在 java 类中表示树形结构
1.3.1 基本方式
- 在 Menu 类中使用 List
1.3.2 为了配合 zTree 使需要添加的属性
属性 | 说明 |
---|---|
pid | 找到父节点 |
name | 作为子节点名称 |
icon | 当前节点使用的图标 |
open | 控制节点是否默认打开 |
url | 点击节点时跳转的 |
- pid 属性: 找到父节点
- name 属性: 作为子节点名称
- icon 属性: 当前节点使用的图标
- open 属性: 控制节点是否默认打开
- url 属性: 点击节点时跳转的
2 菜单维护: 页面显示
2.1 目标
- 将数据库中查询得到的数据页面上显示出来
2.2 思路
- 数据库 -> Java 对象组装 -> 页面上使用 zTree 显示
2.3 代码: 后端
2.3.1 生成代码
atcrowdfunding06-common-reverse
->generatorConfig.xml
<!-- 数据库表名字和我们的 entity 类对应的映射指定 -->
<!-- <table tableName="t_admin" domainObjectName="Admin"/>-->
<!-- <table tableName="t_role" domainObjectName="Role"/>-->
<table tableName="t_menu" domainObjectName="Menu"/>
- 执行命令
- 修改生成的实体类 - Menu.java
/**
* 存储子节点的集合, 初始化是为了避免空指针异常
*/
private List<Menu> children = new ArrayList<>();
/**
* 默认打开树节点
* 控制节点是否默认打开, 设置为 true 表示默认打开
*/
private Boolean open = true;
- 整体代码
package com.atguigu.crowd.entity;
import java.util.ArrayList;
import java.util.List;
public class Menu {
/**
* 主键
*/
private Integer id;
/**
* 父节点的 id
*/
private Integer pid;
/**
* 节点名称
*/
private String name;
/**
* 节点附带的 url 地址, 是将来点击菜单项时要跳转的地址
*/
private String url;
/**
* 节点图标的样式
*/
private String icon;
/**
* 存储子节点的集合, 初始化是为了避免空指针异常
*/
private List<Menu> children = new ArrayList<>();
/**
* 默认打开树节点
* 控制节点是否默认打开, 设置为 true 表示默认打开
*/
private Boolean open = true;
public Menu() {
}
public Menu(Integer id, Integer pid, String name, String url, String icon, List<Menu> children, Boolean open) {
this.id = id;
this.pid = pid;
this.name = name;
this.url = url;
this.icon = icon;
this.children = children;
this.open = open;
}
@Override
public String toString() {
return "Menu{" +
"id=" + id +
", pid=" + pid +
", name='" + name + '\'' +
", url='" + url + '\'' +
", icon='" + icon + '\'' +
", children=" + children +
", open=" + open +
'}';
}
public List<Menu> getChildren() {
return children;
}
public void setChildren(List<Menu> children) {
this.children = children;
}
public Boolean getOpen() {
return open;
}
public void setOpen(Boolean open) {
this.open = open;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url == null ? null : url.trim();
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon == null ? null : icon.trim();
}
}
- 将生成的文件移动到对应的位置
2.3.2 后端代码
- MenuHandler
package com.atguigu.crowd.mvc.handler;
import com.atguigu.crowd.entity.Menu;
import com.atguigu.crowd.service.api.MenuService;
import com.atguigu.crowd.util.ResultEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class MenuHandler {
@Autowired
private MenuService menuService;
@GetMapping("/menu/get/whole/tree.json")
public ResultEntity<Menu> getWholeTree2() {
// 1. 查询全部的 Menu 对象
List<Menu> menuList = menuService.getAll();
// 2. 声明一个变量用来存储找到的根节点
Menu root = null;
// 3. 创建 Map 对象用来存储 id 和 Menu 对象的对应关系便于查找父节点
Map<Integer, Menu> menuMap = new HashMap();
// 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);
}
// 11. 经过上面的运算, 根节点包含了整个树形结构, 返回根节点角色返回整个数
return ResultEntity.successWithData(root);
}
}
- MenuService
package com.atguigu.crowd.service.api;
import com.atguigu.crowd.entity.Menu;
import java.util.List;
public interface MenuService {
/**
* 查询所有
*
* @return
*/
List<Menu> getAll();
}
- MenuServiceImpl
package com.atguigu.crowd.service.impl;
import com.atguigu.crowd.entity.Menu;
import com.atguigu.crowd.entity.MenuExample;
import com.atguigu.crowd.mapper.MenuMapper;
import com.atguigu.crowd.service.api.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MenuServiceImpl implements MenuService {
@Autowired
private MenuMapper menuMapper;
@Override
public List<Menu> getAll() {
return menuMapper.selectByExample(new MenuExample());
}
}
2.3.3 页面跳转
<mvc:view-controller path="/menu/to/page.html" view-name="menu-page" />
menu-page.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zn-CN">
<%@include file="/WEB-INF/include-head.jsp" %>
<link rel="stylesheet" href="css/pagination.css"/>
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
<script src="crowd/js/my-menu.js?v=6" type="text/javascript"></script>
<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 id="treeDemo" class="ztree"></ul>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
include-sidebar.jsp
<a href="menu/to/page.html"><i class="glyphicon glyphicon-lock"></i> 许可维护</a>
-
my-menu.js
-
- 模拟数据
$(function () {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {};
// 2. 准备生成树形结构的 JSON 数据
var zNodes =[
{ name:"父节点1 - 展开", open:true,
children: [
{ name:"父节点11 - 折叠",
children: [
{ name:"叶子节点111"},
{ name:"叶子节点112"},
{ name:"叶子节点113"},
{ name:"叶子节点114"}
]},
{ name:"父节点12 - 折叠",
children: [
{ name:"叶子节点121"},
{ name:"叶子节点122"},
{ name:"叶子节点123"},
{ name:"叶子节点124"}
]},
{ name:"父节点13 - 没有子节点", isParent:true}
]},
{ name:"父节点2 - 折叠",
children: [
{ name:"父节点21 - 展开", open:true,
children: [
{ name:"叶子节点211"},
{ name:"叶子节点212"},
{ name:"叶子节点213"},
{ name:"叶子节点214"}
]},
{ name:"父节点22 - 折叠",
children: [
{ name:"叶子节点221"},
{ name:"叶子节点222"},
{ name:"叶子节点223"},
{ name:"叶子节点224"}
]},
{ name:"父节点23 - 折叠",
children: [
{ name:"叶子节点231"},
{ name:"叶子节点232"},
{ name:"叶子节点233"},
{ name:"叶子节点234"}
]}
]},
{ name:"父节点3 - 没有子节点", isParent:true}
];
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, zNodes);
})
-
- 请求获取数据
$(function () {
$.ajax({
url: 'menu/get/whole/tree.json',
type: 'get',
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {};
// 2. 准备生成树形结构的 JSON 数据
var data = response.data;
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, data);
} else if (result === 'FAILED') {
layer.msg('操作失败! ' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText)
}
})
})
2.4 树
2.4.1 修改默认图标
$(function () {
$.ajax({
url: 'menu/get/whole/tree.json',
type: 'get',
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
view: {
addDiyDom: myAddDiyDom
}
};
// 2. 准备生成树形结构的 JSON 数据
var data = response.data;
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, data);
} else if (result === 'FAILED') {
layer.msg('操作失败! ' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText)
}
})
})
// 修改默认的图标
function myAddDiyDom(treeId, treeNode) {
// treeId 是整个树形结构附着的 ul 标签的 id
// console.log("treeId:" + treeId);
// 当前树形节点的全部的数据, 包括从后端查询得到的 Menu 对象的全部属性
// console.log(treeNode)
// zTree 生成 id 的规则
// 例子: treeDome_7_ico
// 解析:ul 标签的 id_当前节点的序号_功能
// 提示: ul 标签的id_当前节点的序号" - 这个部分可以通过 treeNode 的 tId 属性得到
// 根据 id 的生成规则拼接出来 span 标签的 id
var spanId = treeNode.tId + '_ico';
// 根据控制图标的 span 标签的 id 找到整个 span 标签
// 删除旧的 class
// 添加新的 class
$('#' + spanId)
.removeClass()
.addClass(treeNode.icon);
}
2.4.2 设置: 取消菜单的点击跳转页面功能
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
data: {
key: {
url: 'no'
}
}
};
2.4.3 显示按钮组
// 在鼠标移入节点范围时添加按钮组
function myAddHoverDom(treeId, treeNode) {
// 按钮组的标签结构: <span><a><i></i></a><a><i></i></a></span>
// 按钮组出现的位置: 节点中 treeDDemo_n_a 超链接的后面
// 为了在需要移除按钮组的时候能够精确定位按钮组所在 span, 需要给 spna 设置有规律的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 判断一下以前是否已经添加了按钮组
if ($('#' + btnGroupId).length > 0) {
return false;
}
// 执行在超链接后面附加 span 元素的操作
// 准备各个按钮的 HTML 标签
var addBtn = `<a id="${treeNode.id}" class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="添加子节点"> <i class="fa fa-fw fa-plus rbg "></i></a>`;
var removeBtn = `<a class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="删除节点"> <i class="fa fa-fw fa-times rbg "></i></a>`;
var editBtn = `<a class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="修改节点"> <i class="fa fa-fw fa-edit rbg "></i></a>`;
// 获取当前就带你的级别数据
var level = treeNode.level;
// 声明变量存储拼装号的按钮代码
var btnHtml = '';
if (level === 0) {
// 根节点: 添加子节点
btnHtml = addBtn;
} else if (level === 1) {
// 分支节点: 添加子节点 修改 <没有子节点可以删除 有子节点不能删除>
btnHtml = addBtn + ' ' + editBtn;
// 获取当前节点的子节点数量
var length = treeNode.children.length;
(length === 0) ? btnHtml += ' ' + removeBtn : '';
} else if (level === 2) {
// 叶子节点: 修改 删除
btnHtml = editBtn + ' ' + removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
$('#' + anchorId).after(`<span id="${btnGroupId}">${btnHtml}</span>`);
}
2.4.4 移除按钮组
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId, treeNode) {
// 拼接按钮组的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 移除对应的元素
$('#' + btnGroupId).remove();
}
2.4.5 初始化树形结构函数 - 包装请求
$(function () {
// 调用专门封装好的函数初始化树形结构
generateTree();
})
// 生成树形结构的函数
function generateTree() {
$.ajax({
url: 'menu/get/whole/tree.json',
type: 'get',
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
view: {
addDiyDom: myAddDiyDom,
addHoverDom: myAddHoverDom,
removeHoverDom: myRemoveHoverDom
},
data: {
key: {
url: 'no'
}
}
};
// 2. 准备生成树形结构的 JSON 数据
var data = response.data;
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, data);
} else if (result === 'FAILED') {
layer.msg('操作失败! ' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText)
}
})
}
// 修改默认的图标
function myAddDiyDom(treeId, treeNode) {
// treeId 是整个树形结构附着的 ul 标签的 id
// console.log("treeId:" + treeId);
// 当前树形节点的全部的数据, 包括从后端查询得到的 Menu 对象的全部属性
// console.log(treeNode)
// zTree 生成 id 的规则
// 例子: treeDome_7_ico
// 解析:ul 标签的 id_当前节点的序号_功能
// 提示: ul 标签的id_当前节点的序号" - 这个部分可以通过 treeNode 的 tId 属性得到
// 根据 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>
// 按钮组出现的位置: 节点中 treeDDemo_n_a 超链接的后面
// 为了在需要移除按钮组的时候能够精确定位按钮组所在 span, 需要给 spna 设置有规律的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 判断一下以前是否已经添加了按钮组
if ($('#' + btnGroupId).length > 0) {
return false;
}
// 执行在超链接后面附加 span 元素的操作
// 准备各个按钮的 HTML 标签
var addBtn = `<a id="${treeNode.id}" class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="添加子节点"> <i class="fa fa-fw fa-plus rbg "></i></a>`;
var removeBtn = `<a class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="删除节点"> <i class="fa fa-fw fa-times rbg "></i></a>`;
var editBtn = `<a class="btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="修改节点"> <i class="fa fa-fw fa-edit rbg "></i></a>`;
// 获取当前就带你的级别数据
var level = treeNode.level;
// 声明变量存储拼装号的按钮代码
var btnHtml = '';
// 根节点: 添加子节点
if (level === 0) {
btnHtml += addBtn;
}
// 分支节点: 添加子节点 修改 <没有子节点可以删除 有子节点不能删除>
if (level === 1) {
btnHtml += addBtn + ' ' + editBtn;
// 获取当前节点的子节点数量
var length = treeNode.children.length;
(length === 0) ? btnHtml += ' ' + removeBtn : '';
}
// 叶子节点: 修改 删除
if (level === 2) {
btnHtml = editBtn + ' ' + removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
$('#' + anchorId).after(`<span id="${btnGroupId}">${btnHtml}</span>`);
}
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId, treeNode) {
// 拼接按钮组的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 移除对应的元素
$('#' + btnGroupId).remove();
}
2.5 增删改按钮组 - 模态框系列
2.5.1 添加子节点
2.5.1.1 目标
- 给当前节点添加子节点, 保存到数据库并刷新树形结构的显示
2.5.1.2 思路
2.5.1.3 代码
- 使用按钮的 class 定位绑定点击响应函数
- 给模态框中的保存按钮绑定点击响应函数
$(function () {
// 调用专门封装好的函数初始化树形结构
generateTree();
// 给添加子节点按钮绑定单击响应函数
$('#treeDemo').on('click', '.addBtn', function () {
// 将当前节点的 id, 作为新就带你的 pid 保存到全局变量
window.pid = this.id;
// 打开模态框
$('#menuAddModal').modal('show');
});
// 给添加子节点的新增模态框的保存按钮 绑定单击响应函数
$('#menuSaveBtn').click(function () {
// 收集表单项中用户输入的数据
var name = $.trim($('#menuAddModal [name=name]').val());
var url = $.trim($('#menuAddModal [name=url]').val());
// 单选按钮要定位到'被选中的那一个'
var icon = $.trim($('#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 if (result === 'FAILED') {
layer.msg('操作失败!' + response.message);
}
},
error: function (response) {
layer.msg(response.status + '' + response.statusText);
}
});
// 关闭模态框
$('#menuAddModal').modal('hide');
// 清空表单
// jQuery 对象调用 click() 函数, 里面不传任何参数, 相当于用户点击了一次
$('#menuResetBtn').click();
});
})
// 生成树形结构的函数
function generateTree() {
$.ajax({
url: 'menu/get/whole/tree.json',
type: 'get',
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
view: {
addDiyDom: myAddDiyDom,
addHoverDom: myAddHoverDom,
removeHoverDom: myRemoveHoverDom
},
data: {
key: {
url: 'no'
}
}
};
// 2. 准备生成树形结构的 JSON 数据
var data = response.data;
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, data);
} else if (result === 'FAILED') {
layer.msg('操作失败! ' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText)
}
})
}
// 修改默认的图标
function myAddDiyDom(treeId, treeNode) {
// treeId 是整个树形结构附着的 ul 标签的 id
// console.log("treeId:" + treeId);
// 当前树形节点的全部的数据, 包括从后端查询得到的 Menu 对象的全部属性
// console.log(treeNode)
// zTree 生成 id 的规则
// 例子: treeDome_7_ico
// 解析:ul 标签的 id_当前节点的序号_功能
// 提示: ul 标签的id_当前节点的序号" - 这个部分可以通过 treeNode 的 tId 属性得到
// 根据 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>
// 按钮组出现的位置: 节点中 treeDDemo_n_a 超链接的后面
// 为了在需要移除按钮组的时候能够精确定位按钮组所在 span, 需要给 spna 设置有规律的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 判断一下以前是否已经添加了按钮组
if ($('#' + btnGroupId).length > 0) {
return false;
}
// 执行在超链接后面附加 span 元素的操作
// 准备各个按钮的 HTML 标签
var addBtn = `<a id="${treeNode.id}" class="addBtn btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="添加子节点"> <i class="fa fa-fw fa-plus rbg "></i></a>`;
var removeBtn = `<a class="removeBtn btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="删除节点"> <i class="fa fa-fw fa-times rbg "></i></a>`;
var editBtn = `<a class="editBtn btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" title="修改节点"> <i class="fa fa-fw fa-edit rbg "></i></a>`;
// 获取当前就带你的级别数据
var level = treeNode.level;
// 声明变量存储拼装号的按钮代码
var btnHtml = '';
if (level === 0) {
// 根节点: 添加子节点
btnHtml = addBtn;
} else if (level === 1) {
// 分支节点: 添加子节点 修改 <没有子节点可以删除 有子节点不能删除>
btnHtml = addBtn + ' ' + editBtn;
// 获取当前节点的子节点数量
var length = treeNode.children.length;
(length === 0) ? btnHtml += ' ' + removeBtn : '';
} else if (level === 2) {
// 叶子节点: 修改 删除
btnHtml = editBtn + ' ' + removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
$('#' + anchorId).after(`<span id="${btnGroupId}">${btnHtml}</span>`);
}
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId, treeNode) {
// 拼接按钮组的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 移除对应的元素
$('#' + btnGroupId).remove();
}
- menu-page.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zn-CN">
<%@include file="/WEB-INF/include-head.jsp" %>
<link rel="stylesheet" href="ztree/zTreeStyle.css"/>
<script type="text/javascript" src="jquery/jquery.pagination.js"></script>
<script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
<script src="crowd/js/my-menu.js?v=10" type="text/javascript"></script>
<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 id="treeDemo" class="ztree"></ul>
</div>
</div>
</div>
</div>
</div>
<%@include file="/WEB-INF/modal-menu-add.jsp" %>
<%@include file="/WEB-INF/modal-menu-confirm.jsp" %>
<%@include file="/WEB-INF/modal-menu-edit.jsp" %>
</body>
</html>
modal-menu-add.jsp
添加模态框
<%--
菜单维护的添加模态框
--%>
<%@ page language="java" contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"%>
<div id="menuAddModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">尚筹网-添加节点</h4>
</div>
<form>
<div class="modal-body">
请输入节点名称:<input type="text" name="name" /><br />
请输入URL地址:<input type="text" name="url" /><br />
<i class="glyphicon glyphicon-th-list"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-th-list" />
<i class="glyphicon glyphicon-dashboard"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-dashboard" />
<i class="glyphicon glyphicon glyphicon-tasks"></i>
<input type="radio" name="icon" value="glyphicon glyphicon glyphicon-tasks" />
<i class="glyphicon glyphicon-user"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-user" />
<i class="glyphicon glyphicon-king"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-king" />
<i class="glyphicon glyphicon-lock"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-lock" />
<i class="glyphicon glyphicon-ok"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-ok" />
<i class="glyphicon glyphicon-check"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-check" />
<i class="glyphicon glyphicon-th-large"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-th-large" /> <br />
<i class="glyphicon glyphicon-picture"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-picture" />
<i class="glyphicon glyphicon-equalizer"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-equalizer" />
<i class="glyphicon glyphicon-random"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-random" />
<i class="glyphicon glyphicon-hdd"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-hdd" />
<i class="glyphicon glyphicon-comment"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-comment" />
<i class="glyphicon glyphicon-list"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-list" />
<i class="glyphicon glyphicon-tags"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-tags" />
<i class="glyphicon glyphicon-list-alt"></i>
<input type="radio" name="icon" value="glyphicon glyphicon-list-alt" />
<br />
</div>
<div class="modal-footer">
<button id="menuSaveBtn" type="button" class="btn btn-default"><i class="glyphicon glyphicon-plus"></i> 保存</button>
<button id="menuResetBtn" type="reset" class="btn btn-primary"><i class="glyphicon glyphicon-refresh"></i> 重置</button>
</div>
</form>
</div>
</div>
</div>
2.5.2 修改节点
2.5.2.1 目标
- 修改当前节点的基本属性, 保存到数据库并刷新树形结构的显示
2.5.2.2 思路
2.5.2.3 前端代码
- 将按钮组的 id 设置好
- 给按钮绑定单击响应函数
- 给模态框中的更新按钮绑定单击响应函数
my-menu.js
$(function () {
// 调用专门封装好的函数初始化树形结构
generateTree();
// 给修改子节点按钮绑定单击响应函数
$('#treeDemo').on('click', '.editBtn', function () {
// 将当前节点的 id 保存到全局变量
window.id = this.id;
console.log(window.id);
// 获取 zTreeObj 对象
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 选中
// 回显 radio 可以这样理解: 被选中的 radio 的 value 属性可以组成一个数组, 然后再用这个数组设置回 radio, 就能够把对应的值选中
$('#menuEditModal [name=icon]').val([currentNode.icon]);
// 打开模态框
$('#menuEditModal').modal('show');
});
// 给更新模态框中的更新按钮绑定点击响应函数
$('#menuEditBtn').click(function () {
// 收集表单数据
var name = $('#menuEditModal [name=name]').val();
var url = $('#menuEditModal [name=url]').val();
var icon = $('#menuEditModal [name=icon]:checked').val();
// 发送 ajax 请求
$.ajax({
url: 'menu/update.json',
type: 'put',
contentType: "application/json;charset=UTF-8",
data: JSON.stringify({
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 if (result === 'FAILED') {
layer.msg('操作失败!' + response.message);
}
},
error: function (response) {
layer.msg(' 请求失败! ' + response.status + ' ' + response.statusText);
}
});
// 关闭模态框
$('#menuEditModal').modal('hide');
});
// 给添加子节点按钮绑定单击响应函数
$('#treeDemo').on('click', '.addBtn', function () {
// 将当前节点的 id, 作为新节点的 pid 保存到全局变量
window.pid = this.id;
// 打开模态框
$('#menuAddModal').modal('show');
});
// 给添加子节点的新增模态框的保存按钮 绑定单击响应函数
$('#menuSaveBtn').click(function () {
// 收集表单项中用户输入的数据
var name = $.trim($('#menuAddModal [name=name]').val());
var url = $.trim($('#menuAddModal [name=url]').val());
// 单选按钮要定位到'被选中的那一个'
var icon = $.trim($('#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 if (result === 'FAILED') {
layer.msg('操作失败!' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText);
}
});
// 关闭模态框
$('#menuAddModal').modal('hide');
// 清空表单
// jQuery 对象调用 click() 函数, 里面不传任何参数, 相当于用户点击了一次
$('#menuResetBtn').click();
});
})
// 生成树形结构的函数
function generateTree() {
$.ajax({
url: 'menu/get/whole/tree.json',
type: 'get',
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
// 1. 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
view: {
addDiyDom: myAddDiyDom,
addHoverDom: myAddHoverDom,
removeHoverDom: myRemoveHoverDom
},
data: {
key: {
url: 'no'
}
}
};
// 2. 准备生成树形结构的 JSON 数据
var data = response.data;
// 3. 初始化树形结构
$.fn.zTree.init($('#treeDemo'), setting, data);
} else if (result === 'FAILED') {
layer.msg('操作失败! ' + response.message);
}
},
error: function (response) {
layer.msg(response.status + ' ' + response.statusText)
}
})
}
// 修改默认的图标
function myAddDiyDom(treeId, treeNode) {
// treeId 是整个树形结构附着的 ul 标签的 id
// console.log("treeId:" + treeId);
// 当前树形节点的全部的数据, 包括从后端查询得到的 Menu 对象的全部属性
// console.log(treeNode)
// zTree 生成 id 的规则
// 例子: treeDome_7_ico
// 解析:ul 标签的 id_当前节点的序号_功能
// 提示: ul 标签的id_当前节点的序号" - 这个部分可以通过 treeNode 的 tId 属性得到
// 根据 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>
// 按钮组出现的位置: 节点中 treeDDemo_n_a 超链接的后面
// 为了在需要移除按钮组的时候能够精确定位按钮组所在 span, 需要给 spna 设置有规律的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 判断一下以前是否已经添加了按钮组
if ($('#' + btnGroupId).length > 0) {
return false;
}
// 执行在超链接后面附加 span 元素的操作
// 准备各个按钮的 HTML 标签
var addBtn = `<a id="${treeNode.id}" class="addBtn btn btn-info dropdown-toggle btn-xs" style="margin-left:10px;padding-top:0px;" 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;" 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;" title="修改节点"> <i class="fa fa-fw fa-edit rbg "></i></a>`;
// 获取当前就带你的级别数据
var level = treeNode.level;
// 声明变量存储拼装号的按钮代码
var btnHtml = '';
if (level === 0) {
// 根节点: 添加子节点
btnHtml = addBtn;
} else if (level === 1) {
// 分支节点: 添加子节点 修改 <没有子节点可以删除 有子节点不能删除>
btnHtml = addBtn + ' ' + editBtn;
// 获取当前节点的子节点数量
var length = treeNode.children.length;
(length === 0) ? btnHtml += ' ' + removeBtn : '';
} else if (level === 2) {
// 叶子节点: 修改 删除
btnHtml = editBtn + ' ' + removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
$('#' + anchorId).after(`<span id="${btnGroupId}">${btnHtml}</span>`);
}
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId, treeNode) {
// 拼接按钮组的 id
var btnGroupId = treeNode.tId + '_btnGrp';
// 移除对应的元素
$('#' + btnGroupId).remove();
}
2.5.2.4 后端代码
- MenuHandler
@PutMapping("/menu/update.json")
public ResultEntity<String> updateMenu(@RequestBody Menu menu) {
menuService.updateMenu(menu);
return ResultEntity.successWithoutData();
}
- MenuServiceImpl
@Override
public void updateMenu(Menu menu) {
// 由于 pid 没有传入, 一定要使用有选择的更新, 保证 'pid' 字段不会被置空
menuMapper.updateByPrimaryKeySelective(menu);
}
2.5.3 删除节点
2.5.3.1 目标
- 删除当前节点
2.5.3.2 思路
2.5.3.3 前端代码
// 给 "X" 按钮绑定单击响应函数
$('#treeDemo').on('click', '.removeBtn', function () {
window.id = this.id;
// 获取 zTreeObj 对象
var zTreeObj = $.fn.zTree.getZTreeObj('treeDemo');
var currentNode = zTreeObj.getNodeByParam('id', this.id);
$('#removeNodeSpan').html(`【<i class="${currentNode.icon}"></i> ${currentNode.name}】`);
$('#menuConfirmModal').modal('show');
});
// 给确认模态框中的 OK 按钮绑定单击响应函数
$('#confirmBtn').click(function () {
// 发送 ajax 请求
$.ajax({
url: 'menu/delete.json',
type: 'delete',
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(window.id),
dataType: 'json',
success: function (response) {
var result = response.result;
if (result === 'SUCCESS') {
layer.msg('操作成功!');
// 刷新树形结构
generateTree();
} else if (result === 'FAILED') {
layer.msg('操作失败!' + response.message);
}
},
error: function (response) {
layer.msg(' 请求失败! ' + response.status + ' ' + response.statusText);
}
});
// 关闭模态框
$('#menuConfirmModal').modal('hide');
});
2.5.3.4 后端代码
- MenuHandler
@DeleteMapping("/menu/delete.json")
public ResultEntity<String> deleteMenu(@RequestBody Integer id) {
menuService.deleteMenuById(id);
return ResultEntity.successWithoutData();
}
- MenuServiceImpl
@Override
public void deleteMenuById(Integer id) {
menuMapper.deleteByPrimaryKey(id);
}
- 错误到了主分支
- 记得检查当前分支:
git branch