注意事项
商品新增前端说明:
1.商品新增弹窗页面说明。
2.树形结构分析:3级菜单id在数据库储存方式。
3. 树形结构json返回值分析,封装为vo对象。最外层为[ ],遍历的方式储存到list集合。
4.商品新增选择类目展现。(动态展现:可以利用注解动态获取参数,并且实现转化.)
5.SpringMVC参数传递说明:参数绑定的原理。
商品新增后端说明:
6.表单序列化 $(“#itemAddForm”).serialize();会帮你自动拼接整个表单的参数。
7.系统VO对象(状态码,告诉客户端返回值的释放正确)
8.全局异常处理机制
9.mp属性自带填充功能。
1.商品新增
1). 这2种方式都可以跳转到新增页面。
2.对应的页面代码(首页:index.jsp—>新增:item-add.jsp)
1.1 工具栏菜单说明
1.1.1 入门案例介绍
toolbar: [{
iconCls: 'icon-help',
handler: function(){alert("点击工具栏")}
},{
iconCls: 'icon-help',
handler: function(){alert('帮助工具栏')}
},'-',{
iconCls: 'icon-save',
handler: function(){alert('保存工具栏')}
},{
iconCls: 'icon-add',
text: "测试",
handler: function(){alert('保存工具栏')}
}]
框架自带的demo演示。
访问效果:
1.1.2 表格中的图标样式
toolbar:是个变量的定义。
F12查看页面结构: 给这个标签(新增商品)添加类选择器和点击事件,所以点击新增和新增商品效果相同。
1.2 页面弹出框效果
在新增页面点击选择类目,出现弹出框效果。
1.2.1 页面弹出框效果展现
入门案例:
访问demo效果。
$("#btn1").bind("click",function(){
//注意必须选中某个div之后进行弹出框展现
$("#win1").window({
title:"弹出框",
width:400,
height:400,
modal:false //这是一个模式窗口,只能点击弹出框,不允许点击别处
})
})
1.3 树形结构分析
1.3.1 商品新增选择类目数据结构分析
这个弹窗里面展现的一定是3级菜单的树形结构。
说明:一般电商网址的商品分类信息一般都是3级菜单. 级与级之间存在父子级关系. 在数据库中应该如何存储???
解答: 一般涉及到父子级关系时,一般采用parentId的形式进行关联.
即:一级商品分类菜单信息的父级(parent_id)一定为0,因为一级菜单最大。所以利用parent_id=0就可以查出一级菜单的信息了。
/*查询一级商品分类信息 父级Id=0*/
select * from tb_item_cat where parent_id=0;
/*查询二级商品分类信息 父级一级ID*/
SELECT * FROM tb_item_cat WHERE parent_id=1;
/*查询三级商品分类信息 父级二级ID*/
SELECT * FROM tb_item_cat WHERE parent_id=2;
1.3.2 树形结构入门案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>EasyUI-3-菜单按钮</title>
<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/jquery.min.js"></script>
<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/jquery.easyui.min.js"></script>
<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script>
<link rel="stylesheet" type="text/css"
href="/js/jquery-easyui-1.4.1/themes/icon.css" />
<link rel="stylesheet" type="text/css"
href="/js/jquery-easyui-1.4.1/themes/default/easyui.css" />
<script type="text/javascript">
/*通过js创建树形结构 */
$(function(){
$("#tree").tree({
url:"tree.json", //加载远程JSON数据 ajax请求
method:"get", //请求方式 get
animate:false, //表示显示折叠端口动画效果
checkbox:true, //表述复选框
lines:true, //表示显示连接线
dnd:true, //是否拖拽
onClick:function(node){ //添加点击事件
//控制台
console.info(node);
}
});
})
</script>
</head>
<body>
<h1>EasyUI-树形结构</h1>
<!-- ul-li 定义树形结构-->
<ul id="tree"></ul>
</body>
</html>
访问demo效果.
1.3.3 关于树形结构JSON串返回值分析
树形结构要求的参数结构分析。
一级树形结构的标识.
“[{“id”:“3”,“text”:“吃鸡游戏”,“state”:“open/closed”},{“id”:“3”,“text”:“吃鸡游戏”,“state”:“open/closed”}]”
分析:一级菜单,里面是{ }为一个一个的对象,外面是[ ]为一个List集合。
1.3.4 VO对象封装为EasyUITree
json外面最外层的[ ]一般不需要单独封装,只需要把里面一个一个的vo对象封装起来,在外层套一层list集合就行了。
这个不通用只需要放在manage项目模块下就行。
package com.jt.vo;
import lombok.Data;
import lombok.experimental.Accessors;
//该对象的主要的目的是为了展现树形结构的数据.
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class EasyUITree implements Serializable{
//"id":"3","text":"吃鸡游戏","state":"open/closed" 数据来源 数据表
private Long id; //商品分类的Id信息
private String text; //商品分类name属性
private String state; //由是否为父级决定 如果是父级则关闭closed 否则为子级open
}
1.4 商品新增选择类目展现
1.4.1 页面分析
<tr>
<td>商品类目:</td>
<td>
<a href="javascript:void(0)" class="easyui-linkbutton selectItemCat">选择类目</a>
<input type="hidden" name="cid" style="width: 280px;"></input>
</td>
</tr>
1.4.2 页面JS标识
页面URL标识:
1.4.3 编辑ItemCatController
这里面查询的数据用到的是商品分类表中的数据。
/**
* 业务需求: 实现商品分类的展现,户通过ajax请求,动态获取树形结构的数据.
* url地址: http://localhost:8091/item/cat/list
* 参数: parentId = 0 查询一级商品分类菜单.
* 返回值结果: List<EasyUITree> (因为要求的树状参数格式最外层是个[])
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(){
//这个地方先测试查询一级菜单,所以先把id写死。
Long parentId = 0L;
return itemCatService.findItemCatList(parentId);
}
1.4.3 编辑ItemCatService
总结:因为在与web前端进行交互的时候,前端所用的一些js框架要求返回特定的JSON,一般是在后台用VO对象进行封装。
其它的层级po,mapper在前面已经写过了。
/**
* 返回值: List<EasyUITree> 要求是VO集合对象信息
* 数据库查询返回值: List<ItemCat> 只能查询数据库记录
* 数据类型必须手动的转化,才能将数据进行有效的返回
* @param parentId
* @return
*/
@Override
public List<EasyUITree> findItemCatList(Long parentId) {
//1.根据父级分类Id查询数据,此时的结果为一个list集合里面为一个一个的ItemCat对象。
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", parentId);
List<ItemCat> catList = itemCatMapper.selectList(queryWrapper);
/*2. 现在要求的返回值为List<EasyUITree>类型。
* 需要将catList集合转化为voList,即 List<ItemCat>---->List<EasyUITree>
* 解决:一个一个转化. cartList遍历 封装EasyUITree 添加到集合中即可
*/
List<EasyUITree> treeList = new ArrayList<>();
for (ItemCat itemCat: catList){
Long id = itemCat.getId();
String text = itemCat.getName();
//如果是父级 应该closed(父级菜单关闭状态) 如果不是父级 应该open(子集菜单打开状态)
String state = itemCat.getIsParent()?"closed":"open";
EasyUITree easyUITree = new EasyUITree(id, text, state);
//3.封装返回值数据
treeList.add(easyUITree);
}
return treeList;
}
1.4.4 页面效果展现
1.5 动态实现商品新增选择类目展现
在1.4中父级id写死了,而且只能展现一级菜单信息。如何展现二级、三级标题呢,二级标题一定处于某个一级标题之下,它们之间一定会有个所属关系。
如何实现:比如把一级菜单的id当做二级菜单的父级id可以查询出二级菜单的信息,把二级菜单的id当做三级菜单的父级id就可以查出三级菜单的信息。
1.5.1 异步树控件加载说明
1).树形控件树形
每个节点都具备以下属性:
id:节点ID,对加载远程数据很重要。
text:显示节点文本。
state:节点状态,'open' 或 'closed',默认:'open'。如果为'closed'的时候,将不自动展开该节点。
checked:表示该节点是否被选中。
attributes: 被添加到节点的自定义属性。
children: 一个节点数组声明了若干节点。
可以再次api中查看。
2).异步树说明
树控件读取URL。子节点的加载依赖于父节点的状态。当展开一个封闭的节点,如果节点没有加载子节点,它将会把节点id的值作为http请求参数并命名为’id’,通过URL发送到服务器上面检索子节点。
总结:如果用户点击父节点需要展开子节点时,会将父节点的id的值当做参数传递。
url没有发生变化,只会拼接个参数。
1.5.2 编辑ItemCatController(方式一)
/**
/**
* 业务需求: 用户通过ajax请求,动态获取树形结构的数据.
* url: http://localhost:8091/item/cat/list
* 参数: 只查询一级商品分类信息 parentId = 0
* 返回值结果: List<EasyUITree> (因为要求的树状参数格式最外层是个[])
*
* 注意事项:
* 1.树形结构初始化时不会传递任何信息.只有展开子节点时传递Id,如果没有传递id,及初始化展现的是一级商品的id,展现的父级id就是0.
* 2.页面传递什么样的数据,后端必须接收什么样的数据
*
* id: 296
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(Long id){
//用三木运算符判断。没有传id或传了id的情况。
Long parentId = (id==null?0L:id);初始化没有传id,根据父级id=0就可以查询出一级菜单信息。
return itemCatService.findItemCatList(parentId);
}
1.5.3 编辑ItemCatController(方式二)
可以利用注解动态获取参数,并且实现转化.
/*
* 2.一般写业务需要见名知意(传的是id,需要把id当做父级id传递。当然也可以直接写成id但一般要求见名只
* 意思所以要写成parentId,但这样会导致id与parentId名字不匹配,就要这个注解来解决。)
* 一般用于接收参数名称不匹配,把id赋值给parentId 注意和@Param的区别,这个参数是操作mvc的,
一个是操作数据库的,defaultValue为0默认值用来初始化,mvc会自动进行类型转换变为long类型不用加L.
* */
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(
@RequestParam(value = "id",defaultValue = "0") Long parentId){
//方式1
//Long parentId = (id==null?0L:id);
return itemCatService.findItemCatList(parentId);
}
1.5.4 页面效果
因为一共3级菜单,所以前2级的菜单节点是close,第三级菜单不是父级所以默认为为open状态。
1.6 补充:关于SpringMVC参数传递说明
Request和Response相当于2个中立的容器。
java底层如何通过对象或变量来获取值得。只不过这些现在不需要写,已经封装好了,你只需要写对应的变量类型就可以接收到。
/**
* 页面参数: http://请求路径:/方法名称 ?id=1 name="tomcat"
* 对象: 1.request对象 页面给服务器参数 2.response对象 服务器给页面的响应数据.
*
* @param id
* @param name
* @return
*/
@RequestMapping("/xxxx")
//public String xxx(Long id, String name, Item item){
//springmvc底层都是通过request和response对象取值返回值的,现在直接写对应的参数类型就可以接受参数了,这些代码底层帮你封装好了。
public String xxx(HttpServletRequest request ){
/*
1.基本类型属性赋值.
String id = request.getParameter("id");
Long idLong = Long.valueOf(id);
String strArray = request.getParameter("array");
String[] strArrays = strArray.split(",");
String name = request.getParameter("namexxxxx");*/
//2.利用item对象获取属性(get,set方法)
/*item.getId() ~~~~去除get的前缀~~~~~~~Id~~首字母小写~~~"id"
String id = request.getParameter("id");
id----封装set方法----执行业务调用item.setId(id);
Item.......*/
}
总结:
1.正确编辑html标签 name属性必须书写正确 一般条件下与pojo属性相同.
2.参数接收时,校验属性的类型及属性名称是否正确. POJO属性必须写包装类型.
3.有些特定的属性可以利用SpringMVC 进行动态的转化 数组/list集合/map集合等.
1.7 商品新增业务实现
1.7.1 页面URL分析
1).url请求地址
2).页面提交参数
3).页面分析
html标签:
js代码:
function submitForm(){
//表单校验 .form('validate'),由ui框架提供的。
if(!$('#itemAddForm').form('validate')){
$.messager.alert('提示','表单还未填写完成!');
return ; //表示方法的结束,不会再向下执行。
}
//转化价格单位,将元转化为分 价格*100在通过.val()方法进行赋值。
//eval(js中专门做运算的,把里面的值都当做数字来计算) 100+100=200 100+“100”=100100
$("#itemAddForm [name=price]").val(eval($("#itemAddForm [name=priceView]").val()) * 100);
itemAddEditor.sync();//将输入的内容同步到多行文本中
var paramJson = [];
$("#itemAddForm .params li").each(function(i,e){
var trs = $(e).find("tr");
var group = trs.eq(0).text();
var ps = [];
for(var i = 1;i<trs.length;i++){
var tr = trs.eq(i);
ps.push({
"k" : $.trim(tr.find("td").eq(0).find("span").text()),
"v" : $.trim(tr.find("input").val())
});
}
paramJson.push({
"group" : group,
"params": ps
});
});
paramJson = JSON.stringify(paramJson);//将对象转化为json字符串
$("#itemAddForm [name=itemParams]").val(paramJson);
/* url地址 请求参数 回调函数 返回值类型(可以自动匹配,省略不写。)
* $.post/get(url,JSON,function(data){....})
* ajax参数传递的类型有几种:
* 1.json格式 {"id":"1","name":"tomat"}
* 2.字符串拼接 ?id=1&title="天龙八部&key=value...."
* 问题:如果参数很多的话要拼接很多参数太麻烦.
* 解决:表单序列化 $("#itemAddForm").serialize();会帮你自动拼接。
*/
//alert($("#itemAddForm").serialize());
$.post("/item/save",$("#itemAddForm").serialize(), function(data){
//可以通过封装一个系统级别的vo对象,通知这个对象的返回值状态码在前段的js中进行判断。
if(data.status == 200){
$.messager.alert('提示','新增商品成功!');
}else{
$.messager.alert("提示","新增商品失败!");
}
});
}
alert($(“#itemAddForm”).serialize());输出结果显示。
1.7.2 系统VO对象-SysResult (一般放在common里面)
说明:一般后端处理完成之后,需要给客户端返回有效信息,告知客户端程序执行是否正确。
要求:
1.通过status属性 200请求正确,201请求失败。
2.通过msq属性 服务器向客户端发送提示信息 String类型
3.通过data属性 服务器向客户端发送的业务数据 Object类型
package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {//SysResult 主要的目的是为了与页面进行交互. ajax/json
private Integer status; //200成功 201 失败
private String msg; //服务器提示信息 成功 失败
private Object data; //服务器返回值数据.
/*
* 用户在控制层想用这里面的数据需要每次new一个对象并且传参太过麻烦,
* 可以利用static的静态方法 将数据动态返回.
*/
//(1). 执行失败了也没必要返回数据了。
public static SysResult fail(){ //static为了方便调用。
return new SysResult(201, "业务执行失败", null);
}
/*(2).成功返回值的情况:
* 1.只需要返回状态码信息 200
* 2.需要返状态及业务数据 200,data
* 3.返回提示信息,data业务数据
* 解决:用重载的方法.
* */
public static SysResult success(){
return new SysResult(200, "业务执行成功!", null);
}
//String json = "{key:value}"
public static SysResult success(Object data){
return new SysResult(200, "业务执行成功!", data);
}
/*只想返回提示信息:如果将来传递的是String类型的json值,
*本来想走上面那个方法结果匹配到了下面这个方法显然不合适。所以再加个参数。
*/
public static SysResult success(String msg,Object data){
return new SysResult(200, msg, data);
}
}
1.7.3 编辑ItemController
商品新增属于商品表。
/**
* 业务需求: 完成商品入库操作,返回系统vo对象
* url1: /item/save
* 参数: 整个form表单
* 返回值: SysResult对象
*/
@RequestMapping("/save")
public SysResult saveItem(Item item){
try { //每次写太麻烦可以用全局异常处理类。
itemService.saveItem(item);
//没有要求返回数据,所以直接返回状态码。静态方法通过:类名.调用。
return SysResult.success();
}catch (Exception e){
//没有做日志,所以异常类型暂时打印到控制台。
e.printStackTrace();
//返回给用户错误信息。
return SysResult.fail();
}
1.7.4 编辑ItemService
//商品添加
/*注意2点: 这几个值不是页面传来的是自己设置的。
* 1).商品入库的默认状态:为1(上架)状态。
* 2)商品入库的时间(继承的BasePojo中) :需要编写入库创造和更新时间。
*
* */
@Override
public void saveItem(Item item) {
/*思考:如果每次编译数据库·每次都需要操作公共的属性(设置创造和更新入库的时间),
* 如何完成自动填充功能?
*
*/
Date date =new Date();
item.setStatus(1).setCreated(date).setUpdated(new Date());//更新时间就是创造时间(取值刚存进数据库的创造时间。)
itemMapper.insert(item);
//int a=1/0;测试全局异常处理机制,看后台会报错吗。
/*
* 注意这个错误是全局异常处理类帮你处理的,出现了错误你是自己不能处理的,所以
* 要往外抛。所以try-catch不能随意添加(因为控制层不知道你出现了错误,可能会出现:恭喜你运行成功,一查询程序报错),
* 一旦写了try-catch后就一定要向外抛出(throw)即出现了错误一定要告诉控制层的全局异常处理机制。
* */
}
访问测试:
注意:现在点击新增后不能刷新页面,原因是这是2个不同的页面,不能在一个页面影响另一个页面。想要刷新需要在同一个页面。
1.8 全局异常处理机制
1.8.1 作用
如果在每个方法中添加异常处理机制,则会导致整个代码的结构混乱.即使将来出现了异常,也不能很好的管理.所以需要一种统一的方式实现异常的处理.
核心:该功能在Spring中利用AOP的方式实现.
1.8.2 创建全局异常处理(common里面)
package com.jt.aop;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//@ControllerAdvice //拦截controller层(因为mapper层和service层异常最终抛给controller层,所以拦截控制层最合适。)
//@ResponseBody 与页面交互都是json型的数据
@RestControllerAdvice //定义全局异常的处理类 AOP=异常通知(底层封装好了)
public class SystemAOP {
/**
* 定义全局异常的方法 当遇到了什么异常时,程序开始执行 参数一般class类型
* 如果一旦发生异常,则应该输出异常的信息,之后返回错误数据即可.
*/
@ExceptionHandler(RuntimeException.class) //多个不同类型的异常(如io,sql),写法:{RuntimeException.class,xxx,xxx}
public Object systemAop(Exception e){
e.printStackTrace();
return SysResult.fail();
}
}
1.8.3 重新编译ItemController
/**
* 业务需求: 完成商品入库操作,返回系统vo对象
* url1: /item/save
* 参数: 整个form表单
* 返回值: SysResult对象
*/
@RequestMapping("/save")
public SysResult saveItem(Item item){
itemService.saveItem(item);
//没有要求返回数据,所以直接返回状态码。静态方法通过:类名.调用。
return SysResult.success();
/*try {
itemService.saveItem(item);
//没有要求返回数据,所以直接返回状态码。静态方法通过:类名.调用。
return SysResult.success();
}catch (Exception e){
//没有做日志,所以异常类型暂时打印到控制台。
e.printStackTrace();
//返回给用户错误信息。
return SysResult.fail();
}*/
}
测试:在业务层故意写错
1.9 属性自动填充功能(Mp)
当前每个Item继承BasePojo类(商品对象)都需要设置创造,更新时间,每次写太麻烦。当前用的是mp的方式写的,mp提供了属性自动填充功能(可以再官网查看)
1.9.1 编辑BasePojo类(common)
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
//pojo基类,完成2个任务,2个日期,实现序列化
@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
//注意这里的INSERT UPDATE指的是数据库的更新 插入的sql关键字
@TableField(fill = FieldFill.INSERT) //入库时自动添加 创建时间一般商品入库时添加
private Date created;
@TableField(fill = FieldFill.INSERT_UPDATE) //入库/更新操作自动添加 更新时间一般入库,更新操作都要添加。
private Date updated;
}
1.9.2 编辑配置类MyMetaObjectHandler(common中)
package com.jt.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component //将对象交给spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) { //入库
/*
执行流程:当用注解标识pojo类后,程序执行入库操作时就会执行对应的方法添加参数。
* */
Date date = new Date(); //保证时间一致,如果在括号里每次new Date不能保证时间一致,所以在这里new.
//入库时: 需要更新的字段名 里面的值 api的对象
this.setInsertFieldValByName("created", date,metaObject);
this.setInsertFieldValByName("updated", date,metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {//更新
this.setUpdateFieldValByName("updated", new Date(), metaObject);
}
}
1.9.3 修改ItemServiceImpl(manage中)
@Override
public void saveItem(Item item) {
/*思考:如果每次编译数据库·每次都需要操作公共的属性(设置创造和更新入库的时间),
* 如何完成自动填充功能?
* 属性自动填充功能
*/
Date date =new Date();
//item.setStatus(1).setCreated(date).setUpdated(new Date());//更新时间就是创造时间(取值刚存进数据库的创造时间。)
item.setStatus(1);
itemMapper.insert(item);
//int a=1/0;测试全局异常处理机制 这报异常一定会向上抛
/*
* 注意这个错误是全局异常处理类帮你处理的,出现了错误你是自己不能处理的,所以
* 要往外抛。所以try-catch不能随意添加(因为控制层不知道你出现了错误,可能会出现:恭喜你运行成功,一查询程序报错),
* 一旦写了try-catch后就一定要向外抛出(throw)即出现了错误一定要告诉控制层的全局异常处理机制。
* */
}
测试: