1.菜單管理設計說明
1.1 業務設計說明
菜單管理又稱為資源管理,是 系統資源對外的表現形式。本子產品主要是實作對菜單進行添加、修改、查詢、删除等操作。其表設計語句如下:
CREATE TABLE `sys_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '資源名稱',
`url` varchar(200) DEFAULT NULL COMMENT '資源 URL',
`type` int(11) DEFAULT NULL COMMENT '類型 1:菜單 2:按鈕',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`note` varchar(100) DEFAULT NULL COMMENT '備注',
`parentId` int(11) DEFAULT NULL COMMENT '父菜單 ID,一級菜單為 0',
`permission` varchar(500) DEFAULT NULL COMMENT '授權(如:sys:user:create)',
`createdTime` datetime DEFAULT NULL COMMENT '建立時間',
`modifiedTime` datetime DEFAULT NULL COMMENT '修改時間',
`createdUser` varchar(20) DEFAULT NULL COMMENT '建立使用者',
`modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改使用者',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='資源管理';
菜單表與角色表是多對多的關系,在表設計時,多對多關系通常由中間表(關系表)進行維護,如圖所示:

基于角色菜單表的設計,其角色和菜單對應的關系資料要存儲到關系表中,其具體存儲形式,如圖所示:
菜單與角色的關系表腳本設計如下:
CREATE TABLE `sys_role_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色 ID',
`menu_id` int(11) DEFAULT NULL COMMENT 'ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色與菜單對應關系';
1.2 原型設計說明
基于使用者需求,實作菜單靜态頁面(html/css/js),通過靜态頁面為使用者呈現菜單子產品的基本需求實作。
當在首頁左側菜單欄,點選菜單管理時,在首頁内容呈現區,呈現菜單清單頁面,如圖所示:
當在菜單清單頁面點選添加按鈕時,異步加載菜單編輯頁面,并在清單内容呈現區,呈現菜單編輯頁面,如圖所示:
在菜單編輯頁面選擇上級菜單時,異步加載菜單資訊,并以樹結構的形式呈現上級菜單,如圖所示:
說明:假如客戶對此原型進行了确認,後續則可以基于此原型進行研發
1.3 API設計說明
菜單管理業務背景 API 分層架構及調用關系如圖所示:
說明:分層目的主要将複雜問題簡單化,實作各司其職,各盡所能
2.菜單管理清單頁面呈現
2.1 業務時序分析
菜單管理頁面的加載過程,其時序分析如圖所示:
2.2 服務端實作
2.2.1 Controller實作
▪ 業務描述與設計實作
基于菜單管理的請求業務,在 PageController 中添加 doMenuUI 方法,用于傳回菜單清單頁面
▪ 關鍵代碼設計與實作
第一步:在 PageController 中定義傳回菜單清單的方法。代碼如下:
@RequestMapping("menu/menu_list")
public String doMenuUI() {
return "sys/menu_list";
}
第二步:在 PageController 中基于 rest 風格的 url 方式優化傳回 UI 頁面的方法。找出共性進行提取,例如:
@RequestMapping("{module}/{moduleUI}")
public String doModuleUI(@PathVariable String moduleUI) {
return "sys/"+moduleUI;
}
2.3 用戶端實作
2.3.1 首頁菜單事件處理
▪ 業務描述與設計實作
首先準備菜單清單頁面 (/templates/pages/sys/menu_list.html) , 然後在starter.html 頁面中點選菜單管理時異步加載菜單清單頁面。
▪ 關鍵代碼設計與實作
找到項目中的 starter.html 頁面,頁面加載完成以後,注冊菜單管理項的點選事件,當點選菜單管理時,執行事件處理函數。關鍵代碼如下:
$(function(){
…
doLoadUI("load-menu-id","menu/menu_list")
})
說明:對于 doLoadUI 函數,假如在 starter.html 中已經定義,則無需再次定義
function doLoadUI(id,url){
$("#"+id).click(function(){
$("#mainContentId").load(url);
});
}
其中,load 函數為 jquery 中的 ajax 異步請求函數
2.3.2 業務描述與設計實作
本頁面呈現菜單資訊時要以樹結構形式進行呈現。此樹結構會借助 jquery 中的treeGrid 插件進行實作,是以在菜單清單頁面需要引入 treeGrid 相關 JS。但是,具體的 treeGrid 怎麼用可自行在網上進行查詢(已比較成熟)
▪ 關鍵代碼設計與實作:
關鍵JS引入(menu_list.html,代碼如下:
<script type="text/javascript"src="bower_components/treegrid/jquery.treegrid.extension.js"></script>
<script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.min.js"></script>
<script type="text/javascript" src="bower_components/treegrid/tree.table.js"></script>
3.菜單管理清單資料呈現
3.1 資料架構分析
菜單清單頁面加載完成,啟動菜單資料異步加載操作,本次菜單清單頁面要呈現菜單以及上級菜單資訊,其資料查詢時,資料的封裝及傳遞過程,如圖所示:
說明:本子產品将從資料庫查詢到的菜單資料封裝到 map 對象,一行記錄一個 map 對象,其中 key 為表中的字段(列)名,值為字段(列)對應的值。
資料加載過程其時序分析,如圖所示:
3.2 服務端關鍵業務及代碼實作
3.2.1 Dao接口實作
▪ 業務描述及設計實作
通過資料層對象,基于業務層參數,查詢菜單以及上級菜單資訊(要查詢上級菜單名)。
▪ 關鍵代碼分析及實作
第一步:定義資料層接口對象,通過此對象實作資料庫中菜單資料的通路操作。關鍵代碼如下:
@Mapper
public interface SysMenuDao {
}
第二步:在 SysMenuDao 接口中添加 findObjects 方法,基于此方法實作菜單資料的查詢操作。代碼如下:
說明:一行記錄映射為一個 map 對象,多行存儲到 list。
思考:這裡為什麼使用 map 存儲資料,有什麼優勢劣勢?
3.2.2 Mapper檔案實作
▪ 業務描述及設計實作
基于 Dao 接口建立映射檔案,在此檔案中通過相關元素(例如 select)描述要執行的資料操作。
▪ 關鍵代碼設計及實作
第一步:在映射檔案的設計目錄中添加 SysMenuMapper.xml 映射檔案,代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.pj.sys.dao.SysMenuDao">
</mapper>
第二步:在映射檔案中添加 id 為 findObjects 的元素,實作菜單記錄查詢。我們要查詢所有菜單以及菜單對應的上級菜單名稱。關鍵代碼如下:
<select id="findObjects" resultType="map">
<!-- 方案 1
select c.*,p.name parentName
from sys_menus c left join sys_menus p
on c.parentId=p.id
-->
<!-- 方案 2 -->
select c.*,(
select p.name
from sys_menus p
where c.parentId=p.id
) parentName
from sys_menus c
</select>
說明:自關聯查詢分析,如圖所示:
3.2.3 Service接口及實作類
▪ 業務描述與設計實作
在菜單查詢中,業務層對象主要是借助資料層對象完成菜單資料的查詢。後續還可以基于 AOP 對資料進行緩存,記錄通路日志等。
▪ 關鍵代碼設計及實作
第一步:定義菜單業務接口及方法,暴露外界對菜單業務資料的通路,其代碼參考如下:
package com.cy.pj.sys.service;
public interface SysMenuService {
List<Map<String,Object>> findObjects();
}
第二步:定義菜單業務接口實作類,并添加菜單業務資料對應的查詢操作實作,其代碼參考如下:
package com.cy.pj.sys.service.impl;
@Service
public class SysMenuServiceImpl implements SysMenuService{
@Autowired
private SysMenuDao sysMenuDao;
@Override
public List<Map<String, Object>> findObjects() {
List<Map<String,Object>> list=sysMenuDao.findObjects();
if(list==null||list.size()==0)
throw new ServiceException("沒有對應的菜單資訊");
return list;
}
}
3.2.4 Controller類實作
▪ 業務描述與設計實作
控制層對象主要負責請求和響應資料的處理,例如,本子產品通過業務層對象執行業務邏輯,再通過VO對象封裝響應結果(主要對業務層資料添加狀态資訊),最後将響應結果轉換為 JSON 格式的字元串響應到用戶端。
▪ 關鍵代碼設計與實作
定義 Controller 類,并将此類對象使用 Spring 架構中的@Controller 注解進行辨別,表示此類對象要交給 Spring 管理。然後基于@RequestMapping 注解為此類定義根路徑映射。代碼參考如下:
package com.cy.pj.sys.controller;
@RequestMapping("/menu/")
@RestController
public class SysMenuController {
}
說明:這裡的@RestController 注解等效于在類上同時添加了@Controller 和@ResponseBody 注解
在 Controller 類中添加菜單查詢處理方法,代碼參考如下:
@RequestMapping("doFindObjects")
public JsonResult doFindObjects() {
return new JsonResult(sysMenuService.findObjects());
}
3.3 用戶端關鍵業務及代碼實作
3.3.1 菜單清單資訊呈現
▪ 業務描述與設計實作
菜單頁面加載完成以後,向服務端發起異步請求加載菜單資訊,當菜單資訊加載完成需要将菜單資訊呈現到清單頁面上。
▪ 關鍵代碼設計與實作
第一步:在菜單清單頁面引入 treeGrid 插件相關的 JS。
<script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.extension.js"></script>
<script type="text/javascript" src="bower_components/treegrid/jquery.treegrid.min.js"></script>
<script type="text/javascript" src="bower_components/treegrid/tree.table.js"></script>
第二步:在菜單清單頁面,定義菜單清單配置資訊,關鍵代碼如下:
var columns = [
{
field : 'selectItem',
radio : true
},
{
title : '菜單 ID',
field : 'id',
align : 'center',
valign : 'middle',
width : '80px'
},
{
title : '菜單名稱',
field : 'name',
align : 'center',
valign : 'middle',
width : '130px'
},
{
title : '上級菜單',
field : 'parentName',
align : 'center',
valign : 'middle',
sortable : true,
width : '100px'
},
{
title : '類型',
field : 'type',
align : 'center',
valign : 'middle',
width : '70px',
formatter : function(item, index) {
if (item.type == 1) {
return '<span class="label label-success">菜單</span>';
}
if (item.type == 2) {
return '<span class="label label-warning">按鈕</span>';
}
}
},
{
title : '排序号',
field : 'sort',
align : 'center',
valign : 'middle',
sortable : true,
width : '70px'
},
{
title : '菜單 URL',
field : 'url',
align : 'center',
valign : 'middle',
width : '160px'
},
{
title : '授權辨別',//要顯示的标題名稱
field : 'permission',//json 串中的 key
align : 'center',//水準居中
valign : 'middle',//垂直居中
sortable : false //是否排序
} ];//格式來自官方 demos -->treeGrid(jquery 擴充的一個網格樹插件)
第三步:定義異步請求處理函數,代碼參考如下:
function doGetObjects(){//treeGrid
//1.建構 table 對象(bootstrap 架構中 treeGrid 插件提供)
var treeTable=new TreeTable(
"menuTable",//tableId
"menu/doFindObjects",//url
columns);
//設定從哪一列開始展開(預設是第一列)
//treeTable.setExpandColumn(2);
//2.初始化 table 對象(底層發送 ajax 請求擷取資料)
treeTable.init();//getJSON,get(),...
}
第四步:頁面加載完成,調用菜單查詢對應的異步請求處理函數,關鍵代碼如下:
$(function(){
doGetObjects();
})
4.菜單管理删除操作實作
4.1 業務時序分析
基于使用者在清單頁面上選擇的的菜單記錄 ID,執行删除操作,本次删除業務實作中,首先要基于 id 判斷目前菜單是否有子菜單,假如有子菜單則不允許删除,沒有則先删除菜單角色關系資料,然後再删除菜單自身資訊。其時序分析如圖所示:
4.2 服務端關鍵業務及代碼實作
4.2.1 Dao接口實作
▪ 業務描述及設計實作
資料層基于業務層送出的菜單記錄 id,删除菜單角色關系以及菜單資料,菜單自身記錄資訊。
▪ 關鍵代碼設計及實作
第一步:建立 SysRoleMenuDao 并定義基于菜單 id 删除關系資料的方法,關鍵代碼如下:
@Mapper
public interface SysRoleMenuDao {
int deleteObjectsByMenuId(Integer menuId);
}
第二步:在 SysMenuDao 中添加基于菜單 id 查詢子菜單記錄的方法。代碼參考如下:
第三步:在 SysMenuDao 中添加基于菜單 id 删除菜單記錄的方法。代碼參考如下:
4.2.2 Mapper檔案實作
▪ 業務描述及設計實作
在 SysRoleMenuDao,SysMenuDao 接口對應的映射檔案中添加用于執行删除業務的delete 元素,然後在元素内部定義具體的 SQL 實作。
▪ 關鍵代碼設計與實作
第一步:建立 SysRoleMenuMapper.xml 檔案并添加基于菜單 id 删除關系資料的元素,關鍵代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.pj.sys.dao.SysRoleMenuDao">
<delete id="deleteObjectsByMenuId"
parameterType="int">
delete from sys_role_menus
where menu_id=#{menuId}
</delete>
</mapper>
第二步:在 SysMenuMapper.xml 檔案中添加基于 id 統計子菜單數量的元素,關鍵代碼如下:
<select id="getChildCount"
parameterType="int"
resultType="int">
select count(*)
from sys_menus
where parentId=#{id}
</select>
第三步:在 SysMenuMapper.xml 檔案添加 delete 元素,基于帶單 id 删除菜單自身記錄資訊,關鍵代碼如下:
<delete id="deleteObject">
delete from sys_menus
where id =#{id}
</delete>
4.2.3 Service接口及實作類
▪ 業務描述與設計實作
在菜單業務層定義用于執行菜單删除業務的方法,首先通過方法參數接收控制層傳遞的菜單 id,并對參數 id 進行校驗。然後基于菜單 id 統計子菜單個數,假如有子菜單則抛出異常,提示不允許删除。假如沒有子菜單,則先删除角色菜單關系資料。最後删除菜單自身記錄資訊後并傳回業務執行結果。
▪ 關鍵代碼設計與實作
第一步:在 SysMenuService 接口中,添加基于 id 進行菜單删除的方法。關鍵代碼如下:
第二步:在 SysMenuServiceImpl 實作類中注入 SysRoleMenuDao 相關對象。關鍵代碼如下:
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
第三步:在 SysMenuServiceImpl 實作類中添加删除業務的具體實作。關鍵代碼如下:
@Override
public int deleteObject(Integer id) {
//1.驗證資料的合法性
if(id==null||id<=0)
throw new IllegalArgumentException("請先選擇");
//2.基于 id 進行子元素查詢
int count=sysMenuDao.getChildCount(id);
if(count>0)
throw new ServiceException("請先删除子菜單");
//3.删除角色,菜單關系資料
sysRoleMenuDao.deleteObjectsByMenuId(id);
//4.删除菜單元素
int rows=sysMenuDao.deleteObject(id);
if(rows==0)
throw new ServiceException("此菜單可能已經不存在");
//5.傳回結果
return rows;
}
4.2.4 Controller類實作
▪ 業務描述與設計實作
在菜單控制層對象中,添加用于處理菜單删除請求的方法。首先在此方法中通過形參接收用戶端送出的資料,然後調用業務層對象執行删除操作,最後封裝執行結果,并在運作時将響應對象轉換為 JSON 格式的字元串,響應到用戶端。
▪ 關鍵代碼設計與實作
第一步:在 SysMenuController 中添加用于執行删除業務的方法。代碼如下:
@RequestMapping("doDeleteObject")
public JsonResult doDeleteObject(Integer id){
sysMenuService.deleteObject(id);
return new JsonResult("delete ok");
}
第二步:啟動 tomcat 進行通路測試,打開浏覽器輸入如下網址:
http://localhost/menu/doDeleteObject?id=10
4.3 用戶端關鍵業務及代碼實作
4.3.1 菜單清單頁面事件處理
▪ 業務描述及設計實作
使用者在頁面上首先選擇要删除的元素,然後點選删除按鈕,将使用者選擇的記錄 id 異步送出到服務端,最後在服務端執行菜單的删除動作。
▪ 關鍵代碼設計與實作
第一步:頁面加載完成以後,在删除按鈕上進行點選事件注冊。關鍵代碼如下:
...
$(".input-group-btn")
.on("click",".btn-delete",doDeleteObject)
...
第二步:定義删除操作對應的事件處理函數。關鍵代碼如下:
function doDeleteObject(){
//1.擷取選中的記錄 id
var id=doGetCheckedId();
if(!id){
alert("請先選擇");
return;
}
//2.給出提示是否确認删除
if(!confirm("确認删除嗎"))return;
//3.異步送出請求删除資料
var url="menu/doDeleteObject";
var params={"id":id};
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
$("tbody input[type='radio']:checked")
.parents("tr").remove();
}else{
alert(result.message);
}
});
}
第三步:定義擷取使用者選中的記錄 id 的函數。關鍵代碼如下:
function doGetCheckedId(){
//1.擷取選中的記錄
var selections=$("#menuTable")
//bootstrapTreeTable 是 treeGrid 插件内部定義的 jquery 擴充函數
//getSelections 為擴充函數内部要調用的一個方法
.bootstrapTreeTable("getSelections");
//2.對記錄進行判定
if(selections.length==1)
return selections[0].id;
}
5.菜單添加頁面呈現
5.1 業務時序分析
添加頁面加載時序分析,如圖所示:
5.2 準備菜單編輯頁面
首先準備菜單清單頁面(/templates/pages/sys/menu_edit.html),然後在menu_list.html 頁面中點選菜單添加時異步加載菜單編輯頁面
5.3 菜單編輯頁面呈現
▪ 業務描述與設計實作
菜單清單頁面點選添加按鈕時,異步加載菜單編輯頁面。
▪ 關鍵代碼設計與實作
第一步:菜單清單頁面上,對添加按鈕進行事件注冊,關鍵代碼如下:
$(document).ready(function(){
...
$(".input-group-btn")
.on("click",".btn-add",doLoadEditUI);
});
第二步:定義添加按鈕事件處理函數,關鍵代碼如下:
function doLoadEditUI(){
var title;
if($(this).hasClass("btn-add")){
title="添加菜單"
}
var url="menu/menu_edit";
$("#mainContentId").load(url,function(){
$(".box-title").html(title);
})
}
6.菜單編輯頁面上級菜單呈現
6.1 業務時序分析
在菜單編輯頁面上,點選上級菜單時,其資料加載時序分析,如圖所示:
6.2 服務端關鍵業務及代碼實作
6.2.1 Node對象
▪ 業務描述與設計實作
定義值對象封裝查詢到的上級菜單 id,name,parentId 資訊。
▪ 關鍵代碼設計與實作
import lombok.Data;
@Data
public class Node implements Serializable{
private static final long serialVersionUID = -6577397050669133046L;
private Integer id;
private String name;
private Integer parentId;
}
6.2.2 Dao 接口實作
▪ 業務描述與設計實作
基于請求擷取資料庫對應的菜單表中的所有菜單 id,name,parentId,一行記錄封裝為一個 Node 對象,多個 node 對象存儲到 List 集合
▪ 關鍵代碼設計與實作
在 SysMenuDao 接口中添加,用于查詢上級菜單相關資訊。關鍵代碼如下:
6.2.3 Mapper映射檔案
▪ 業務描述與設計實作
基于 SysMenuMapper 中方法的定義,編寫用于菜單查詢的 SQL 元素。
▪ 關鍵代碼設計與實作
在 SysMenuMapper.xml 中添加 findZtreeMenuNodes 元素,用于查詢上級菜單資訊。
關鍵代碼如下:
<select id="findZtreeMenuNodes"
resultType="com.cy.pj.common.vo.Node">
select id,name,parentId
from sys_menus
</select>
6.2.4 Service 接口及實作類
▪ 業務描述與設計實作
基于使用者請求,通過資料層對象擷取上級菜單相關資訊。
▪ 關鍵代碼實作
第一步:在 SysMenuService 接口中,添加查詢菜單資訊的方法。關鍵代碼如下:
第二步:在 SysMenuServiceImpl 類中添加,查詢菜單資訊方法的實作。關鍵代碼如下:
@Override
public List<Node> findZtreeMenuNodes() {
return sysMenuDao.findZtreeMenuNodes();
}
6.2.5 Controller類實作
▪ 業務描述與設計實作
基于用戶端請求,通路業務層對象方法,擷取菜單節點對象,并封裝傳回。
▪ 關鍵代碼設計與實作
@RequestMapping("doFindZtreeMenuNodes")
public JsonResult doFindZtreeMenuNodes(){
return new JsonResult(sysMenuService.findZtreeMenuNodes());
}
6.3 用戶端關鍵業務及代碼實作
6.3.1 ZTree結構定義
▪ 業務描述與設計實作
本子產品以開源 JS 元件方式實作 ZTree 結構資訊的呈現。
▪ 關鍵代碼設計與實作
在 menu_edit.html 頁面中定義用于呈現樹結構的 DIV 元件(假如已有則無需定義)
<div class="layui-layer layui-layer-page layui-layer-molv layer-anim" id="menuLayer" type="page" times="2" showtime="0" contype="object" style="z-index:59891016; width: 300px; height: 450px; top:
100px; left: 500px; display:none">
<div class="layui-layer-title" style="cursor: move;">選擇菜單</div>
<div class="layui-layer-content" style="height: 358px;">
<div style="padding: 10px;" class="layui-layer-wrap">
<ul id="menuTree" class="ztree"></ul> <!--動态加載樹 -->
</div>
</div>
<span class="layui-layer-setwin"> <a class="layui-layer-icolayui-layer-close layui-layer-close1 btn-cancel" ></a></span>
<div class="layui-layer-btn layui-layer-btn-">
<a class="layui-layer-btn0 btn-confirm">确定</a>
<a class="layui-layer-btn1 btn-cancel">取消</a>
</div>
</div>
6.3.2 ZTree 資料呈現
▪ 業務描述與設計實作
引入 zTree 需要的 JS,并,并基于 JS 中的定義的 API 初始化 zTree 中的菜單資訊。
▪ 關鍵代碼設計與實作
第一步:引入 js 檔案
<script type="text/javascript"
src="bower_components/ztree/jquery.ztree.all.min.js"></script>
<script type="text/javascript"
src="bower_components/layer/layer.js">
</script>
第二步:在 menu_edit.html 中定義 zTree 配置資訊(初始化 zTree 時使用)
var zTree;
var setting = {
data : {
simpleData : {
enable : true,
idKey : "id", //節點資料中儲存唯一辨別的屬性名稱
pIdKey : "parentId", //節點資料中儲存其父節點唯一辨別的屬性名稱
rootPId : null //根節點 id
}
}
}
第三步:定義異步加載 zTree 資訊的函數,關鍵代碼如下:
function doLoadZtreeNodes(){
var url="menu/doFindZtreeMenuNodes";
//異步加載資料,并初始化資料
$.getJSON(url,function(result){
if(result.state==1){
//使用 init 函數需要先引入 ztree 對應的 js 檔案
zTree=$.fn.zTree.init(
$("#menuTree"),
setting,
result.data);
$("#menuLayer").css("display","block");
}else{
alert(result.message);
}
})
}
第四步:定義 zTree 中取消按鈕事件處理函數,點選取消隐藏 zTree。關鍵代碼如下:
function doHideTree(){
$("#menuLayer").css("display","none");
}
第五步:定義 zTree 中确定按鈕對應的事件處理處理函數。關鍵代碼如下:
function doSetSelectNode(){
//1.擷取選中的節點對象
var nodes=zTree.getSelectedNodes();
if(nodes.length==1){
var node=nodes[0];
console.log(node);
//2.将對象中内容,填充到表單
$("#parentId").data("parentId",node.id);
$("#parentId").val(node.name);
}
//3.隐藏樹對象
doHideTree();
}
第六步:定義頁面加載完成以後的事件處理函數:
$(document).ready(function(){
$("#mainContentId")
.on("click",".load-sys-menu",doLoadZtreeNodes)
$("#menuLayer")
.on("click",".btn-confirm",doSetSelectNode)
.on("click",".btn-cancel",doHideTree)
});
7.菜單資料添加實作
7.1 資料基本架構分析
使用者在菜單編輯頁面輸入資料,然後異步送出到服務端,其簡易資料傳遞基本架構,如圖所示:
使用者在菜單添加頁面中填寫好菜單資料,然後點選儲存按鈕,将使用者填寫的資料添加到資料庫。其時序分析,如圖所示:
7.2 服務端關鍵業務及代碼實作
7.2.1 POJO類定義
▪ 業務描述與設計實作
定義持久化對象,封裝用戶端請求資料,并将資料傳遞到資料層進行持久化。
▪ 關鍵代碼設計與實作
菜單持久層對象類型定義,關鍵代碼如下:
import lombok.Data;
@Data
public class SysMenu implements Serializable{
private static final long serialVersionUID = -8805983256624854549L;
private Integer id;
/**菜單名稱*/
private String name;
/**菜單 url: log/doFindPageObjects*/
private String url;
/**菜單類型(兩種:按鈕,普通菜單)*/
private Integer type=1;
/**排序(序号)*/
private Integer sort;
/**備注*/
private String note;
/**上級菜單 id*/
private Integer parentId;
/**菜單對應的權限辨別(sys:log:delete)*/
private String permission;
/**建立使用者*/
private String createdUser;
/**修改使用者*/
private String modifiedUser;
private Date createdTime;
private Date modifiedTime;
}
7.2.2 DAO接口定義
▪ 業務描述與設計實作
負責将使用者送出的菜單資料,持久化到資料庫。
▪ 關鍵代碼設計與實作
在 SysMenuDao 接口中定義資料持久化方法:
7.2.3 Mapper映射檔案定義
▪ 業務描述與設計實作
基于 SysMenuDao 中方法的定義,編寫用于實作菜單添加的 SQL 元素。
▪ 關鍵代碼設計與實作
在 SysMenuMapper.xml 中添加 insertObject 元素,用于寫入菜單資訊。關鍵代碼如下:
<insert id="insertObject"
parameterType="com.cy.pj.sys.entity.SysMenu">
insert into sys_menus
(name,url,type,sort,note,parentId,permission,
createdTime,modifiedTime,createdUser,modifiedUser)
values
(#{name},#{url},#{type},#{sort},#{note},#{parentId},
#{permission},now(),now(),#{createdUser},#{modifiedUser})
</insert>
7.2.4 Service 接口定義及實作
▪ 業務描述與設計實作
基于控制層請求,調用資料層對象将菜單資訊寫入到資料庫中。
▪ 關鍵代碼設計與實作
第一步:在 SysMenuService 接口中,添加用于儲存菜單對象的方法。關鍵代碼如下:
第二步:在 SysMenuServiceImpl 類中,實作菜單儲存操作。關鍵代碼如下:
@Override
public int saveObject(SysMenu entity) {
//1.合法驗證
if(entity==null)
throw new IllegalArgumentException("儲存對象不能為空");
if(StringUtils.isEmpty(entity.getName()))
throw new IllegalArgumentException("菜單名不能為空");
//2.儲存資料
int rows=sysMenuDao.insertObject(entity);
//3.傳回資料
return rows;
}
7.2.5 Controller 類定義
▪ 業務描述與設計實作
接收用戶端送出的菜單資料,并對其進行封裝,然後調用業務層對象進行業務處理,最後将業務層處理結果響應到用戶端。
▪ 關鍵代碼設計與實作
定義 Controller 方法,借助此方法處理儲存菜單資料請求和響應邏輯。關鍵代碼如下:
@RequestMapping("doSaveObject")
public JsonResult doSaveObject(SysMenu entity){
sysMenuService.saveObject(entity);
return new JsonResult ("save ok");
}
7.3 用戶端關鍵業務及代碼實作
7.3.1 頁面 cancel 按鈕事件處理
▪ 業務描述與設計實作
點選頁面 cancel 按鈕時,加載菜單那清單頁面。
▪ 關鍵代碼設計與實作
第一步:事件注冊(頁面加載完成以後)
$(".box-footer")
.on("click",".btn-cancel",doCancel)
第二步:事件處理函數定義
function doCancel(){
var url="menu/menu_list";
$("#mainContentId").load(url);
}
7.3.2 頁面 Save 按鈕事件處理
▪ 業務描述與設計實作
點選頁面 save 按鈕時,将頁面上輸入的菜單資訊異步送出到服務端。
▪ 關鍵代碼設計與實作
第一步:事件注冊(頁面加載完成以後)
$(".box-footer")
.on("click",".btn-save",doSaveOrUpdate)
第二步:Save 按鈕事件處理函數定義。關鍵代碼如下:
function doSaveOrUpdate(){
//1.擷取表單資料
var params=doGetEditFormData();
//2.定義 url
var url="menu/doSaveObject";
//3.異步送出資料
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
doCancel();
}else{
alert(result.message);
}
});
}
第三步:表單資料擷取及封裝函數定義。關鍵代碼如下:
function doGetEditFormData(){
var params={
type:$("form input[name='typeId']:checked").val(),
name:$("#nameId").val(),
url:$("#urlId").val(),
sort:$("#sortId").val(),
permission:$("#permissionId").val(),
parentId:$("#parentId").data("parentId")
}
return params;
}
8.菜單修改頁面資料呈現
8.1 業務時序分析
當在菜單清單頁面中選中某條記錄,然後點選修改按鈕時,其業務時序分析如圖所示:
8.2 用戶端關鍵業務及代碼實作
8.2.1 清單頁面修改按鈕事件處理
▪ 業務描述與設計實作
點選頁面修改按鈕時,擷取選中菜單記錄,并異步加載編輯頁面。
▪ 關鍵代碼設計與實作
第一步:清單頁面修改按鈕事件注冊,關鍵代碼如下:
$(".input-group-btn")
.on("click",".btn-update",doLoadEditUI);
第二步:修改按鈕事件處理函數定義或修改,關鍵代碼如下:
function doLoadEditUI(){
var title;
if($(this).hasClass("btn-add")){
title="添加菜單"
}else if($(this).hasClass("btn-update")){
title="修改菜單"
//擷取選中的記錄資料
var rowData=doGetCheckedItem();
if(!rowData){
alert("請選擇一個");
return;
}
$("#mainContentId").data("rowData",rowData);
}
var url="menu/menu_edit";
$("#mainContentId").load(url,function(){
$(".box-title").html(title);
})
}
第三步:擷取使用者選中記錄的函數定義。關鍵代碼如下:
function doGetCheckedItem(){
var tr=$("tbody input[type='radio']:checked")
.parents("tr");
return tr.data("rowData");
}
8.2.2 編輯頁面菜單資料呈現
▪ 業務描述與設計實作
頁面加載完成,在頁面指定位置呈現要修改的資料。
▪ 關鍵代碼設計與實作
第一步:頁面加載完成以後,擷取頁面div中綁定的資料。關鍵代碼如下:
$(function(){
…
//假如是修改
var data=$("#mainContentId").data("rowData");
if(data)doInitEditFormData(data);
});
第二步:定義編輯頁面資料初始化方法。關鍵代碼如下:
function doInitEditFormData(data){
/* $("input[type='radio']").each(function(){
if($(this).val()==data.type){
$(this).prop("checked",true);
}
}) */
$(".typeRadio
input[value='"+data.type+"']").prop("checked",true);
$("#nameId").val(data.name);
$("#sortId").val(data.sort);
$("#urlId").val(data.url);
$("#permissionId").val(data.permission);
$("#parentId").val(data.parentName);
$("#parentId").data("parentId",data.parentId);
}
9.菜單資料更新實作
9.1 業務時序分析
當點選編輯頁面更新按鈕時,其時序分析如圖所示:
9.2 服務端關鍵業務及代碼實作
9.2.1 DAO接口實作
▪ 業務描述與設計實作
負責将使用者編輯頁面送出到服務端的菜單資料,更新到資料庫進行持久性存儲。
▪ 關鍵代碼設計與實作
在 SysMenuDao 接口中添加資料更新方法,關鍵代碼如下:
9.2.2 Mapper 映射檔案定義
▪ 業務描述與設計實作
基于 SysMenuDao 中 updateObject 方法的定義,編寫用于實作菜單更新的SQL元素。
▪ 關鍵代碼設計與實作
在 SysMenuMapper.xml 中添加 updateObject 元素,用于更新菜單資訊。關鍵代碼如下:
<update id="updateObject"
parameterType="com.cy.pj.sys.entity.SysMenu">
update sys_menus
set
name=#{name},
type=#{type},
sort=#{sort},
url=#{url},
parentId=#{parentId},
permission=#{permission},
modifiedUser=#{modifiedUser},
modifiedTime=now()
where id=#{id}
</update>
9.2.3 Service 接口及實作
▪ 業務描述與設計實作
基于控制層請求,對資料進行校驗并調用資料層對象将菜單資訊更新到資料庫中。
▪ 關鍵代碼設計與實作
第一步:在 SysMenuService 接口中,添加用于更新菜單對象的方法。關鍵代碼如下:
第二步:在 SysMenuServiceImpl 類中,實作菜單儲存操作。關鍵代碼如下:
@Override
public int updateObject(SysMenu entity) {
//1.合法驗證
if(entity==null)
throw new ServiceException("儲存對象不能為空");
if(StringUtils.isEmpty(entity.getName()))
throw new ServiceException("菜單名不能為空");
//2.更新資料
int rows=sysMenuDao.updateObject(entity);
if(rows==0)
throw new ServiceException("記錄可能已經不存在");
//3.傳回資料
return rows;
}
9.2.4 Controller 類定義
▪ 業務描述與設計實作
接收用戶端送出的菜單資料,并對其進行封裝,然後調用業務層對象進行業務處理,最後将業務層處理結果響應到用戶端。
▪ 關鍵代碼設計與實作
定義 Controller 方法,借助此方法處理儲存菜單資料請求和響應邏輯。關鍵代碼如下:
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysMenu entity){
sysMenuService.updateObject(entity);
return new JsonResult("update ok");
}
9.3 用戶端關鍵業務及代碼實作
9.3.1 編輯頁面更新按鈕事件處理
▪ 業務描述與設計實作
點選頁面 save 按鈕時,将頁面上輸入的菜單編輯資訊送出到服務端。
▪ 關鍵代碼設計與實作
編輯 Save 按鈕對應的事件處理函數。關鍵代碼如下:
function doSaveOrUpdate(){
//1.擷取表單資料
var params=doGetEditFormData();
var rowData=$("#mainContentId").data("rowData");
//2.定義 url
var insertUrl="menu/doSaveObject";
var updateUrl="menu/doUpdateObject";
var url=rowData?updateUrl:insertUrl;
if(rowData)params.id=rowData.id;
//3.異步送出資料
$.post(url,params,function(result){
if(result.state==1){
alert(result.message);
doCancel();
}else{
alert(result.message);
}
});
}
10.總結
10.1 重難點分析
▪ 菜單管理在整個系統中的定位(資源管理)。
▪ 菜單資料的自關聯查詢實作(查詢目前菜單以及這個菜單的上級菜單)。
▪ 菜單管理中資料的封裝過程(請求資料,響應資料)。
▪ 菜單資料在用戶端的呈現。(treeGrid,zTree)
10.2 FAQ 分析
▪ 菜單表是如何設計的,都有哪些字段?
▪ 菜單清單資料在用戶端是如何展示的?(TreeGrid)
▪ 菜單删除業務是如何處理的?
▪ 菜單編輯頁面中上級菜單資料的呈現方式?(zTree)
▪ 常用表連接配接方式,如圖所示:
10.3 BUG 分析
▪ 無效參數異常(IllegalArgumentException),,如圖所示:
問題分析:檢查目前執行的業務,其結果映射配置,是否将 resultType 寫成了resultMap。
▪ 菜單編輯頁面,上級菜單樹結構呈現,如圖所示:
問題分析:檢查查詢結果中是否有 parentId,或映射對象 Node 中 parentId 是否寫錯
▪ 屬性值注入失敗,如圖所示:
問題分析:檢查 Spring 容器中是否有 SysMenuService 接口的實作類對象,因為在SysMenuController 中需要一個這樣的對象