业务设计说明
菜单管理又称为资源管理,是系统资源对外的表现形式。本模块主要是实现对菜单进行添加、修改、查询、删除等操作。
菜单表与角色表是多对多的关系,在表设计时,多对多关系通常由中间表(关系表)进行维护,如图:
基于角色菜单表的设计,其角色和菜单对应的关系数据要存储到关系表中,其具体存
储形式,如图:
原型设计说明
基于用户需求,实现菜单静态页面(html/css/js),通过静态页面为用户呈现菜单模块的基本需求实现。
当在主页左侧菜单栏,点击菜单管理时,在主页内容呈现区,呈现菜单列表页面,如
图:
当在菜单列表页面点击添加按钮时,异步加载菜单编辑页面,并在列表内容呈现区,
呈现菜单编辑页面,如图
在菜单编辑页面选择上级菜单时,异步加载菜单信息,并以树结构的形式呈现上级菜
单,如图
说明:假如客户对此原型进行了确认,后续则可以基于此原型进行研发。
API设计说明
菜单管理业务后台API分层架构及调用关系如图
说明:分层目的主要将复杂问题简单化,实现各司其职,各尽所能。
菜单管理列表页面呈现
1.业务时序分析
菜单管理页面的加载过程,其时序分析如图
2.服务端实现
共性代码的提取:
a)SysLogController中:
当controller中每个方法的返回值都有@responseBody,可以将@responseBody放于类上,这时可以用
**@RestController//@RestController等效于@Controller加@ResponseBody**
b)GlobalExceptionHanderler中:
1)@ControllerAdvice 注解描述的类,spring mvc会认为它是一个控制层全局异常处理对象。
2) @ExceptionHandler 注解描述的方法为异常处理方法(不是我们定的),
此注解中定义的异常类型,为这个方法可以处理的异常类型,它可以处理 此异常以及这个异常类型的子类类型的异常。
**@RestControllerAdvice//等效于@ControllerAdvice加上@ResponseBody**
GlobalExceptionHandler
package com.cy.pj.common.web;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.cy.pj.common.pojo.JsonResult;
/**
* @ControllerAdvice 注解描述的类,spring mvc会认为它是一个控制层全局异常处理对象。
*/
//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice //等效于@ControllerAdvice+@ResponseBody
public class GlobalExceptionHandler {
/**
* @ExceptionHandler 注解描述的方法为异常处理方法(不是我们定的),
* 此注解中定义的异常类型,为这个方法可以处理的异常类型,它可以处理
* 此异常以及这个异常类型的子类类型的异常。
* @param e 此参数用于接收具体异常对象,其类型一般与@ExceptionHandler
* 注解中定义异常类型相同或者为其父类类型。
* @return 封装了异常信息的对象
*/
@ExceptionHandler(RuntimeException.class)
//告诉spring mvc最终将响应结果转换为json格式进行输出。
// @ResponseBody
public JsonResult doHandleRuntimeException(RuntimeException e) {
//方法内部实现异常处理
//例如:输出异常,封装异常
//输出或打印异常
e.printStackTrace();
//封装异常信息
return new JsonResult(e);
}
//。。。。。。。。。。。
}
第一步:在PageController中定义返回菜单列表的方法。
@RequestMapping("menu/menu_list")
public String doMenuUI() {
return "sys/menu_list";
}
第二步:在PageController中基于rest风格的url方式优化返回UI页面的方法。找出共性进行提取
//rest 风格(一种架构风格)的url,其语法结构{变量名}/{变量}
//@PathVariable 注解用于修饰方法参数,可以从rest风格的url中取和参数名对应的值
@RequestMapping("{module}/{moduleUI}")
public String doModuleUI(@PathVariable String moduleUI) {
return "sys/"+moduleUI;
}
分析:
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.菜单列表页面
本页面呈现菜单信息时要以树结构形式进行呈现。此树结构会借助jquery中的treeGrid插件进行实现,所以在列表页面需要引入treeGrid相关JS。但是,具体的treeGrid怎么用可自行在网上进行查询(已比较成熟)
关键JS引入,代码如下:
<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>
菜单管理列表数据呈现
1.数据架构分析
说明:本模块将从数据库查询到的菜单数据封装到map对象,一行记录一个map对象,其中key为表中的字段(列)名,值为字段(列)对应的值。
2.数据加载过程其时序分析,如图
3.服务端关键业务及代码实现
3.1 SysMenuDao
package com.cy.pj.sys.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
/**
* 查询所有菜单信息以及菜单对应的上级菜单名称
*/
@Mapper
public interface SysMenuDao {
List<Map<String,Object>> findObjects();
}
3.2 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">
<!-- 查询所有菜单以及菜单对应的上级菜单名称 -->
<select id="findObjects" resultType="map">
<!-- 方案1:嵌套查询 -->
<!-- select c.*,(
select p.name
from sys_menus p
where c.parentId=p.id
) parentName
from sys_menus c
-->
<!-- 方案2: 多表查询中自关联查询 左外关联-->
select c.*,p.name parentName
from sys_menus c left join sys_menus p
on c.parentId=p.id
</select>
</mapper>
说明:自关联查询分析,如图
3.3 Service接口及实现类
第一步:定义菜单业务接口及方法,暴露外界对菜单业务数据的访问,其代码参考如下:
package com.cy.pj.sys.service;
import java.util.List;
import java.util.Map;
public interface SysMenuService {
List<Map<String,Object>> findObjects();
}
第二步:定义菜单业务接口实现类,并添加菜单业务数据对应的查询操作实现,其代码参考如下:
package com.cy.pj.sys.service.impl;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.service.SysMenuService;
@Service
public class SysMenuServiceImpl implements SysMenuService {
@Autowired
private SysMenuDao sysMenuDao;
@Override
public List<Map<String, Object>> findObjects() {
// TODO Auto-generated method stub
List<Map<String,Object>> list=sysMenuDao.findObjects();
if(list==null||list.size()==0)
throw new ServiceException("没有对应的菜单信息");
return list;
}
}
service层测试类
package com.cy.pj.sys.dao;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import lombok.extern.slf4j.Slf4j;
//@Slf4j
@SpringBootTest
public class SysMenuTests {
//初始化日志对象,假如使用lombok的@Slf4j,底层会自动帮我们生成如下语句
//设计思想:门面模式
private static final Logger log=//接口,默认指向的实现类类为logback
LoggerFactory.getLogger(SysMenuTests.class);
@Autowired
private SysMenuDao sysMenuDao;
@Test
void testFindObjects() {
List<Map<String,Object>> list=sysMenuDao.findObjects();
log.info("list.size{}",list.size());
/*
* for (Map<String, Object> map : list) { System.out.println(map); }
*/
list.forEach(item->System.out.println(item));//JDK8 Lamda
list.forEach(System.out::println);//JDK8方法引用
}
}
关于日志输出说明
4.Controller类的实现
控制层对象主要负责请求和响应数据的处理,例如,本模块通过业务层对象执行业务逻辑,再通过VO对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为JSON格式的字符串响应到客户端。
package com.cy.pj.sys.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cy.pj.common.pojo.JsonResult;
import com.cy.pj.sys.service.SysMenuService;
@RequestMapping("/menu/")
@RestController
public class SysMenuController {
@Autowired
private SysMenuService sysMenuService;
@RequestMapping("doFindObjects")
public JsonResult doFindObjects() {
return new JsonResult(sysMenuService.findObjects());
}
}
客户端业务的实现
菜单页面加载完成以后,向服务端发起异步请求加载菜单信息,当菜单信息加载完成需要将菜单信息呈现到列表页面上。
第一步:在菜单列表页面引入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//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();
})
菜单管理删除操作实现
页面上的删除代码(局部刷新):
1.业务时序分析
基于用户在列表页面上选择的的菜单记录ID,执行删除操作,本次删除业务实现中,首先要基于id判断当前菜单是否有子菜单,假如有子菜单则不允许删除,没有则先删除菜单角色关系数据,然后再删除菜单自身信息。其时序分析如图
2.服务端关键业务及代码实现
数据层基于业务层提交的菜单记录id,删除菜单角色关系以及菜单数据,菜单自身记录信息。
第一步:在创建SysRoleMenuDao并定义基于菜单id删除关系数据的方法,关键代码如下:
package com.cy.pj.sys.dao;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
/**
* 基于此执行角色,菜单关系数据操作
* */
@Mapper
public interface SysRoleMenuDao {
//基于菜单id删除角色菜单关系数据
@Delete(" delete from sys_role_menus where menu_id=#{menuId} ")
int deleteObjectsByMenuId(Integer MenuId);
}
第二布:SysMenuDao中:
//基于菜单id统计菜单对应的子元素
@Select("select count(*) from sys_menus where parentId=#{id} ")
int getChildCount(Integer id);
//删除菜单自身信息
@Delete("delete from sys_menus where id =#{id}")
int deleteObject(Integer id);
第三步:在SysMenuService中:
//基于菜单id删除角色菜单关系,在删除自身信息
int deleteObject(Integer id);
第四步:在SysMenuServicImple中
//首先注入SysRoleMenuDao
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
//添加方法
@Override
public int deleteObject(Integer id) {
//1.判定参数,参数校验
if(id==null||id<1)
throw new IllegalArgumentException("参数值无效");
//2.统计菜单子元素并校验
int childCount=sysMenuDao.getChildCount(id);
if(childCount>0)
throw new ServiceException("请先删除子菜单");
//3.删除关系数据
sysRoleMenuDao.deleteObjectsByMenuId(id);
//4.删除自身信息
int rows=sysMenuDao.deleteObject(id);
if(rows==0)
throw new ServiceException("记录可能已经不存在");
return rows;
}
第五步:SysMenuController中:
@RequestMapping("doDeleteObject")
public JsonResult doDeleteObject(Integer id){
sysMenuService.deleteObject(id);
return new JsonResult("delete ok");
}
知识点:
1.控制台页面的清空:
问题分析:
添加功能的实现
页面的加载:
加载成功后页面:
添加页面加载时序分析:
功能说明:
首先准备菜单列表页面(/templates/pages/sys/menu_edit.html),然后在menu_list.html页面中点击菜单添加时异步加载菜单编辑页面。
菜单编辑页面呈现
第一步:菜单列表页面上,对添加按钮进行事件注册,关键代码如下
$(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);
})
}
ZTree结构:
官网 http://www.treejs.cn/v3/api.php
在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>
第一步:引入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)
});
服务端代码:
Node对象:
package com.cy.pj.common.pojo;
import java.io.Serializable;
import lombok.Data;
@Data
public class Node implements Serializable{
private static final long serialVersionUID = 485127885402380257L;
private Integer id;
private String name;
private Integer parentId;
}
SysMenuDao接口中添加
//查询所有菜单的菜单id,name,parentId
@Select("select id,name,parentId from sys_menus ")
List<Node> findZtreeMenuNodes();
SysMenuService接口
List<Node> findZtreeMenuNodes()
SysMenuServiceImpl类中
@Override
public List<Node> findZtreeMenuNodes() {
// TODO Auto-generated method stub
return sysMenuDao.findZtreeMenuNodes();
}
controller中:
@RequestMapping("doFindZtreeMenuNodes")
public JsonResult doFindZtreeMenuNodes(){
return new JsonResult(
sysMenuService.findZtreeMenuNodes());
}
菜单数据添加实现
数据基本架构分析
用户在菜单编辑页面输入数据,然后异步提交到服务端,其简易数据传递基本架构,如图
用户在菜单添加页面中填写好菜单数据,然后点击保存按钮,将用户填写的数据添加到数据库。其时序分析,如图
服务端关键业务及代码实现
Entity类定义
菜单持久层对象定义:
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;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getCreatedUser() {
return createdUser;
}
public void setCreatedUser(String createdUser) {
this.createdUser = createdUser;
}
public String getModifiedUser() {
return modifiedUser;
}
public void setModifiedUser(String modifiedUser) {
this.modifiedUser = modifiedUser;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}
SysMenuDao接口
int insertObject(SysMenu entity);
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>
SysMenuService接口中
int saveObject(SysMenu entity);
SysMenuServiceImpl类中,实现菜单保存操作。
@Override
public int saveObject(SysMenu entity) {
//1.合法验证
if(entity==null)
throw new ServiceException("保存对象不能为空");
if(StringUtils.isEmpty(entity.getName()))
throw new ServiceException("菜单名不能为空");
int rows;
//2.保存数据
try{
rows=sysMenuDao.insertObject(entity);
}catch(Exception e){
e.printStackTrace();
throw new ServiceException("保存失败");
}
//3.返回数据
return rows;
}
等效于
Controller中
@RequestMapping("doSaveObject")
@ResponseBody
public JsonResult doSaveObject(SysMenu entity){
sysMenuService.saveObject(entity);
return new JsonResult("save ok");
}
客户端关键业务及代码实现
1.页面cancel按钮事件处理
第一步:事件注册(页面加载完成以后)
$(".box-footer")
.on("click",".btn-cancel",doCancel)
第二步:事件处理函数定义
function doCancel(){
var url="menu/menu_list";
$("#mainContentId").load(url);
}
2.页面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;
}
优化:
1.将parentId,id存储到缓存中,在添加或者修改时,菜单某些模块信息直接从缓存中获得,不用与数据库做交互
2.将Map<String,Object>封装成一个对象
3.可以考虑在表中增加冗余字段parentname,这样虽然提高了查询速度但是却提高了维护成本,在修改表时需要同时修改多个数据
一般增加冗余字段时为了增加查询速度,而且不经常修改的数据
能使用单表不使用连表查询
菜单修改页面数据呈现
数据来源于页面绑定的tr
var data=$(“tbody input📻checked”).parents(“tr”).data(“rowData”);
业务时序分析
当在菜单列表页面中选中某条记录,然后点击修改按钮时,其业务时序分析如图
客户端关键业务及代码实现
点击页面修改按钮时,获取选中菜单记录,并异步加载编辑页面。
第一步:列表页面修改按钮事件注册
$(".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);
})
}
第三步:获取用户选中记录的函数定义。
unction doGetCheckedItem(){
var tr=$("tbody input[type='radio']:checked")
.parents("tr");
return tr.data("rowData");
}
编辑页面菜单数据呈现
页面加载完成,在页面指定位置呈现要修改的数据。
第一步:页面加载完成以后,获取页面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);
}
菜单数据更新实现
SysMenuDao接口
int updateObject(SysMenu entity);
SysMenuMapper.xml
<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>
SysMenuService接口中
int updateObject(SysMenu entity);
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;
}
controller中
@RequestMapping("doUpdateObject")
public JsonResult doUpdateObject(
SysMenu entity){
sysMenuService.updateObject(entity);
return new JsonResult("update ok");
}
客户端关键业务及代码实现
点击页面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);
}
});
}
知识点:
1.在Lombok中,生成构造方法的annotation一共有三个:
a)@NoArgsConstructor : 生成一个无参数的构造方法,这个annotation在与其他的annotation配合起来使用的时候更加能凸显出他的重要性,例如在使用hibernate这种框架的时候,如果有一个有参数的构造方法的时候,NoArgsConstructor会展示出他的作用。
b)@RequiredArgsConstructor: 会生成一个包含常量,和标识了NotNull的变量 的构造方法。生成的构造方法是private,如何想要对外提供使用可以使用staticName选项生成一个static方法。
c)@AllArgsContructor: 会生成一个包含所有变量,同时如果变量使用了NotNull annotation , 会进行是否为空的校验.@AllArgsConstructor 在生成的构造函数上会生成一@ConstructorProperties 的Java annotation, 当然也可以通过将suppressConstructorProperties 设置为true来禁用@ConstructorProperties 。
2.表单数据的获取:
val():取类型的值,获取的是输入的值
data():获取绑定的数据
3.项目使用map和实体类作为参数的优缺点
map的优点:
1.灵活性强于javaBean,易拓展,耦合性低;
2.写起来简单,代码量少;
3.mybatis查询返回的结果本身就是map,可能会比返回javabean快;
map缺点:
1.javabean在数据输入编译期就会对一些数据类型进行校验,如果出错会直接提示。而map的数据类型则需要到sql层,才会进行处理判断;
2.map的参数名称如果写错,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误;
3.map的参数值如果多传、乱传,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误;
4.仅仅看方法签名,你不清楚Map中所拥有的参数个数、类型、每个参数代表的含义。 后期人员去维护,例如需要加一个参数等,如果项目层次较多,就需要把每一层的代码都了解清楚才能知道传递了哪些参数。
javabean的优点:
1.面向对象的良好诠释;
2.数据结构清晰,便于团队开发和后期维护;
3.代码足够健壮,可以排除掉编译期错误;
javabean缺点:
1.代码量增多,大量时间去封装用到的表对象;
2.可能会影响开发效率。
4.将项目中数据导出到excel(用阿里的easyexcel)
5.客户端取值
data是用于存取数据
val()获取input、select的value值,需要有value属性