菜单维护
一、树形结构基础知识
(1) 节点类型
(2) 在数据库表中表示树形结构
① 创建菜单的数据库表
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)
);
② 插入数据
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');
③ 关联方式
子节点通过 pid 字段关联到父节点的 id 字段,建立父子关系。
(3) 在 Java 类中表示树形结构
① 基本方式
在 Menu 类中使用 List<Menu> children
属性存储 当前节点的子节点。
② 为了配合 zTree 所需要添加的属性
pid 属性:找到父节点
name 属性:作为节点名称
icon 属性:当前节点使用的图标
open 属性:控制节点是否默认打开
url 属性:点击节点时跳转的位置
(4) 按钮增删改查的规则
evel 0:根节点
添加子节点
level 1:分支节点
修改
添加子节点
没有子节点:可以删除
有子节点:不能删除
level 2:叶子节点
修改
删除
二、 菜单维护:页面显示树形结构
(1) 思路
数据库查询全部 → Java 对象组装 → 页面上使用 zTree 显示
(2) 代码:逆向工程
生成后和之前一样,放到该放的位置
<table tableName="t_menu" domainObjectName="Menu" />
(3) 逆向生成的 Menu 实体类需要做一些调整:
添加两个属性,并生成get set方法,以及构造器,tostring()等方法
// 存储子节点的集合,初始化是为了避免空指针异常
private List<Menu> children = new ArrayList<>();
// 控制节点是否默认为打开装,设置为 true 表示默认打开
private Boolean open = true;
(4) 将数据在 Java 代码中组装成树形结构
① MenuHandler
controller 为直接写为 restcontroller , 所以不用加 responsebody
@RequestMapping("/menu/get/whole/tree.json")
public ResultEntity<Menu> getAll() {
// 查询全部的menu对象
List<Menu> menuList = menuService.getAll();
// 声明一个变量用来存储找到的根节点
Menu root = null;
// 创建 Map 对象用来存储 id 和 Menu 对象的对应关系便于查找父节点
Map<Integer,Menu> menuMap = new HashMap<>();
// 遍历 menuList 找出所有 id 所对应的 menu
for (Menu menu : menuList) {
// 获取当前菜单的id
Integer id = menu.getId();
// 填充menuMap: id所对应的menu对象
menuMap.put(id,menu);
}
for (Menu menu : menuList) {
// 获取父节点id
Integer pid = menu.getPid();
// 若父节点为空,则该节点为根节点
if (pid == null) {
// 赋值根节点
root = menu;
continue;
}
// 若父节点不为空,则找出对应关系
Menu father = menuMap.get(pid);
// 将当前节点放入父节点的子节点列表中
father.getChildren().add(menu);
}
// 根节点包含了整个树形结构,返回根节点就是返回整个树
return ResultEntity.successWithData(root);
}
② MenuService
public List<Menu> getAll();
③ MenuServiceImpl
@Override
public List<Menu> getAll() {
return menuMapper.selectByExample(new MenuExample());
}
(5) 代码:跳转页面
<mvc:view-controller path="/menu/to/page.html" view-name="menu-page" />
(6) 引入 zTree 环境
<link rel="stylesheet" href="ztree/zTreeStyle.css"/>
<script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
(7) 页面上使用 zTree 初步显示树形结构(假数据)
// 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);
(8) 代码:在页面上使用真实数据显示树形结构
// 准备生成树形结构的 JSON 数据
$.ajax({
url: "menu/get/whole/tree.json",
type: "post",
dataType: "json",
success: function (response) {
// 获取响应的操作结果
var result = response.operationResult;
if (result == "SUCCESS") {
// 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = { };
// 从响应体中获取用来生成树形结构的 JSON 数据
var zNodes = response.queryData;
// 初始化树形结构
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
if (result == "FAILED") {
layer.msg(response.message);
}
}
});
(9) 代码:修改默认图标为真实图标
function myAddDiyDom(treeId, treeNode) {
// treeId是整个树形结构附着的ul标签的id
console.log("treeId=" + treeId);
// 当前树形节点的全部的数据,包括从后端查询得到的Menu对象的全部属性
console.log(treeNode);
// zTree生成图标的规则
// 例子:treeDemo_7_ico
// 解析:ul标签的id_当前节点的序号_功能
// 提示:"ul标签的id_当前节点的序号"可以通过访问treeNode的tId属性得到
// 根据id的生成规则拼接出来span标签的id
var spanId = treeNode.tId + "_ico";
// 根据id的生成规则拼接出来span的class
// 根据控制图标的span标签的id找到这个span标签
// 删除旧的class
// 添加新的class
$("#" + spanId).removeClass()
.addClass(treeNode.icon);
}
(10) 实现“点了不跑”
// 创建 JSON 对象用于存储对 zTree 所做的设置
var setting = {
"view": {
"addDiyDom": myAddDiyDom
},
"data": {
"key": {
"url": "maomi" //使页面不跳转
}
}
};
(11) 代码:显示按钮组
① 思路和步骤
第一步:控制<span>A</span>是否显示
第二步:明确具体按钮的添加规则
第三步:准备好按钮的 HTML 标签
第四步:根据按钮规则把按钮填充到 span 中
② myRemoveHoverDom(treeId, treeNode)函数
// 在鼠标离开节点范围时删除按钮组
function myRemoveHoverDom(treeId, treeNode) {
// 拼接按钮组的 id
var btnGroupId = treeNode.tId + "_btnGrp";
// 移除对应的元素
$("#"+btnGroupId).remove();
}
③ myAddHoverDom(treeId, treeNode)函数
// 在鼠标移入节点范围时添加按钮组
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='btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title='添加子节点'> <i class='fafa - fwfa - plusrbg'></i></a>";
var removeBtn = "<a id='" + treeNode.id + "' class='btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title=' 删 除 节 点 '> <i class='fafa - fwfa - timesrbg'></i></a>";
var editBtn = "<a id='" + treeNode.id + "' class='btn btn-info dropdown-toggle btn-xs' style='margin-left:10px;padding-top:0px;' href='#' title=' 修 改 节 点 '> <i class='fafa - fwfa - editrbg'></i></a>";
// 获取当前节点的级别数据
var level = treeNode.level;
// 声明变量存储拼装好的按钮代码
var btnHTML = "";
// 判断当前节点的级别
if (level == 0) {
// 级别为 0 时是根节点,只能添加子节点
btnHTML = addBtn;
}
if (level == 1) {
// 级别为 1 时是分支节点,可以添加子节点、修改
btnHTML = addBtn + " " + editBtn;
// 获取当前节点的子节点数量
var length = treeNode.children.length;
// 如果没有子节点,可以删除
if (length == 0) {
btnHTML = btnHTML + " " + removeBtn;
}
}
if (level == 2) {
// 级别为 2 时是叶子节点,可以修改、删除
btnHTML = editBtn + " " + removeBtn;
}
// 找到附着按钮组的超链接
var anchorId = treeNode.tId + "_a";
// 执行在超链接后面附加 span 元素的操作
$("#" + anchorId).after("<span id='" + btnGroupId + "'>" + btnHTML + "</span>");
}
④ 代码:把生成树形结构的代码封装到函数
menu-page.jsp 页面上调用函数即可
$(function(){
// 调用专门封装好的函数初始化树形结构
generateTree();
});
三、 菜单维护:添加子节点
① 思路
② 给“+”按钮绑定单击响应函数
// 为 '+' 号创建点击事件
$("#treeDemo").on("click",".addBtn",function () {
// 添加子节点,当前节点就是添加子节点后的父节点,保存到全局变量中
window.pid = this.id;
// 打开模态框
$("#menuAddModal").modal("show");
// 禁止页面跳转
return false;
});
③ 给添加子节点的模态框中的保存按钮绑定单击响应函数
// 添加子节点:点击保存后发送ajax请求
$("#menuSaveBtn").click(function () {
// 收集表单项中用户输入的数据
var name = $("#menuAddModal [name=name]").val();
var url = $("#menuAddModal [name=url]").val();
// radio要收集的是被选中的那一个
var icon = $("#menuAddModal [name=icon]:checked").val();
$.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.operationResult;
if (result == "SUCCESS") {
layer.msg("添加成功!");
// 刷新菜单页面
generateTree();
}
if(result == "FAILED") {
layer("添加失败: " + response.message);
}
},
error: function (response) {
layer.msg((response.status+" "+response.statusText));
}
});
// 关闭模态框
$("#menuAddModal").modal("hide");
// 清空表单
// jQuery 对象调用 click()函数,里面不传任何参数,相当于用户点击了一下
$("#menuResetBtn").click();
});
④ 后端代码 MenuHandler
// 添加子节点
@RequestMapping("/menu/save.json")
public ResultEntity<String> addMenu(Menu menu) {
// 执行添加子节点
menuService.addMenu(menu);
return ResultEntity.successWithoutData();
}
⑤ 后端代码 MenuService
void addMenu(Menu menu);
⑥ 后端代码 MenuServiceImpl
@Override
public void addMenu(Menu menu) {
menuMapper.insert(menu);
}
四、菜单维护:修改菜单节点
① 思路
② 给 修改 按钮绑定单击响应函数
// 为 修改节点 按钮创建点击事件
$("#treeDemo").on("click",".editBtn",function () {
// 将当前节点的 id 保存到全局变量
window.id = this.id;
// 显示模态框
$("#menuEditModal").modal("show");
// 获取 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 可以这样理解:被选中的 radio 的 value 属性可以组成一个数组,
// 然后再用这个数组设置回 radio,就能够把对应的值选中
$("#menuEditModal [name=icon]").val([currentNode.icon]);
return false;
});
③ 给更新节点的模态框中的更新按钮绑定单击响应函数
// 为修改模态框中 更新 按钮绑定单击事件
$("#menuEditBtn").click(function () {
// 收集信息
var name = $("#menuEditModal [name=name]").val();
var url = $("#menuEditModal [name=url]").val();
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.operationResult;
if (result == "SUCCESS") {
layer.msg("修改成功!");
// 刷新菜单页面
generateTree();
}
if(result == "FAILED") {
layer("修改失败: " + response.message);
}
},
error: function (response) {
layer.msg((response.status+" "+response.statusText));
}
});
// 隐藏模态框
$("#menuEditModal").modal("hide");
});
④ 后端代码 MenuHandler
// 修改菜单
@RequestMapping("/menu/update.json")
public ResultEntity<String> editMenu(Menu menu) {
// 执行修改菜单
menuService.editMenu(menu);
return ResultEntity.successWithoutData();
}
⑤ 后端代码 MenuService
void editMenu(Menu menu);
⑥ 后端代码 MenuServiceImpl
@Override
public void editMenu(Menu menu) {
// 由于 pid 没有传入,一定要使用有选择的更新,保证“pid”字段不会被置空
menuMapper.updateByPrimaryKeySelective(menu);
}
五、菜单维护:修改菜单节点
① 思路
② 给“×”按钮绑定单击响应函数
// 为 x 按钮绑定单击事件
$("#treeDemo").on("click",".removeBtn ",function () {
// 显示模态框
$("#menuConfirmModal").modal("show");
// 将当前节点的 id 保存到全局变量
window.id = this.id;
// 获取 zTreeObj 对象
var zTreeObj = $.fn.zTree.getZTreeObj("treeDemo");
// 根据 id 属性查询节点对象
// 用来搜索节点的属性名
var key = "id";
// 用来搜索节点的属性值
var value = window.id;
var currentNode = zTreeObj.getNodeByParam(key, value);
// 将当前要删除的icon和name展示在模态框中 (不知道代码哪里有问题,回显和这里的icon都不显示)
$("#removeNodeSpan").html(" 【 <iclass='"+currentNode.icon+"'></i>"+currentNode.name+"】");
// 禁止页面跳转
return false;
});
③ 给确认模态框中的 OK 按钮绑定单击响应函数
// 为模态框中的 ok 绑定单击事件
$("#confirmBtn").click(function () {
$.ajax({
url: "menu/remove.json",
type: "post",
data: {
id: window.id
},
dataType: "json",
success: function (response) {
var result = response.operationResult;
if (result == "SUCCESS") {
layer.msg("删除成功!");
// 刷新菜单页面
generateTree();
}
if(result == "FAILED") {
layer("删除失败: " + response.message);
}
},
error: function (response) {
layer.msg((response.status+" "+response.statusText));
}
});
// 隐藏模态框
$("#menuConfirmModal").modal("hide");
});
④ 后端代码 MenuHandler
// 删除菜单节点
@RequestMapping("/menu/remove.json")
public ResultEntity<String> removeMenu(int id) {
// 执行删除菜单节点
menuService.removeMenu(id);
return ResultEntity.successWithoutData();
}
⑤ 后端代码 MenuService
void removeMenu(int id);
⑥ 后端代码 MenuServiceImpl
@Override
public void removeMenu(int id) {
// 执行删除菜单节点
menuMapper.deleteByPrimaryKey(id);
}