1 建模
1.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)
);
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 形成树形结构的关键依据
在数据库表中通过pid字段,让当前记录指向它的父节点的记录,从而形成树形结构。
一条记录关联了同一个表中的其他记录,称之为自关联。
1.3 逆向工程
<table tableName="t_menu" domainObjectName="Menu" />
生成的Menu实体类需要做一些修改
// 对应数据库表主键
private Integer id;
// 对应父节点id(如果pid为null,则说明当前节点是根节点)
private Integer pid;
// 节点名称
private String name;
// 节点对应的URL地址
private String url;
// 节点的图标
private String icon;
// 当前节点的子节点集合,设置默认值是为了避免组装节点时空指针异常
private List<Menu> children = new ArrayList<>();
// 控制节点展开还是折叠,设置为true是让整个树形菜单默认展开
private Boolean open = true;
2 在页面显示树形结构
2.1 后端操作
把树形结构组装好,具体来说是:给前端返回根节点对象。在根节点中包含子节点,子节点中再包含下一级的子节点。
handler方法中的具体操作:
@RequestMapping("/menu/get/whole/tree")
public ResultEntity<Menu> getWholeTree() {
// 1.查询所有的树形节点用于组装
List<Menu> menuList = menuService.getAll();
// 2.将List<Menu>转换为Map<Menu的id,Menu>
Map<Integer,Menu> menuMap = new HashMap<>();
for (Menu menu : menuList) {
Integer id = menu.getId();
menuMap.put(id, menu);
}
// 3.声明变量用于存储根节点对象
Menu rootNode = null;
// 4.遍历List<Menu>
for (Menu menu : menuList) {
// 5.获取当前Menu对象的pid属性
Integer pid = menu.getPid();
// 6.判断pid是否为null
if(pid == null) {
// 7.如果pid为null,说明当前节点是根节点,所以赋值
rootNode = menu;
// 8.根节点没有父节点,所以不必找父节点组装,本次for循环停止执行,继续执行下一次循环
continue ;
}
// 9.既然pid不为null,那么我们根据这个pid查找当前节点的父节点。
Menu father = menuMap.get(pid);
// 10.组装:将menu添加到maybeFather的子节点集合中
father.getChildren().add(menu);
}
return ResultEntity.successWithData(rootNode);
}
service方法:
@Override
public List<Menu> getAll() {
return menuMapper.selectByExample(new MenuExample());
}
2.2 前端操作
2.2.1 创建menu-page.jsp
主体内容如下:
<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>
配置view-controller跳转到menp-page.jsp
<mvc:view-controller path="/menu/to/page.html" view-name="menu-page"/>
sidebar中的超链接也做相应调整
<li style="height: 30px;"><a href="menu/to/page.html"><i class="glyphicon glyphicon-lock"></i> 菜单维护</a></li>
2.2.2 加入zTree环境
<script type="text/javascript" src="ztree/jquery.ztree.all-3.5.min.js"></script>
<script type="text/javascript" src="script/my-menu.js"></script>
2.2.3 显示树形结构
$(function(){
// setting对象中包含zTree的设置属性
var setting = {
"view": {
"addDiyDom": showMyIcon
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止点击节点后跳转
}
}
};
// 发送Ajax请求获取zNodes数据
$.ajax({
"url":"menu/get/whole/tree.json",
"type":"post",
"dataType":"json",
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
// 用于显示树形结构的节点数据
var zNodes = response.data;
// 执行树形结构的初始操作
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
if(result == "FAILED") {
layer.msg("加载菜单数据失败!原因是:"+response.message);
}
},
"error":function(response){
layer.msg("加载菜单数据失败!原因是:"+response.message);
}
});
});
2.2.4 声明函数将图标修改为自定义图标
// 由setting.view.addDiyDom属性引用,负责显示自定义图标
// treeId是<ul id="treeDemo" class="ztree"></ul>的id属性值
// treeNode对应每一个树形节点
function showMyIcon(treeId, treeNode) {
// 获取当前节点的id
var currentNodeId = treeNode.tId;
// 获取新的class值用于替换
var newClass = treeNode.icon;
// 在当前节点id之后附加“_ico”得到目标span的id
var targetSpanId = currentNodeId + "_ico";
// 将目标span的旧class移除,添加新class
$("#"+targetSpanId)
.removeClass()
.addClass(newClass)
.css("background","");
}
附:节点图标修改前后HTML代码变化
<span
id="treeDemo_4_ico"
title=""
treenode_ico=""
class="button ico_docu"
style="background:url(glyphicon glyphicon-user) 0 0 no-repeat;">
</span>
<span
id="treeDemo_2_ico"
title=""
treenode_ico=""
class="glyphicon glyphicon-dashboard"
style="background:url(glyphicon glyphicon-dashboard) 0 0 no-repeat;">
</span>
2.2.5 节点结构
<li 节点>
<span 展开折叠部分图标></span>
<a 节点内容>
<span 节点的具体图标></span>
<span>节点文本内容</span>
</a>
<ul 子节点></ul>
</li>
id结构:treeDemo_节点序号_节点具体部件
id="treeDemo_3_ul" id="treeDemo_3_ico"
2.2.6 阻止点击节点后跳转
使用了setting.data.key.url属性。
2.3 封装函数实现生成树形结构操作
function initWholeTree() {
// setting对象中包含zTree的设置属性
var setting = {
"view": {
"addDiyDom": showMyIcon
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止点击节点后跳转
}
}
};
// 发送Ajax请求获取zNodes数据
$.ajax({
"url":"menu/get/whole/tree.json",
"type":"post",
"dataType":"json",
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
// 用于显示树形结构的节点数据
var zNodes = response.data;
// 执行树形结构的初始操作
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
if(result == "FAILED") {
layer.msg("加载菜单数据失败!原因是:"+response.message);
}
},
"error":function(response){
layer.msg("加载菜单数据失败!原因是:"+response.message);
}
});
}
3 生成按钮组
3.1 生成按钮组规则
3.1.1 根节点(level 0)
- 增加子节点
3.1.2 level为1的有子节点的节点
- 增加子节点
- 修改
3.1.3 level为1的无子节点的节点
- 增加子节点
- 删除
- 修改
3.1.4 level为2的节点
- 删除
- 修改
3.2 设计按钮组所在span的id
- 规则:treeDemo
_序号_btnGrp - 举例:treeDemo_20_btnGrp
3.3 各个按钮HTML标签
<span id="treeDemo_20_btnGrp">
<a onclick="showAddModal(this)"
id="19"
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>
<a onclick="showEditModal(this)"
id="19"
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>
<a onclick="showConfirmModal(this)"
id="19"
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>
</span>
3.4 声明函数生成按钮组
// 专门生成按钮组的函数
function generateBtnGrp(treeNode) {
// 获取当前节点的id(HTML中li标签的id)
var treeNodeId = treeNode.tId;
// 获取当前节点在数据库中的id值
// Menu实体类中的属性,都可以通过treeNode以“.属性名”的方式直接访问
var menuId = treeNode.id;
// 组装按钮组所在的span的id
var btnGrpSpanId = treeNodeId + "_btnGrp";
// 生成span标签对应的jQuery对象
var $span = $("<span id='"+btnGrpSpanId+"'></span>");
// 获取当前节点的level值
var level = treeNode.level;
// 声明三种按钮
var addBtn = "<a onclick='showAddModal(this)' id='"+menuId+"' 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 editBtn = "<a onclick='showEditModal(this)' id='"+menuId+"' 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 removeBtn = "<a onclick='showConfirmModal(this)' id='"+menuId+"' 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>";
// 根据level进行判断
if(level == 0) {
$span.append(addBtn);
}
if(level == 1) {
if(treeNode.children.length > 0) {
$span.append(addBtn+" "+editBtn);
} else {
$span.append(addBtn+" "+editBtn+" "+removeBtn);
}
}
if(level == 2) {
$span.append(editBtn+" "+removeBtn);
}
return $span;
}
3.5 addHoverDom(treeId, treeNode)函数
// 在鼠标移入节点范围时添加自定义控件
function addHoverDom(treeId, treeNode) {
// 在执行添加前,先进行判断,如果已经添加过,就停止执行
// 组装按钮组所在的span标签的id
var btnGrpSpanId = treeNode.tId + "_btnGrp";
var btnGrpSpanLength = $("#"+btnGrpSpanId).length;
if(btnGrpSpanLength > 0) {
return ;
}
// 由于按钮组是放在当前节点中的超链接(a标签)后面,所以先定位到a标签
// 按id生成规则组装a标签的id
var anchorId = treeNode.tId + "_a";
// 调用已封装函数生成按钮组
var $btnGrpSpan = generateBtnGrp(treeNode);
// 在a标签后面追加按钮组
$("#"+anchorId).after($btnGrpSpan);
}
3.6 removeHoverDom(treeId, treeNode)函数
// 在鼠标移出节点范围时删除自定义控件
function removeHoverDom(treeId, treeNode) {
// 组装按钮组所在的span标签的id
var btnGrpSpanId = treeNode.tId + "_btnGrp";
// 删除对应的元素
$("#"+btnGrpSpanId).remove();
}
3.7 加入add和remove的setting
var setting = {
"view": {
"addDiyDom": showMyIcon,
"addHoverDom": addHoverDom,
"removeHoverDom": removeHoverDom
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止点击节点后跳转
}
}
};
4 添加子节点
4.1 准备模态框
模态框的HTML代码,参考下面文件的代码:
include-modal-menu-add.jsp
<%@ include file="/WEB-INF/include-modal-menu-add.jsp" %>
4.2 showAddModal()函数
// 在点击添加按钮时执行这个函数,打开模态框
function showAddModal(currentBtn) {
// 打开模态框
$("#menuAddModal").modal("show");
// 将当前节点的id存入全局变量
window.menuId = currentBtn.id;
}
4.3 给保存按钮绑定单击响应函数
$("#menuAddBtn").click(function(){
// 收集表单填写的数据
var name = $.trim($("#menuAddModal [name='name']").val());
var url = $.trim($("#menuAddModal [name='url']").val());
var icon = $("#menuAddModal [name='icon']:checked").val();
if(name == null || name == "") {
layer.msg("请填写菜单项名称!");
return ;
}
if(url == null || url == "") {
layer.msg("请填写菜单项对应的访问地址!");
return ;
}
// 发送Ajax请求
$.ajax({
"url":"menu/save.json",
"type":"post",
"dataType":"json",
"data":{
"name":name,
"url":url,
"pid":window.menuId, // 当前操作的节点是新节点的父节点
"icon":icon
},
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
layer.msg("操作成功!");
initWholeTree();
}
if(result == "FAILED") {
layer.msg(response.message);
}
},
"error":function(response){
layer.msg(response.message);
}
});
$("#menuAddModal").modal("hide");
});
4.4 后端代码
@RequestMapping("/menu/save")
public ResultEntity<String> saveMenu(Menu menu) {
menuService.saveMenu(menu);
return ResultEntity.successWithoutData();
}
5 更新节点
5.1 准备模态框
include-modal-menu-edit.jsp
<%@ include file="/WEB-INF/include-modal-menu-edit.jsp" %>
5.2 showEditModal()函数
// 在点击编辑按钮时执行这个函数,打开模态框
function showEditModal(currentBtn) {
// 打开模态框
$("#menuEditModal").modal("show");
// 获取当前节点的id存入全局变量
window.menuId = currentBtn.id;
// 发送请求查询Menu对象
$.ajax({
"url":"menu/get/"+window.menuId+".json",
"type":"get",
"dataType":"json",
"success":function(response) {
var result = response.result;
if(result == "SUCCESS") {
// 从响应数据中获取Menu对象
var menu = response.data;
// 获取各个属性值
var name = menu.name;
var url = menu.url;
var icon = menu.icon;
// 设置各个对应的表单项
$("#menuEditModal [name='name']").val(name);
$("#menuEditModal [name='url']").val(url);
// radio需要让value值和后端查询到的icon一致的设置为被选中
$("#menuEditModal [name='icon'][value='"+icon+"']").attr("checked",true);
}
if(result == "FAILED") {
layer.msg(response.message);
}
},
"error":function(response) {
layer.msg(response.message);
}
});
}
handler
@RequestMapping("/menu/get/{menuId}")
public ResultEntity<Menu> getMenuById(@PathVariable("menuId") Integer menuId){
Menu menu = menuService.getMenuById(menuId);
return ResultEntity.successWithData(menu);
}
service
@Override
public Menu getMenuById(Integer menuId) {
return menuMapper.selectByPrimaryKey(menuId);
}
5.3 给更新按钮绑定单击响应函数
$("#menuEditBtn").click(function(){
// 收集表单填写的数据
var name = $.trim($("#menuEditModal [name='name']").val());
var url = $.trim($("#menuEditModal [name='url']").val());
var icon = $("#menuEditModal [name='icon']:checked").val();
if(name == null || name == "") {
layer.msg("请填写菜单项名称!");
return ;
}
if(url == null || url == "") {
layer.msg("请填写菜单项对应的访问地址!");
return ;
}
// 发送Ajax请求
$.ajax({
"url":"menu/update.json",
"type":"post",
"dataType":"json",
"data":{
"id":window.menuId,
"name":name,
"url":url,
"icon":icon
},
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
layer.msg("操作成功!");
initWholeTree();
}
if(result == "FAILED") {
layer.msg(response.message);
}
},
"error":function(response){
layer.msg(response.message);
}
});
$("#menuEditModal").modal("hide");
});
5.4 执行更新的后端代码
handler
@RequestMapping("/menu/update")
public ResultEntity<String> updateMenu(Menu menu){
menuService.updateMenu(menu);
return ResultEntity.successWithoutData();
}
service
@Override
public void updateMenu(Menu menu) {
menuMapper.updateByPrimaryKey(menu);
}
MenuMapper.xml
<update id="updateByPrimaryKey" parameterType="com.rgh.crowd.funding.entity.Menu" >
update t_menu
set
name = #{name,jdbcType=VARCHAR},
url = #{url,jdbcType=VARCHAR},
icon = #{icon,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>
注意:把SQL中的pid修改去掉,不改变节点的父节点。