第三阶段实战(三)——菜单管理功能设计与实现
1 菜单管理设计说明
1.1 业务设计说明
菜单管理又称为资源管理,是系统资源对外的表现形式。本模块主要是实现对菜
单进行添加、修改、查询、删除等操作。其表设计语句如下:
DROP TABLE IF EXISTS `sys_menus`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
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 '授权(如: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=149 DEFAULT CHARSET=utf8 COMMENT='资源管理';
菜单表与角色表是多对多的关系,在表设计时,多对多关系通常由中间表(关系表)进
行维护,如图:
基于角色菜单表的设计,其角色和菜单对应的关系数据要存储到关系表中,其具体存
储形式,如图
菜单与角色的关系表脚本设计如下:
DROP TABLE IF EXISTS `sys_role_menus`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
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=1400 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';
/*!40101 SET character_set_client = @saved_cs_client */;
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="bowe r_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 方法,基于此方法实现菜单数据的
查询操作。代码如下:
List<Map<String,Object>> 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 parentNamefrom sys_menus c left join sys_menus pon c.parentId=p.id--><!--方案2:通过嵌套实现-->select c.*,(select namefrom sys_menus pwhere c.parentId=p.id) parentNamefrom 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 注解为此类定义根路径映射。在 Controller 类中添加菜单查询处理方法。 代码参考如下:
package com.cy.pj.sys.controller;import com.cy.pj.common.pojo.JsonResult;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.service.SysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/menu/")
public class SysMenuController {@Autowiredprivate SysMenuService sysMenuService;@RequestMapping("doFindObjects")public JsonResult doFindObjects(){List<Map<String,Object>> list=sysMenuService.findObjects();return new JsonResult(list);}
}
说明:这里的@RestController 注解等效于在类上同时添加了@Controller 和 @ResponseBody 注解.
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串中的keyalign : 'center',//水平居中valign : 'middle',//垂直居中sortable : false //是否排序
} ];//格式来自官方demos -->treeGrid(jquery扩展的一个网格树插件)
第三步:定义异步请求处理函数,代码参考如下:
function doGetObjects(){let url="/menu/doFindObjects";//构建treetable对象(tree.table.js)let treeTable=new TreeTable("menuTable",url,columns);//初始化treetabletreeTable.init();//底层会向服务端发送异步请求获取数据并更新到页面上
}
第四步:页面加载完成,调用菜单查询对应的异步请求处理函数,关键代码如下:
$(function(){doGetObjects();//在.input-group-btn容器上注册click事件,当点击容器内部的btn-add对应的子元素时,执行事件处理函数$(".input-group-btn").on("click",".btn-add",doLoadEditUI);
})
4 菜单管理删除操作实现
4.1 业务时序分析
基于用户在列表页面上选择的的菜单记录 ID,执行删除操作,本次删除业务实现中,首先
要基于 id 判断当前菜单是否有子菜单,假如有子菜单则不允许删除,没有则先删除菜单角
色关系数据,然后再删除菜单自身信息。其时序分析如图
4.2 服务端关键业务及代码实现
4.2.1 Dao 接口实现
业务描述及设计实现
数据层基于业务层提交的菜单记录 id,删除菜单角色关系以及菜单数据,菜单自身记录信息。
关键代码设计及实现
第一步:创建 SysRoleMenuDao 并定义基于菜单 id 删除关系数据的方法,关键代码如下:
@Mapper
public interface SysMenuDao(){int deleteObjectsByMenuId(Integer menuId);
}
第二步:在 SysMenuDao 中添加基于菜单 id 查询子菜单记录的方法。代码参考如下:
int getChildCount(Integer id);
第三步:在 SysMenuDao 中添加基于菜单 id 删除菜单记录的方法。代码参考如下:
int deleteObject(Integer id);
4.2.2 Mapper 文件实现
业务描述及设计实现
在 SysRoleMenuDao,SysMenuDao 接口对应的映射文件中添加用于执行删除业务的delete 元素,然后在元素内部定义具体的 SQL 实现。
关键代码设计与实现
第一步:创建 SysRoleMenuMapper.xml 文件并添加基于菜单 id 删除关系数据的元素,关键代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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="deleteObjectsByRoleId" parameterType="int">delete from sys_role_menuswhere menu_id=#{menuId}</delete>
</mapper>
第二步:在 SysMenuMapper.xml 文件中添加基于 id 统计子菜单数量的元素,关键代码如下:
<select id="getRowCount" parameterType="int" resultType="int">select count(*) from sys_menuswhere parentId=#{id}
</select>
第三步:在 SysMenuMapper.xml 文件添加 delete 元素,基于带单 id 删除菜单自身记录信息,关键代码如下:
<delete id="deleteObject">delete from sys_menuswhere id=#{id}
</delete>
4.2.3 Service 接口及实现类
业务描述与设计实现
在菜单业务层定义用于执行菜单删除业务的方法,首先通过方法参数接收控制层传递的菜单 id,并对参数 id 进行校验。然后基于菜单 id 统计子菜单个数,假如有子菜单则抛出异常,提示不允许删除。假如没有子菜单,则先删除角色菜单关系数据。最后删除菜单自身记录信息后并返回业务执行结果。
关键代码设计与实现
第一步:在 SysMenuService 接口中,添加基于 id 进行菜单删除的方法。关键代码如下:
int deleteObject(Integer id);
第二步:在 SysMenuServiceImpl 实现类中注入 SysRoleMenuDao 相关对象。关键代码如下:
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
第三步:在 SysMenuServiceImpl 实现类中添加删除业务的具体实现。关键代码如下:
@Overridepublic int deleteObject(Integer id) {//1、验证桉树合法性if (id == null || id <= 0) throw new IllegalArgumentException("请先选择");//2、基于id进行子元素查询int count = sysMenuDao.getRowCount(id);if (count > 0) throw new IllegalArgumentException("请先删除子菜单");//3、删除角色、菜单关系数据sysRoleMenuDao.deleteObjectsByRoleId(id);//4、删除菜单元素int rows = sysMenuDao.deleteObject(id);if (rows == 0) throw new ServiceException("此菜单可能已经不存在");//5、返回结果return rows;}
4.2.4 Controller 类实现
业务描述与设计实现
在菜单控制层对象中,添加用于处理菜单删除请求的方法。首先在此方法中通过形参
接收客户端提交的数据,然后调用业务层对象执行删除操作,最后封装执行结果,并在运
行时将响应对象转换为 JSON 格式的字符串,响应到客户端。
关键代码设计与实现
第一步:在 SysMenuController 中添加用于执行删除业务的方法。代码如下:
@RequestMapping("doDeleteObjects")
public JsonResult doDeleteObjects(Integer id){return new JsonResult(sysMenuService.deleteObject(id));
}
第二步:启动 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 信息。
关键代码设计与实现
package com.cy.pj.common.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Node implements Serializable {private static final long serialVersionUID = -7437395435719036861L;private Integer id;private String name;private Integer parentId;
}
6.2.2 Dao 接口实现
业务描述与设计实现
基于请求获取数据库对应的菜单表中的所有菜单 id,name,parentId,一行记录封装
为一个 Node 对象,多个 node 对象存储到 List 集合
关键代码设计与实现
在 SysMenuDao 接口中添加,用于查询上级菜单相关信息。关键代码如下:
List<Node> findztreeMenuNodes();
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 接口中,添加查询菜单信息的方法。关键代码如下:
List<Node> findZtreeMenuNodes();
第二步:在 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-ico layui-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 Entity 类定义
业务描述与设计实现
定义持久化对象,封装客户端请求数据,并将数据传递到数据层进行持久化。
关键代码设计与实现
菜单持久层对象类型定义,关键代码如下:
package com.cy.pj.sys.pojo;import lombok.Data;
import java.io.Serializable;
import java.util.Date;@Data
public class SysMenu implements Serializable {private static final long serialVersionUID = -7691821439056974454L;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 接口中定义数据持久化方法:
int insertObject(SysLog entity);
7.2.3 Mapper 映射文件定义
业务描述与设计实现
基于 SysMenuDao 中方法的定义,编写用于实现菜单添加的 SQL 元素。
关键代码设计与实现
在 SysMenuMapper.xml 中添加 insertObject 元素,用于写入菜单信息。关键代码如下:
<insert id="insertObject" parameterType="com.cy.pj.sys.pojo.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 接口中,添加用于保存菜单对象的方法。关键代码如下:
int saveObject(SysMenu sysMenu);
第二步:在 SysMenuServiceImpl 类中,实现菜单保存操作。关键代码如下:
@Override
public int saveObject(SysMenu sysMenu) {//1.参数校验if(sysMenu==null) throw new IllegalArgumentException("保存对象不能为空");if(StringUtils.isEmpty(sysMenu.getName())) throw new IllegalArgumentException("菜单名不能为空");//2.保存菜单int rows=sysMenuDao.insertObject(sysMenu);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.获取表单数据let type=$("form input:radio:checked").val();let name=$("#nameId").val();let parentId=$("#parentId").data("parentId");let url=$("#urlId").val();let permission=$("#permissionId").val();let sort=$("#sortId").val();//2.校验表单数据//3.异步提交表单数据并基于响应结果刷新页面.let saveUrl="/menu/doSaveObject";let params={name:name,type:type,parentId:parentId,url:url,permission:permission,sort:sort}console.log(params);$.post(saveUrl,params,function(result){if(result.state==1){alert(result.message);$("#mainContentId").load("/menu/menu_list");}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 接口中添加数据更新方法,关键代码如下:
int updateObject(SysMenu entity);
9.2.2 Mapper 映射文件定义
业务描述与设计实现
基于 SysMenuDao 中 updateObject 方法的定义,编写用于实现菜单更新的 SQL 元 素。
关键代码设计与实现
在 SysMenuMapper.xml 中添加 updateObject 元素,用于更新菜单信息。关键代码如下:
<update id="updateObject" parameterType="com.cy.pj.sys.pojo.SysMenu">update sys_menus setname=#{name},type=#{type},sort=#{sort},url=#{url},parentId=#{parentId},permission=#{permission},modifiedUser=#{modifiedUser},modifiedTime=now()where id=#{id}
</update>
9.2.3 Service 接口及实现
业务描述与设计实现
基于控制层请求,对数据进行校验并调用数据层对象将菜单信息更新到数据库中。
关键代码设计与实现
第一步:在 SysMenuService 接口中,添加用于更新菜单对象的方法。关键代码如下:
int updateObject(System entity);
第二步:在 SysMenuServiceImpl 类中,实现菜单保存操作。关键代码如下:
@Overridepublic int updateObject(SysMenu entity) {//1、合法验证if (entity == null) throw new IllegalArgumentException("保存对象不能为空");if (StringUtils.isEmpty(entity.getName())) throw new IllegalArgumentException("菜单名不能为空");//2、更新数据int rows = sysMenuDao.updateObject(entity);if (rows == 0) throw new ServiceException("记录可能已经不存在");return rows;}
9.2.4 Controller 类定义
业务描述与设计实现
接收客户端提交的菜单数据,并对其进行封装,然后调用业务层对象进行业务处理,最后将业务层处理结果响应到客户端。
关键代码设计与实现
定义 Controller 方法,借助此方法处理保存菜单数据请求和响应逻辑。关键代码如下:
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(SysMenu entity){return new JsonResult(sysMenuService.updateObject(entity));
}
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)
▪ 常用表连接方式,如图所示:
第三阶段实战(三)——菜单管理功能设计与实现相关推荐
- java 高并发第三阶段实战_JAVA多线程编程实战视频-第三阶段(共80节)
高并发编程第三阶段01讲 AtomicInteger多线程下测试讲解 高并发编程第三阶段02讲 AtomicInteger API详解,以及CAS算法详细介绍 高并发编程第三阶段03讲 利用CAS构造 ...
- java 高并发第三阶段实战_Java 高并发第三阶段实战---Java并发包深入解析与使用详解...
第三阶段的课程主要围绕着Java并发包的使用,展开详细的介绍,主要内容有1.原子包源码剖析,2.并发包工具类详细介绍,3.线程服务以及Future和callable等详细介绍,4.高并发容器和阻塞容器 ...
- Java 高并发第三阶段实战---Java并发包深入解析与使用详解
第三阶段的课程主要围绕着Java并发包的使用,展开详细的介绍,主要内容有1.原子包源码剖析,2.并发包工具类详细介绍,3.线程服务以及Future和callable等详细介绍,4.高并发容器和阻塞容器 ...
- 管理Linkedin账号三步曲,高效管理领英账号。
管理Linkedin账号三步曲 Linkedin是全球职业社交平台,全球有6亿用户,中国有5000+万用户.而且这些用户大多是公司的管理层或高层,在公司有很大的决策权.正是这些庞大的用户群体和高质量的 ...
- 分布式事务:两阶段提交与三阶段提交
两阶段提交与三阶段提交 分布式事务 二阶段提交 请求阶段 提交阶段 举例 故障分析 柜员侧出现故障或拒绝Proposal 第一阶段经理侧出现故障 第二阶段经理侧出现故障 二阶段提交存在的问题 三阶段提 ...
- Yii2.0 后台UI框架以及权限管理扩展实现权限菜单管理
Yii2.0 后台UI框架以及权限管理扩展实现权限菜单管理 一.安装后台框架ui 二.安装权限管理扩展 三.实现权限菜单管理 一.安装后台框架ui composer require dmstr/yii ...
- #6讲项目实战js第三区域响应式菜单
#6讲项目实战js第三区域响应式菜单 <!--整站建设步骤如下: 1.构建一个宽度100% 高度149px 这样一个长方形,为了保证浏览器的兼容,我们加了*{margin:0px;pad ...
- 7讲项目实战js第三区域响应式菜单续
#7讲项目实战js第三区域响应式菜单<续> <!--整站建设步骤如下: 1.构建一个宽度100% 高度149px 这样一个长方形,为了保证浏览器的兼容,我们加了*{margi ...
- Linux教学辅助训练(第三阶段)
Linux教学辅助训练(第三阶段) 标签(空格分隔): Linux辅助训练-陈思齐 ---更多资料点我查看 提示:本阶段性练习题是对<实战教学笔记>相应章节知识的归纳与扩展部分,必须要 会 ...
最新文章
- MaskFlownet:基于可学习遮挡掩模的非对称特征匹配丨CVPR 2020
- c# 实现文本读取,语音报警功能
- mysql查看使用的索引_Mysql查看是否使用到索引
- 【Java 网络编程】Socket TCP UDP 联系
- 路由协议:RIP/OSPF/BGP—Vecloud微云
- RV1108开发环境搭建
- 递归 - 求数字/字符串的全排列
- windows下gradle下使用windows版普罗米修斯prometheus和metrics简单的制作一个监控java环境的内存状况的教程
- 中props使用this报错_为什么在静态方法中不能使用this
- 怎样更改itunes备份位置_iphone备份太大,严重挤占C盘空间,怎么把备份放在其他的硬盘?...
- flex measure
- 【算法】排序_直接插入排序及其 优化(希尔排序)
- Typora自动上传图片
- ICC2里面多进程任务
- 三星note10 android q,当之无愧的安卓机皇!三星Note10+测评,结果难以想象
- nordic 52832中添加RTT打印
- ASP.NET MVC 学习(一)--MVC的cshtml的介绍
- tomcat执行shutdown报错Could not contact [localhost:8005] (base port [8005] and offset [0]). Tomcat may n
- 谴责那些没有良知的人
- 取汉字拼音首字母--生成不重复ID(汉字--拼音--首字母)
热门文章
- 对txt文件内容按首字母排序
- 拉好友退押金?ofo 小黄车奇葩操作又被骂上热搜!
- 海南大学计算机保研,海南大学有个“学霸团队” 13人保研“985”高校 他们干了啥?...
- 单行注释,多行注释,为什么我的多行注释会出现错误?
- C++的Dll来实现IceBox的服务
- python课堂笔记手抄图片_读书手抄报简单又漂亮_读书手抄报内容图片
- Java jar包解压
- matlab计算复活节概率,复活节算法
- 解决adb server version (36) doesn‘t match this client (41); killing...
- ebus sdk LNK2001:无法解析的外部符号