天天看點

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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='資源管理';
           

菜單表與角色表是多對多的關系,在表設計時,多對多關系通常由中間表(關系表)進行維護,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

基于角色菜單表的設計,其角色和菜單對應的關系資料要存儲到關系表中,其具體存儲形式,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

菜單與角色的關系表腳本設計如下:

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.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

當在菜單清單頁面點選添加按鈕時,異步加載菜單編輯頁面,并在清單内容呈現區,呈現菜單編輯頁面,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

在菜單編輯頁面選擇上級菜單時,異步加載菜單資訊,并以樹結構的形式呈現上級菜單,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

說明:假如客戶對此原型進行了确認,後續則可以基于此原型進行研發

1.3 API設計說明

菜單管理業務背景 API 分層架構及調用關系如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

說明:分層目的主要将複雜問題簡單化,實作各司其職,各盡所能

2.菜單管理清單頁面呈現

2.1 業務時序分析

菜單管理頁面的加載過程,其時序分析如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 資料架構分析

菜單清單頁面加載完成,啟動菜單資料異步加載操作,本次菜單清單頁面要呈現菜單以及上級菜單資訊,其資料查詢時,資料的封裝及傳遞過程,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

說明:本子產品将從資料庫查詢到的菜單資料封裝到 map 對象,一行記錄一個 map 對象,其中 key 為表中的字段(列)名,值為字段(列)對應的值。

資料加載過程其時序分析,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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>
           

說明:自關聯查詢分析,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 判斷目前菜單是否有子菜單,假如有子菜單則不允許删除,沒有則先删除菜單角色關系資料,然後再删除菜單自身資訊。其時序分析如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 業務時序分析

添加頁面加載時序分析,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 業務時序分析

在菜單編輯頁面上,點選上級菜單時,其資料加載時序分析,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 資料基本架構分析

使用者在菜單編輯頁面輸入資料,然後異步送出到服務端,其簡易資料傳遞基本架構,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

使用者在菜單添加頁面中填寫好菜單資料,然後點選儲存按鈕,将使用者填寫的資料添加到資料庫。其時序分析,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 業務時序分析

當在菜單清單頁面中選中某條記錄,然後點選修改按鈕時,其業務時序分析如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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 業務時序分析

當點選編輯頁面更新按鈕時,其時序分析如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

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)

▪ 常用表連接配接方式,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

10.3 BUG 分析

▪ 無效參數異常(IllegalArgumentException),,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

問題分析:檢查目前執行的業務,其結果映射配置,是否将 resultType 寫成了resultMap。

▪ 菜單編輯頁面,上級菜單樹結構呈現,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

問題分析:檢查查詢結果中是否有 parentId,或映射對象 Node 中 parentId 是否寫錯

▪ 屬性值注入失敗,如圖所示:

動吧旅遊生态系統--菜單1.菜單管理設計說明2.菜單管理清單頁面呈現3.菜單管理清單資料呈現4.菜單管理删除操作實作5.菜單添加頁面呈現6.菜單編輯頁面上級菜單呈現7.菜單資料添加實作8.菜單修改頁面資料呈現9.菜單資料更新實作10.總結

問題分析:檢查 Spring 容器中是否有 SysMenuService 接口的實作類對象,因為在SysMenuController 中需要一個這樣的對象