1 建模
1.1 創(chuàng)建數(shù)據(jù)庫
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,'系統(tǒng)權(quán)限菜單','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','權(quán)限管理','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','業(yè)務(wù)審核','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','業(yè)務(wù)管理','glyphicon glyphicon-th-large',NULL);
insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('12','11','資質(zhì)維護','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','項目標(biāo)簽','glyphicon glyphicon-tags','tag/index.htm');
insert into `t_menu` (`id`, `pid`, `name`, `icon`, `url`) values('19','1','參數(shù)管理','glyphicon glyphicon-list-alt','param/index.htm');
1.2 形成樹形結(jié)構(gòu)的關(guān)鍵依據(jù)
在數(shù)據(jù)庫表中通過pid字段稚茅,讓當(dāng)前記錄指向它的父節(jié)點的記錄怎棱,從而形成樹形結(jié)構(gòu)九默。
一條記錄關(guān)聯(lián)了同一個表中的其他記錄蒸殿,稱之為自關(guān)聯(lián)择克。
1.3 逆向工程
<table tableName="t_menu" domainObjectName="Menu" />
生成的Menu實體類需要做一些修改
// 對應(yīng)數(shù)據(jù)庫表主鍵
private Integer id;
// 對應(yīng)父節(jié)點id(如果pid為null恬总,則說明當(dāng)前節(jié)點是根節(jié)點)
private Integer pid;
// 節(jié)點名稱
private String name;
// 節(jié)點對應(yīng)的URL地址
private String url;
// 節(jié)點的圖標(biāo)
private String icon;
// 當(dāng)前節(jié)點的子節(jié)點集合,設(shè)置默認值是為了避免組裝節(jié)點時空指針異常
private List<Menu> children = new ArrayList<>();
// 控制節(jié)點展開還是折疊肚邢,設(shè)置為true是讓整個樹形菜單默認展開
private Boolean open = true;
2 在頁面顯示樹形結(jié)構(gòu)
2.1 后端操作
把樹形結(jié)構(gòu)組裝好壹堰,具體來說是:給前端返回根節(jié)點對象。在根節(jié)點中包含子節(jié)點骡湖,子節(jié)點中再包含下一級的子節(jié)點贱纠。
handler方法中的具體操作:
@RequestMapping("/menu/get/whole/tree")
public ResultEntity<Menu> getWholeTree() {
// 1.查詢所有的樹形節(jié)點用于組裝
List<Menu> menuList = menuService.getAll();
// 2.將List<Menu>轉(zhuǎn)換為Map<Menu的id,Menu>
Map<Integer,Menu> menuMap = new HashMap<>();
for (Menu menu : menuList) {
Integer id = menu.getId();
menuMap.put(id, menu);
}
// 3.聲明變量用于存儲根節(jié)點對象
Menu rootNode = null;
// 4.遍歷List<Menu>
for (Menu menu : menuList) {
// 5.獲取當(dāng)前Menu對象的pid屬性
Integer pid = menu.getPid();
// 6.判斷pid是否為null
if(pid == null) {
// 7.如果pid為null,說明當(dāng)前節(jié)點是根節(jié)點响蕴,所以賦值
rootNode = menu;
// 8.根節(jié)點沒有父節(jié)點谆焊,所以不必找父節(jié)點組裝,本次for循環(huán)停止執(zhí)行浦夷,繼續(xù)執(zhí)行下一次循環(huán)
continue ;
}
// 9.既然pid不為null辖试,那么我們根據(jù)這個pid查找當(dāng)前節(jié)點的父節(jié)點。
Menu father = menuMap.get(pid);
// 10.組裝:將menu添加到maybeFather的子節(jié)點集合中
father.getChildren().add(menu);
}
return ResultEntity.successWithData(rootNode);
}
service方法:
@Override
public List<Menu> getAll() {
return menuMapper.selectByExample(new MenuExample());
}
2.2 前端操作
2.2.1 創(chuàng)建menu-page.jsp
主體內(nèi)容如下:
<div class="panel panel-default">
<div class="panel-heading">
<i class="glyphicon glyphicon-th-list"></i> 權(quán)限菜單列表
<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跳轉(zhuǎn)到menp-page.jsp
<mvc:view-controller path="/menu/to/page.html" view-name="menu-page"/>
sidebar中的超鏈接也做相應(yīng)調(diào)整
<li style="height: 30px;"><a href="menu/to/page.html"><i class="glyphicon glyphicon-lock"></i> 菜單維護</a></li>
2.2.2 加入zTree環(huán)境
<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 顯示樹形結(jié)構(gòu)
$(function(){
// setting對象中包含zTree的設(shè)置屬性
var setting = {
"view": {
"addDiyDom": showMyIcon
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止點擊節(jié)點后跳轉(zhuǎn)
}
}
};
// 發(fā)送Ajax請求獲取zNodes數(shù)據(jù)
$.ajax({
"url":"menu/get/whole/tree.json",
"type":"post",
"dataType":"json",
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
// 用于顯示樹形結(jié)構(gòu)的節(jié)點數(shù)據(jù)
var zNodes = response.data;
// 執(zhí)行樹形結(jié)構(gòu)的初始操作
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
if(result == "FAILED") {
layer.msg("加載菜單數(shù)據(jù)失斉罐孝!原因是:"+response.message);
}
},
"error":function(response){
layer.msg("加載菜單數(shù)據(jù)失敗肥缔!原因是:"+response.message);
}
});
});
2.2.4 聲明函數(shù)將圖標(biāo)修改為自定義圖標(biāo)
// 由setting.view.addDiyDom屬性引用莲兢,負責(zé)顯示自定義圖標(biāo)
// treeId是<ul id="treeDemo" class="ztree"></ul>的id屬性值
// treeNode對應(yīng)每一個樹形節(jié)點
function showMyIcon(treeId, treeNode) {
// 獲取當(dāng)前節(jié)點的id
var currentNodeId = treeNode.tId;
// 獲取新的class值用于替換
var newClass = treeNode.icon;
// 在當(dāng)前節(jié)點id之后附加“_ico”得到目標(biāo)span的id
var targetSpanId = currentNodeId + "_ico";
// 將目標(biāo)span的舊class移除,添加新class
$("#"+targetSpanId)
.removeClass()
.addClass(newClass)
.css("background","");
}
附:節(jié)點圖標(biāo)修改前后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 節(jié)點結(jié)構(gòu)
<li 節(jié)點>
<span 展開折疊部分圖標(biāo)></span>
<a 節(jié)點內(nèi)容>
<span 節(jié)點的具體圖標(biāo)></span>
<span>節(jié)點文本內(nèi)容</span>
</a>
<ul 子節(jié)點></ul>
</li>
id結(jié)構(gòu):treeDemo_
節(jié)點序號_
節(jié)點具體部件
id="treeDemo_3_ul" id="treeDemo_3_ico"
2.2.6 阻止點擊節(jié)點后跳轉(zhuǎn)
使用了setting.data.key.url屬性辫继。
2.3 封裝函數(shù)實現(xiàn)生成樹形結(jié)構(gòu)操作
function initWholeTree() {
// setting對象中包含zTree的設(shè)置屬性
var setting = {
"view": {
"addDiyDom": showMyIcon
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止點擊節(jié)點后跳轉(zhuǎn)
}
}
};
// 發(fā)送Ajax請求獲取zNodes數(shù)據(jù)
$.ajax({
"url":"menu/get/whole/tree.json",
"type":"post",
"dataType":"json",
"success":function(response){
var result = response.result;
if(result == "SUCCESS") {
// 用于顯示樹形結(jié)構(gòu)的節(jié)點數(shù)據(jù)
var zNodes = response.data;
// 執(zhí)行樹形結(jié)構(gòu)的初始操作
$.fn.zTree.init($("#treeDemo"), setting, zNodes);
}
if(result == "FAILED") {
layer.msg("加載菜單數(shù)據(jù)失斉俗慈!原因是:"+response.message);
}
},
"error":function(response){
layer.msg("加載菜單數(shù)據(jù)失敼每怼!原因是:"+response.message);
}
});
}
3 生成按鈕組
3.1 生成按鈕組規(guī)則
3.1.1 根節(jié)點(level 0)
- 增加子節(jié)點
3.1.2 level為1的有子節(jié)點的節(jié)點
- 增加子節(jié)點
- 修改
3.1.3 level為1的無子節(jié)點的節(jié)點
- 增加子節(jié)點
- 刪除
- 修改
3.1.4 level為2的節(jié)點
- 刪除
- 修改
3.2 設(shè)計按鈕組所在span的id
- 規(guī)則:treeDemo
_
序號_
btnGrp - 舉例:treeDemo_20_btnGrp
3.3 各個按鈕HTML標(biāo)簽
<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="添加子節(jié)點">
<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="編輯節(jié)點">
<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="刪除節(jié)點">
<i class="fa fa-fw fa-times rbg"></i>
</a>
</span>
3.4 聲明函數(shù)生成按鈕組
// 專門生成按鈕組的函數(shù)
function generateBtnGrp(treeNode) {
// 獲取當(dāng)前節(jié)點的id(HTML中l(wèi)i標(biāo)簽的id)
var treeNodeId = treeNode.tId;
// 獲取當(dāng)前節(jié)點在數(shù)據(jù)庫中的id值
// Menu實體類中的屬性闺阱,都可以通過treeNode以“.屬性名”的方式直接訪問
var menuId = treeNode.id;
// 組裝按鈕組所在的span的id
var btnGrpSpanId = treeNodeId + "_btnGrp";
// 生成span標(biāo)簽對應(yīng)的jQuery對象
var $span = $("<span id='"+btnGrpSpanId+"'></span>");
// 獲取當(dāng)前節(jié)點的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='添加子節(jié)點'> <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='編輯節(jié)點'> <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='刪除節(jié)點'> <i class='fa fa-fw fa-times rbg'></i></a>";
// 根據(jù)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)函數(shù)
// 在鼠標(biāo)移入節(jié)點范圍時添加自定義控件
function addHoverDom(treeId, treeNode) {
// 在執(zhí)行添加前炮车,先進行判斷,如果已經(jīng)添加過酣溃,就停止執(zhí)行
// 組裝按鈕組所在的span標(biāo)簽的id
var btnGrpSpanId = treeNode.tId + "_btnGrp";
var btnGrpSpanLength = $("#"+btnGrpSpanId).length;
if(btnGrpSpanLength > 0) {
return ;
}
// 由于按鈕組是放在當(dāng)前節(jié)點中的超鏈接(a標(biāo)簽)后面瘦穆,所以先定位到a標(biāo)簽
// 按id生成規(guī)則組裝a標(biāo)簽的id
var anchorId = treeNode.tId + "_a";
// 調(diào)用已封裝函數(shù)生成按鈕組
var $btnGrpSpan = generateBtnGrp(treeNode);
// 在a標(biāo)簽后面追加按鈕組
$("#"+anchorId).after($btnGrpSpan);
}
3.6 removeHoverDom(treeId, treeNode)函數(shù)
// 在鼠標(biāo)移出節(jié)點范圍時刪除自定義控件
function removeHoverDom(treeId, treeNode) {
// 組裝按鈕組所在的span標(biāo)簽的id
var btnGrpSpanId = treeNode.tId + "_btnGrp";
// 刪除對應(yīng)的元素
$("#"+btnGrpSpanId).remove();
}
3.7 加入add和remove的setting
var setting = {
"view": {
"addDiyDom": showMyIcon,
"addHoverDom": addHoverDom,
"removeHoverDom": removeHoverDom
},
"data": {
"key": {
"url": "notExistsProperty" // 阻止點擊節(jié)點后跳轉(zhuǎn)
}
}
};
4 添加子節(jié)點
4.1 準備模態(tài)框
模態(tài)框的HTML代碼,參考下面文件的代碼:
include-modal-menu-add.jsp
<%@ include file="/WEB-INF/include-modal-menu-add.jsp" %>
4.2 showAddModal()函數(shù)
// 在點擊添加按鈕時執(zhí)行這個函數(shù)赊豌,打開模態(tài)框
function showAddModal(currentBtn) {
// 打開模態(tài)框
$("#menuAddModal").modal("show");
// 將當(dāng)前節(jié)點的id存入全局變量
window.menuId = currentBtn.id;
}
4.3 給保存按鈕綁定單擊響應(yīng)函數(shù)
$("#menuAddBtn").click(function(){
// 收集表單填寫的數(shù)據(jù)
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("請?zhí)顚懖藛雾椕Q扛或!");
return ;
}
if(url == null || url == "") {
layer.msg("請?zhí)顚懖藛雾棇?yīng)的訪問地址!");
return ;
}
// 發(fā)送Ajax請求
$.ajax({
"url":"menu/save.json",
"type":"post",
"dataType":"json",
"data":{
"name":name,
"url":url,
"pid":window.menuId, // 當(dāng)前操作的節(jié)點是新節(jié)點的父節(jié)點
"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 更新節(jié)點
5.1 準備模態(tài)框
include-modal-menu-edit.jsp
<%@ include file="/WEB-INF/include-modal-menu-edit.jsp" %>
5.2 showEditModal()函數(shù)
// 在點擊編輯按鈕時執(zhí)行這個函數(shù)熙兔,打開模態(tài)框
function showEditModal(currentBtn) {
// 打開模態(tài)框
$("#menuEditModal").modal("show");
// 獲取當(dāng)前節(jié)點的id存入全局變量
window.menuId = currentBtn.id;
// 發(fā)送請求查詢Menu對象
$.ajax({
"url":"menu/get/"+window.menuId+".json",
"type":"get",
"dataType":"json",
"success":function(response) {
var result = response.result;
if(result == "SUCCESS") {
// 從響應(yīng)數(shù)據(jù)中獲取Menu對象
var menu = response.data;
// 獲取各個屬性值
var name = menu.name;
var url = menu.url;
var icon = menu.icon;
// 設(shè)置各個對應(yīng)的表單項
$("#menuEditModal [name='name']").val(name);
$("#menuEditModal [name='url']").val(url);
// radio需要讓value值和后端查詢到的icon一致的設(shè)置為被選中
$("#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 給更新按鈕綁定單擊響應(yīng)函數(shù)
$("#menuEditBtn").click(function(){
// 收集表單填寫的數(shù)據(jù)
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("請?zhí)顚懖藛雾椕Q悲伶!");
return ;
}
if(url == null || url == "") {
layer.msg("請?zhí)顚懖藛雾棇?yīng)的訪問地址!");
return ;
}
// 發(fā)送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 執(zhí)行更新的后端代碼
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修改去掉麸锉,不改變節(jié)點的父節(jié)點。