admin管理员组文章数量:1547999
1 京淘项目总体架构图
这个项目中用到了分布式思想。
下面介绍一下分布式思想。
1.1 分布式思想
1.1.1 概念
说明:将大型项目按照特定的规则进行拆分.
目的:减少项目架构的耦合性. (化整为零 拆! )
思考:单体项目存在什么问题?
如果在一个大型项目中,将所有的功能模块(eg:登录模块,权限模块,订单模块······)都写在一个服务器上,将来如果其中一个模块出现了问题,gg了,那么整个项目都会gg掉。
而项目如果做成“分布式”架构,即使一个模块gg了,其他模块不受影响,还能继续运行。
1.1.2 大项目的拆分方法-----按照功能业务拆分
说明:按照特定的模块进行拆分,之后各自独立的运行.相互之间不受影响.
但有时候,按照功能业务拆分后,一个个“系统”范围还是很大,需要进一步拆分,这样就能把责任具体落实到单个人。
1.1.3 大项目的拆分方法-----先照功能业务拆分,再按照层级进行拆分
按照层级拆分 是 按照功能拆分 的一种递进。
1.2 分布式应用中的问题说明
问题:虽然引入了分布式思想.但是同时也带来了一些问题,怎么解决???
1.2.1 分布式系统中的jar包如何管理?
jt项目的父级工程叫做:jt
用来统一管理第三方的jar包,统一各个子项目用的jar包的版本号。
这些jar包通常都是第三方的大厂写的开源的。在我的项目中我直接拿来用就好了。
jt父级工程,它是一个聚合工程,它里面包含了很多的子项目,eg:jt-common,jt-manager…
jt父级工程的最小单位是一个个jar包,而子项目继承了父工程,那么各个子工程也能用父类下的第三方jar包文件了。
各个子项目 继承了 父级工程jt
1.2.2 分布式系统中的工具API等如何管理?
而我在写各个子项目时,公司的大佬会提前写好一个个工具API,并打成jar包,让我去调用,规范我的代码的书写。
各个子项目 依赖于 大佬写的工具API的jar包
而大佬在写工具API时也是参照第三方大厂的jar包文件写的,最后也会打成jar包文件。
所以 本公司大佬写的 工具API 也是 继承了 父级工程jt
2 一个项目 整合web资源的3种形式
2.1 如果sping项目整合的资源是.html资源。如DB系统,它里面整合的资源就是.html的形式。(一般新项目会是这种形式)
我就可以通过Thymeleaf这个模板引擎,将服务器传回来的数据绑定到页面上。
2.2 如果spring项目整合的资源是.jsp资源,由于jsp资源是动态的web资源(即会与服务器交互,根据服务器传回来的数据的不同,展现不同的结果),一般这些.jsp文件会被放进webapp目录下,从而将服务器传回来的数据整合到.jsp上。
如:jt项目
2.3 大前端形式
当前后端完全分录后,前端通过node.js封装前端框架。前端工程师只需要写html/css/js。
前端也会有自己的端口号。
前端的请求数据是由后端服务器提供的。
首先请求发送到后端的“服务消费者”,即Controller层(DispatcherServlet)。
业务比较麻烦时,再在后端的内部,由Controller将请求发送给业务层,数据层等,即发送给(服务提供者)。
3 项目中引入js类库的2种方式
3.1 将下载下来的js文件放到本地,再在.jsp文件中通过
3.2 从cdn服务器上下载js文件。
提供js的公司会把它们的js产品文件放到cdn服务器上。而cdn服务器会自动选择一个离我最近的一个服务器,让我从它上下载js资源。
4 正式开始创建jt项目
4.1 前言
jt项目是由一个父工程jt,里面包裹着众多子项目jt-common,jt-manage…构成的。
可以通过项目打包的方式区分它们。
通常,项目的打包方式分3种:
jar (通常工具API都打成jar包(eg:jt-common),springboot中一般项目也可以打成jar包)
war (业务项目可以打成war包,也可以打成jar包,eg:jt-manage)
pom (父级项目都要打成pom包)
一般在一个项目的pom.xml文件中,通过标签,来定义这个项目的打包方式。
如果不写这个标签,这个项目就是默认的打jar包。
4.2 创建父级项目jt
4.2.1 新建一个Project,名字为jt
整个jt项目中在创建项目时,创建的都是普通的maven项目。这里不用选择“原型”,直接下一步。
一个个原型 ,就是一个个骨架。
我创建的maven长什么样,有什么包结构什么,都可以从不同的骨架去选择。
但现在都是微服务了,骨架这里已经不用选了。直接下一步。
GroupId 是在定义maven的坐标,一般都是公司域名倒着写。
填写完此页的内容,直接finish。
4.2.2 编辑父级项目jt的pom.xml文件
上一步完成后,会自动生成一个父级jt的pom文件,需要手动加一些依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0"
xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>jt</artifactId>
<version>1.0-SNAPSHOT</version>
<!--我是父级工程,是聚合项目可以包含子项目-->
<packaging>pom</packaging>
<!--parent标签作用: 定义了SpringBoot中所有关联项目的版本号信息.-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<!--项目打包时,跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
<!--开箱即用:SpringBoot项目只需要引入少量的jar包及配置,即可拥有其功能.
spring-boot-starter 拥有开箱即用的能力.
maven项目中依赖具有传递性.
A 依赖 B 依赖 C项目 导入A bc会自动依赖
-->
<dependencies>
<!--直接依赖web springMVC配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<!--springBoot-start SpringBoot启动项 -->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot重构了测试方式 可以在测试类中 直接引入依赖对象-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springBoot数据库连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--spring整合mybatis-plus 只导入MP包,删除mybatis包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!--springBoot整合JSP添加依赖 -->
<!--servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--jstl依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--使jsp页面生效 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--添加httpClient jar包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--引入dubbo配置 -->
<!--<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>-->
<!--添加Quartz的支持 -->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>-->
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--spring整合redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
</dependencies>
<!--build标签只有添加了主启动类的java文件才需要 jt是父级工程只做jar包的定义.-->
</project>
4.3 创建子项目jt-common
4.3.1 新建一个module,名字为jt-common
jt-common也是一个普通的maven项目
4.3.2添加工具API
这些API是老师提前已经写好的,目前是两个pojo类,我只是CV进了我的项目中。
我在别的子项目,eg:jt-manage中会用到。
4.4 创建子项目jt-manage
4.4.1 新建一个module,名字为jt-manage
注意:如果项目名错了,尽量重新建一个项目,直接改原项目名会引来很多不必要的麻烦。—记一坑
4.4.2 导入src文件
这些src文件也是老师写的半成品,我直接CV过来
4.4.3 修改YML配置文件
先将druid数据源和driver-class-name 这两行注释掉
server:
port: 8091
servlet:
context-path: /
spring:
datasource:
#引入druid数据源
#type: com.alibaba.druid.pool.DruidDataSource
#driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
4.4.4 配置项目启动项
4.4.5 编辑pom文件
引入jtmon依赖,并添加build标签
<project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache/POM/4.0.0 https://maven.apache/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jt</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>jt-manage</artifactId>
<packaging>war</packaging>
<!--添加依赖-->
<dependencies>
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--添加插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
知识点==============================
哪个子项目需要单独运行发布的,就在哪个项目的pom文件中添加build标签。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
注意:
用idea创建时,pom文件中<version>标签中的版本号都是1.0-SNAPSHOT
而用STS创建时,pom文件中<version>标签中的版本号都是0.0.1-SNAPSHOT
所以不要盲目CV,切记切记
5 jt项目后台业务说明
5.1 项目默认访问路径localhost:8091的说明
这是springboot提供的功能。
通常在DB项目中,我访问主页时输入的是:localhost:8080/doIndexUI…(controller中doIndexUI方法对应返回的就是主页:start.html)
但在第二阶段,WEB阶段,老师讲过,在我自己下载的Tomcat文件夹中,有一个web.xml配置的文件。
打开它以后,发现在最后面,它默认进行了如下配置。
似乎是有关“欢迎页面”的一些设置。
具体是啥,还是有点儿模糊。
我再看看项目启动后,控制台输出的内容
这时,老师又讲了,spring在将tomcat内嵌进来后,保留了tomcat的这个功能。
控制台这句话翻译过来也就是,“增加欢迎页面模板:index”
SpringBoot内部通过默认引擎 发送了一个index请求,但是这个请求不需要通过controller进行接收。
SpringBoot会自己拼接视图解析器的前缀和后缀,完成页面的跳转。
拼接完之后就成了:
/WEB-INF/views/index.jsp
正好就对应着我项目中的首页页面
所以,我只要输入localhost:8091就可以访问到index.jsp
5.2 jt项目前台的UI---------EasyUI(已过时)
5.2.1 EasyUI介绍
easyui是一种基于jQuery、Angular.、Vue和React的用户界面插件集合。
easyui为创建现代化,互动,JavaScript应用程序,提供必要的功能。
使用easyui你不需要写很多代码,你只需要通过编写一些简单HTML标记,就可以定义用户界面。
easyui是个完美支持HTML5网页的完整框架。
easyui节省您网页开发的时间和规模。
easyui很简单但功能还是挺强大的。
5.2.2 页面布局的介绍 easyui-layout.jsp
以后不管用哪个UI框架,第一步,都需要引入人家自己的js和css样式。
easyui-layout.jsp 这个jsp就是用来定义页面布局的一个demo
第18行,第19行的data-options是 这个EasyUI框架提供的功能。
所以,我们要想用,就要遵守人家的规定。人家怎么规定,我就怎么用这个属性。
“region” :方位。 上北下南,左西右东。就是规定本div位于整合页面的哪里。
5.2.3 页面树形结构 easyui-tree.jsp
在任何UI框架中,要想展现树形结构,都离不开两个标签 :ul 和 li
li标签中的内容是无序的。
加上这个class=“easyui-tree” 才能成功展现树形结构。
要想展现“树”,就是< ul >和<li>的嵌套
5.2.4 选项卡技术 easyui-tab.jsp
在jsp中如果去掉这的if条件,那么点击几次“商品新增”,右面就会产生几个“商品新增”选项卡。
而加上这个if条件,意思就是“如果存在这个选项卡”,再次点击时这个选项卡就会被选中,而不是再新增一个。
iframe这个标签是画中画效果,相当于引入另一个页面
eg:点击“商品新增”后,在右侧显示“百度地图”
是不是很amazing。
这东西应急还行。
真格的时候 还得去调用百度地图对外的接口。
5.3 通用页面跳转
所谓的“通用页面跳转”的意思就是:
在点击这几个按钮时,在index.jsp中设置的是,客户端向服务器请求这3个地址。
通常情况下,客户端的每个请求,服务器的controller都会为之创建一个方法去接。
但有没有一个通用的一个方法,去接这些长得差不多的url请求呢?
那就用到restFul风格。
最大的功臣就是@PathVariable这个注解。
这个注解的作用就是,把客户端传过来的地址用{moduleName}来接收。再赋值给方法中的变量名 moduleName。
而方法结束后,会return 回相应的页面。
总结1:如果需要获取客户端发送的url地址请求中的参数时,则可以使用RestFul风格来实现。
总结2:可以按照客户端发送的请求的类型,让controller去执行特定的功能。
5.4 商品信息查询 SELECT
点击“商品查询”后,在右侧展现出商品信息
这些商品信息是服务端从数据库中查询出来的(按照一定的条件:第几页page,每页最多显示几条数据rows)。
5.4.1 表格的入门案例
通过一个demo来理解表格数据的呈现
data-options 就是当前这个EasyUI框架的一个属性标识符。
在data-options后面,我可以写很多我需要的属性。
在EasyUI中,它对ajax请求进行了动态的封装。
看到url这个属性信息,在EasyUI内部解析时,它就是一个ajax请求。
fitColumns:true表示自动适应,singleSelect:true 表示选中单个。
pagination:true表示这个表格有没有分页功能
客户端发送了ajax请求,服务器端最后会将数据响应回来。
那么是怎么将数据回显到页面上的呢?
就是通过这个,field后的code,name,price。这里的数据信息会和url请求得到的数据相绑定。
这个就是服务器响应回来的数据。
而服务器响应的数据 是怎么 和页面表格中的标签一一对应上的呢?
这两个地方的名字要对应上,要不然页面中的表格中会显示不出数据
5.4.2 关于JSON字符串的说明
这里的total和rows的数据肯定不是我手动一行一行敲上去的。
是服务器端将响应的结果绑定到一个VO对象上,再将这个VO对象封装成JSON格式的字符串,返回给客户端的。
5.4.2.1 什么是JSON
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。
官网:https://www.json/json-zh.html
5.4.2.2 JSON有几种格式
两种。
1.Object格式
eg:
{"id":"1","name":"张三","age":"888"}
2.数组格式
eg:
[ '11','22','33','44','4567' ]
拓展.嵌套格式的JSON
eg:
5.4.3 根据上表的JSON字符串,创建VO对象 ------jtmon中
//这个对象是根据客户端的请求分析出来,客户端想要的Json对象中的两个属性,1个是total,1个是rows
//total就是一个整数,rows是一个嵌套的JSON,是一堆“code”,“name”,“price”组成的JSON,又组成的数组。
//见webapp/easy-ui/datagrid_data.json
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class EasyUITable {
private Integer total; //一共有多少数据
private List<Item> rows; //每页要显示的Item们
}
当一个对象(object)需要在服务器与服务器之间进行传递时,(其实是将对象(object)转成字节数组,在服务器之间传递),必须要实现序列化接口。
JSON的本质是“字符串”,它不是对象(object)。所以在http协议中,字符串可以直接传递,不用转成字节数组。
所以在传输JSON时不需要将其序列化。
回忆知识点:序列化 与 反序列化
概念:序列化,就是将内存中的数据按照特定的规律,以字节的形式(0,1)进行排列组合,进行标识,通过IO流的形式将这些数据保存到磁盘中。
反序列化,将磁盘中的以字节形式(0,1)存储的数据,按照特定的规律进行解码,再通过IO流的形式存回内存中。
用途:就是 传输数据。(根据 类名称 属性/类型 属性值 将其重新组装回原来那个对象的样子)
5.4.4 商品列表页面展现 item-list.jsp -------------- 前端JS
<table class="easyui-datagrid" id="itemList" title="商品列表"
data-options="singleSelect:false,fitColumns:true,collapsible:true,pagination:true,url:'/item/query',method:'get',pageSize:20,toolbar:toolbar">
<thead>
<tr>
<th data-options="field:'ck',checkbox:true"></th>
<th data-options="field:'id',width:60">商品ID</th>
<th data-options="field:'title',width:200">商品标题</th>
<th data-options="field:'cid',width:100,align:'center',formatter:KindEditorUtil.findItemCatName">叶子类目</th>
<th data-options="field:'sellPoint',width:100">卖点</th>
<th data-options="field:'price',width:70,align:'right',formatter:KindEditorUtil.formatPrice">价格</th>
<th data-options="field:'num',width:70,align:'right'">库存数量</th>
<th data-options="field:'barcode',width:100">条形码</th>
<th data-options="field:'status',width:60,align:'center',formatter:KindEditorUtil.formatItemStatus">状态</th>
<th data-options="field:'created',width:130,align:'center',formatter:KindEditorUtil.formatDateTime">创建日期</th>
<th data-options="field:'updated',width:130,align:'center',formatter:KindEditorUtil.formatDateTime">更新日期</th>
</tr>
</thead>
</table>
运行的规则:
在数据库中先按照分页信息 查询对应的Item信息,只返回这一部分Item信息给客户端。
即:
按照分页的规则,(比如,查第1页,显示20条),就从数据库中查出第1页,前20条的数据,返回给客户端;
想查 第2页,也显示20条,就从数据库中 再查出第2页 中 20条数据的记录,返回给客户端。
(绝对不是从数据库中把所有的数据都查出来再在客户端做分页显示)
5.4.4.1 客户端发送的请求的URL是什么,参数是什么,请求方式是什么?
打开主页后,按F12,点进NetWork sheet。
再点击“查询商品”,可见
状态是404,说明没找到url请求所对应的资源。
(那是肯定的,客户端老师已经提前写好了,而服务器端我还没有写呢)
客户端请求的URL是:http://localhost:8091/item/query?page=1&rows=20
由于我查询时 是带着这的分页信息查的,所以会将查询请求的参数拼接到url后面。
而当我改变分页信息中的任何一个参数(page,rows)如:
客户端发送的请求的url后拼接的参数也会相应的跟着变化。
5.4.5 商品列表页面展现 ItemController -----------服务器端Controller
客户端向服务端发送了Ajax请求
于是我要在Controller中写一个方法去接收请求。
@RestController //返回的是Json字符串 注意是RestController在这被坑了好久!
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务需求:客户端点击“查询商品”后,分页显示出商品信息
* 客户端请求的url:http://localhost:8091/item/query?page=1&rows=20
* 客户端提交请求的方式:get
* 客户端传过来的参数:
* @param page 第几页
* @param rows 每页最多显示多少行Item数据
* @return 服务器给的返回值结果就是 当前页(第1页) 要显示的(20条)Item数据(EasyUITable)
*/
@RequestMapping("/query")
public EasyUITable findItemByPage(Integer page,Integer rows){
return itemService.findItemByPage(page,rows);
}
引申:开发的顺序
1.自下而上:Mapper----->Service-------->Controller-------->前端JS页面 eg:DB项目
2.自上而下:分析前端JS页面的需求--------------->Controller---------->Service----->Mapper eg: JT项目
5.4.6 商品列表页面展现 ItemService --------------服务器端Service
当Controller中写完findItemByPage()方法后,会飘红色警告,根据提示,idea会自动帮我在ItemService中生成findItemByPage()方法。
再转战到ItemServiceImpl中,ALT+SHIFT+P,会出现如下框
就能自动生成findItemByPage()的重写方法了。
我在业务层要做的就是,接收到controller层发送过来的两个参数:page和rows后
调用ItemMapper去完成与数据库的交互环节,具体一点就是,根据page和rows,从数据库中查询出相应的信息,封装成EasyUITable对象,将来返回给Controller层。
这里,我用两种方式达成这个目标。
1.传统的手写Sql方式
@Override
public EasyUITable findItemByPage(Integer page, Integer rows) {
/**方式一:手写分页查询的service代码,和sql语句
* sql:select * from tb_item limit 起始位置(startIndex), 每页最多显示的条数(rows)
* 起始位置:startIndex 的理解
* 查询第1页:page=1 rows=20
* sql: select * from tb_item limit 0,20
* 查询第2页:page=2 rows=20
* sql: select * from tb_item limit 20,20
* 查询第3页:page=3 rows=20
* sql: select * from tb_item limit 40,20
*/
//1.计算起始位置的号startIndex
int startIndex = (page - 1) * rows; // 第2步
//2.通过itemMapper去调用我手写的findItemByPage()方法,去数据库中查询出,当页要显示的Item们的信息
List<Item> itemList = itemMapper.findItemByPage(startIndex, rows); //这个方法是我自定义的,在ItemMapper中有对应的方法 第1步
//至此,已经得到了EasyUITable对象中的List<Item>的值,还差total
//现在的目标,从数据库中查询出一共有多少条数据,即为total属性赋值
Integer total = itemMapper.countItems(); //这个方法是我自定义的,在ItemMapper中有对应的方法 // 第3步
//至此,total总数也从数据库中查询出来了。
//这样就可以把List<Item>和total封装好,返回给controller了。把它俩赋值给一个EasyUITable对象,返回给controller。
//新建个EasyUITable对象
EasyUITable easyUITable = new EasyUITable(); // 第4步
easyUITable.setRows(itemList); // 第5步
easyUITable.setTotal(total); // 第6步
return easyUITable; // 第7步
}
2.借助于MyBatisPlus中的API
@Override
public EasyUITable findItemByPage(Integer page, Integer rows) {
/**方式二:通过MP中的API去查询出List<Item>和total
* 注意:利用MP进行分页查询时 是有条件的:
* 在进行分页查询时,MP必须添加配置类
*/
//通过MP中的selectPage方法去查出当前页要显示的Item们
//由于selectPage方法中的第一个参数是IPage类型的,所以我要先new一个IPage类型的对象出来,IPage是个接口
//由Page的底层源码可知,new对象时,可以传2个参数:(第几页:page,每页显示多少条数据:rows),正好就是controller将要传过来的那2个参数
IPage<Item> iPage = new Page<>(page,rows); //第2步
//当我把iPage和queryWrapper这两个参数传给MP中的selectPage()后,MP会自己执行分页操作,将需要的数据进行封装。
//我的查询条件是啥?new一个queryWrapper,来定义一下
QueryWrapper<Item> queryWrapper = new QueryWrapper<>(); //第5步
//根据修改时间updated降序排列
queryWrapper.orderByDesc("updated"); //第6步
//iPage身上绑定着selectPage方法查询出的所有分页信息
iPage = itemMapper.selectPage(iPage,queryWrapper); //第1步
//调取出iPage中的total
int total = (int)iPage.getTotal(); //第3步
//调取出iPage中符合条件的记录
List<Item> itemList = iPage.getRecords(); //第4步
EasyUITable easyUITable = new EasyUITable(total,itemList); //第7步
easyUITable.setTotal(total).setRows(itemList); //第8步
return easyUITable; //第9步
}
5.4.7 商品列表页面展现 ItemMapper --------------服务器端Mapper------Service中的方式1
如果在service中 用的是第一种手写Sql的方法,那么还需要有ItemMapper这个数据层。
//@Mapper //由于在主启动类上加了@MapperScan("com.jt.mapper")注解,这里就不用写@Mapper注解了
public interface ItemMapper extends BaseMapper<Item>{
//查询出当前页要显示的Item们 先排序 再分页
@Select("select * from tb_item order by updated desc limit #{startIndex},#{rows}")
List<Item> findItemByPage(int startIndex, Integer rows);
//查询出总共有多少条数据,total
@Select("select count(*) from tb_item")
Integer countItems();
}
5.4.8 商品列表页面展现------------Service中的方式2
如果是调用MP的API实现的结果查询,还需要编写一个配置类。
这个配置类的模板可以在MP的官网上找到。(快速入门-------核心功能--------分页插件)
package com.jt.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//1.标识一下,这是一个配置类。写配置类的作用就是,给需要交给Spring管理的Bean对象(eg:PaginationInterceptor)进行一些配置
@Configuration //这个注解通常与@Bean注解一起使用
public class MybatisPlusConfig {
//2.将对象交给Spring管理
/**spring是怎么管理对象的呢?
* 会把对象放在一个超大的Map中。map<K,V>
* 其中K就是方法名,V就是实例化之后的对象
* map<paginationInterceptor,paginationInterceptor>
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
至此 商品信息的查询功能就已完成 90%
在主界面点击“查询商品”后,在右侧就能显示出Item的数据了。
5.4.9 商品分类信息(叶子类目)的回显
注意,“叶子类目”那一列的信息还没有。(所谓的“叶子类目”,就是这个商品所属的类的名字)
接下来我就要想办法把“叶子类目”中的信息展现出来。
在解决这个问题之前,还要再仔细研究一下Item这个POJO
5.4.9.1 ItemPojo的说明
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.experimental.Accessors;
@JsonIgnoreProperties(ignoreUnknown=true) //表示JSON转化时忽略未知属性
@TableName("tb_item")
@Data
@Accessors(chain=true)
public class Item extends BasePojo{
@TableId(type=IdType.AUTO) //主键自增
private Long id; //商品id
private String title; //商品标题
private String sellPoint; //商品卖点信息
private Long price; //商品价格 Long > double 算算数时Long算得更快,并且double存在精度问题(算得不准)
//如果一个商品的价格是9.58元,它在存储到数据库中时会*100。数据库在将数据返回时,会再÷100。
private Integer num; //商品数量
private String barcode; //条形码
private String image; //商品图片信息 1.jpg,2.jpg,3.jpg
private Long cid; //表示商品的分类id
private Integer status; //1上架,2下架
//为了满足页面调用需求,添加get方法
public String[] getImages(){
return image.split(",");
}
}
问题1:price为啥是Long类型?
分析:通常price都是double类型,比如:3.58元。
而在程序中,通常会涉及到商品价格的计算。
一般情况下,整数的计算 速度 会 快于 小数的计算速度。
并且double类型的数在计算时,也会存在精度问题。(eg:0.0000000000001+0.999999999999999 会无限趋近于1 而不能等于1)
所以这里的price 选择Long类型。
问题2:客户端那边用户输入的价格是有小数的,eg:99.88元。
而我服务器这边用price接收时,由于price是Long类型,会自动把小数点部分舍掉,就成了99元!!!这不能忍!!!
所以:在前端JS代码中item-add.jsp中有这么一个方法:
客户端把用户输入的带小数点儿的钱数 (eg:99.88元)会*100,转化成分 (9988分),再传给服务器端。
而这时服务器端再用Long类型的price去接收时,就不会有误差了。
所以最后存到数据库中的钱数 的单位也是分。
问题3:image 代表的是商品的图片,那数据库中存的是图片本身吗?
答:不是。数据库中如果真存这种图片,会很占地方,查询的效率也会非常慢。
所以数据库中存储的是图片的 地址。
并且一个商品中通常会有多个图片。
这些图片就以字符串的形式存储在数据库中。(eg: 1.jpg,2.jpg,3.jpg…)
由于用“,”进行了分隔,以后就可以通过数组取值的方式,[0]就代表第1张图片,[1]代表第2张图片…
5.4.9.2 商品价格的格式化(从数据库中“分”-------->页面上的“元”)
由5.4.9.1中的问题2中可知,数据库中price都是以“分”来存储的。
而页面上的数据展现给用户时是正常按“元”来展现的。
是怎么做到的呢?
原来在前端页面item-list中,客户端在接收服务器传回来的price时,price上还有这么一个属性:formatter:KindEditorUtil.formatPrice
若我把formatter:KindEditorUtil.formatPrice这个属性去掉,页面上呈现的price就是数据库中以“分”保存的形式。
可以看出 这一切都是KindEditorUtil.formatPrice的功劳。
formatter属性是EasyUI中专门 负责 格式化数据的函数。通过调用.js,将服务器传回来的结果 展现在页面上。
那么.formPrice这个方法在哪里呢?
答:在jt-manage------webapp------js-------common.js中
这个方法就是:客户端接收到服务器传回的price后,要÷100,再保留2位小数。就是以“元”为单位了。
formatPrice这个方法有2个参数:当前这个标签的值,也就是“价格”的值。(val)
当前这个标签的行号。(row)(这个row是这个方法规定要传的一个参数,我如果用不到它,就可以不管它。)
那么问题来了:
item-list.jsp 是怎么引用到 common.js中的方法的呢?它俩又不在同一个目录下,(⊙o⊙)…
问题的关键在于index.jsp !!!!!
啥??跟它有毛关系???
因为我最先登录进系统时,显示的主页就是index.jsp。
而index.jsp中引入了common-js.jsp。
而common-js.jsp中引入了common.js。
所以,也就是说,index.jsp这个整体可以引用common.js中的方法。
而item-list.jsp是index的一部分,相当于儿子。他爸爸index能用common.js,他item-list也就能用common.js。
5.4.9.3 商品状态的格式化(1:上架 2:下架)
由于商品的状态 可能是“上架”“下架”,过一段时间可能又叫别的名字了,如“正常”,“异常”…
数据库中最好不要这么存总是变的字符串信息。
如果以数字的形式代表 两种状态,将数字存到数据库中,就很好。
private Integer status; //1上架,2下架
在前端的item.jsp中也用了formatter:KindEditorUtil.formatItemStatus来格式化“状态”。
在common.js中是这样定义的
val就是服务器传回来的商品的status值。(1或2)
在页面中显示如下
5.4.9.4 “叶子类目”信息的展现----需求引出
可见,叶子类目中 也有这个属性 调用这个方法
formatter:KindEditorUtil.findItemCatName
如果我把这个去掉,页面上显示如下;
“叶子类目”中展示了一些数字,这些数字就是 本商品对应的分类类目的id。
所以,现在的需求就是:
需要通过格式化的函数formatter:KindEditorUtil.findItemCatName ,动态地获取到这些id对应的分类类目的名称,并显示出来。
5.4.9.5 “叶子类目”信息的展现 (ajax同步请求) --------------前端JS
利用formatter:KindEditorUtil.findItemCatName这个函数的调用,向后端发送ajax请求,获取商品分类的名称。
在common.js中定义了indItemCatName这个方法
注意:这里先用ajax 发同步请求的方式。
稍后 会再用 ajax 发异步请求的方式,即async:false
5.4.9.6 “叶子类目”信息的展现 ItemCat----------------服务器端POJO
由JT项目总体的物理模型图可知,商品的分类信息不在商品表中,它在tb_item_cat表中。
所以,要为这个tb_item_cat 创建一个pojo对象,用于封装 商品分类的ID与商品名称的关系。
注意,这个ItemCat的pojo要写在jt-common中。
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("tb_item_cat")
@Data
@Accessors(chain=true)
public class ItemCat extends BasePojo {
@TableId(type= IdType.AUTO)
private Long id; //商品分类的id
private Long parentId; // 对应的父级商品分类的id ,父类目ID=0时,代表的是一级的类目
private String name; // 商品分类的名称
private Integer status; //商品分类信息的状态 (正常 / 已删除)
private Integer sortOrder; //排列序号,表示同级类目的展现次序,如数值相等则按名称次序排列。取值范围:大于零的整数
private Boolean isParent; //该类目是否为父类目,1为true,0为false (在tb_item_cat表中,这项对应的字段是is_parent 类型是tinyint(1),以后看到tinyint(1)类型,在pojo中就用Boolean与之对应)
//created 和 updated在继承的BasePojo中
}
5.4.9.7 “叶子类目”信息的展现(ajax同步请求) ItemCatController----------------服务器端Controller
根据前端的JS代码可知,它发送的是ajax 同步请求
url:"/item/cat/queryItemName"
data:{itemCatId:val}
所以我要新建一个ItemCatController,里面要这么写
@RestController
@RequestMapping("/item/cat") //只写请求的业务名
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/**
* 分析业务:通过itemCatId获取商品分裂的名称
* 1.url地址:url:"/item/cat/queryItemName"
* 2.参数:data:{itemCatId:val}
* 3.返回值:商品分类的名称 String
*
*/
@RequestMapping("/queryItemName")
public String findItemCatName(Long itemCatId){ //由于tb_item_cat表中的id是bigint类型,所以这里我要用Long类型来接收
return itemCatService.findItemCatNameById(itemCatId);
}
}
5.4.9.8 “叶子类目”信息的展现(ajax同步请求) ItemCatService----------------服务器端Service
由于Controller层派发了任务,所以我又要新建一个ItemCatService,里面要这么写:
public interface ItemCatService {
String findItemCatNameById(Long itemCatId);
}
5.4.9.9 “叶子类目”信息的展现(ajax同步请求) ItemCatServiceImpl----------------服务器端ServiceImpl
ItemCatService中接收到了任务,真正干活的还是ItemCatServiceImpl。
所以我又要新建一个ItemCatServiceImpl。
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
@Override
public String findItemCatNameById(Long itemCatId) {
return itemCatMapper.selectById(itemCatId).getName();
}
}
5.4.9.10 “叶子类目”信息的展现(ajax同步请求) ItemCatMapper----------------服务器端Mapper
有了ItemCat这个POJO对象,就要通过这个对象去数据库中查结果了,所以就要写ItemCatMapper了
package com.jt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.pojo.ItemCat;
public interface ItemCatMapper extends BaseMapper<ItemCat> {
}
由于我查数据库用的是MP,所以继承 BaseMapper ,我就可以不用手写sql了。
至此,客户端发(ajax同步请求),展现“叶子类目”的需求已达成,页面展现如下:
5.4.9.11 拓展:“叶子类目”信息的展现 (ajax异步请求) --------------前端JS--------Ajax请求嵌套问题
思考:
当前端改发Ajax异步请求时,会发生什么?
不知道会发生什么,试试看先。
服务器端的代码不动,只改客户端这里。
点进jt页面后发现,“叶子类目”又没了!!!!
这就是前端经常遇到的 Ajax请求嵌套问题
当我点完“查询商品”时,客户端一共向服务端发送了2次Ajax请求:
第一次是5.4.4.1中,发送的查询整个页面信息Item
第二次是5.4.9.11中,查询“叶子类目”ItemCat
当第一个查询Item的Ajax请求发出后,服务器返回的Item信息,会一行一行地显示在页面上。
eg:当查询第一行Item信息的Ajax请求发出后,服务器返回第一行Item的值,并刷新页面,把值展现在页面上。而Item的值中是没有包含“叶子类目”ItemCat的值的。
客户端又发送查询第一行ItemCat的请求,服务器返回第一行ItemCat的值。
但注意查询Item第2行,第3行的Ajax请求也在源源不断的发给着服务器,服务器不会等着把上一行的ItemCat返回并刷新页面后再去接查询Item第2行,第3行…的Ajax请求。
所以就会造成这种局面:
Item的信息展现在页面后,ItemCat的信息虽然已经由服务器返回来了,但没来得及刷新显示出来,也就是只是欠缺一次“刷新”。
所以5.4.9.11中,查询“叶子类目”ItemCat,这个Ajax请求,只能是发“同步”请求。
总结,如果在页面中涉及到Ajax请求嵌套的问题,通常的解决方法是:将内层的Ajax设置为同步模式,就可以正确展现数据了。
5.5 新增商品 INSERT
5.5.1 铺垫小案例
5.5.1.1 弹出框效果
当我点击“选择类目”按钮时,在当前页面上就弹出一个框
这个效果是通过EasyUI框架中给定的功能实现的,(各大UI框架差不多都有这个功能)。
在本例中,webapp----easy-ui----easyui-5-window.html就是一个弹出框功能的小demo。
页面效果如图:
点击“搜索”,“退出系统”后也会弹出相应的框。
easyui-5-window.html中的代码如下:
<!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">
$(function(){
//jQuery中的编程方式:函数式编程 (function(){})
$("#btn1").bind("click",function(){
//.window()是EasyUI框架提供的函数,功能是弹出一个框
$("#win1").window({
title:"弹出框",
width:400,
height:400,
modal:true //这是一个模式窗口,只能点击弹出框,不允许点击别处
})
})
$("#btn3").click(function(){
alert("关闭");
$("#win2").window("close");
});
/*定义退出消息框 */
$("#btn4").click(function(){
$.messager.confirm('退出','你确定要退出吗',function(r){
if (r){
alert("确认退出");
} else{
alert("点错了!");
}
});
})
/*定义消息提示框 */
$.messager.show({
title:'My Title',
msg:'郑哥你都胖成一个球了,圆的',
timeout:5000,
height:200,
width:300,
showType:'slide'
});
})
</script>
</head>
<body>
<h1>Easy-弹出窗口</h1>
<!--主要展现弹出框 -->
<a id="btn1" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">搜索</a>
<div id="win1"></div>
<!--定义弹出窗口 -->
<div id="win2" class="easyui-window" title="My Window" style="width:600px;height:400px" data-options="iconCls:'icon-save',modal:true">
我是一个窗口
<a id="btn3" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-back'">关闭</a>
</div>
<div style="float: right">
<a id="btn4" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-cancel'">退出系统</a>
</div>
</body>
</html>
5.5.1.2 商品分类数据结构分析
一般电商网站的 商品分类信息 都是分3级。级与级之间是父级与子级的关系。
那么在数据库中是怎么存储这种关系的呢?
答:涉及到父级与子级的关系时,一般用表中的parent_id字段进行关联。POJO中对应的属性名就是parentId
查询1级商品分类信息 其parent_id=0 SELECT
∗
\ast
∗ FROM tb_item_cat WHERE parent_id=0;
查询2级商品分类信息 其parent_id=1 SELECT
∗
\ast
∗ FROM tb_item_cat WHERE parent_id=1;
查询3级商品分类信息 其parent_id=2 SELECT
∗
\ast
∗ FROM tb_item_cat WHERE parent_id=2;
5.5.1.3 商品分类信息树形结构的展现
在本例中,webapp----easy-ui----easyui-7-tree.html就是一个 树形结构 的小demo。
页面展示如下:
Ztree ,treegride,Bootstrap中都提供了这种树形结构的实现方式。
easyui-7-tree.html中代码如下:
<!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数据 看见url 就说明这个发的是一个ajax请求
method:"get", //请求方式 get
animate:true, //表示显示折叠端口动画效果
checkbox:true, //表述复选框
lines:true, //表示显示连接线
dnd:true, //是否拖拽
onClick:function(node){ //添加点击事件
//控制台
console.info(node);
}
});
})
</script>
</head>
<body>
<h1>EasyUI-树形结构</h1>
<ul id="tree"></ul>
</body>
</html>
表示树形结构的时候,通常用的都是<ul>和<li>
.tee()方法就是EasyUI框架提供的一个生成树形结构的方法。
而url中请求的tree.json就在
点开tree.json后:
[
{
"id":"1",
"text":"英雄联盟",
"iconCls":"icon-save",
"children":[
{
"id":"4",
"text":"沙漠死神"
},{
"id":"5",
"text":"德玛西亚"
},{
"id":"6",
"text":"诺克萨斯之手"
},
{
"id":"7",
"text":"蛮族之王"
},
{
"id":"8",
"text":"孙悟空"
}
],
"state":"open"
},{
"id":"2",
"text":"王者荣耀",
"children":[
{
"id":"10",
"text":"阿科"
},{
"id":"11",
"text":"吕布"
},{
"id":"12",
"text":"陈咬金"
},{
"id":"13",
"text":"典韦"
}
],
"state":"closed" //决定着 这个节点 一开始 是“开着的” 还是“关闭的”
},
{
"id":"3",
"text":"吃鸡游戏",
"iconCls":"icon-save"
}
]
可见这个json整体是一个数组,将来我用一个List<>封装一下里面的各个子元素即可,就不用再给它弄一个pojo对象了。
数组内的子元素是一个一个的对象,总体来说,对象们差不多都有“id”和“text”两个属性。所以将来它对应的pojo中的属性肯定要有id和text。
5.5.2 新增商品 --封装EasyUITree 这个VO对象
在点击“选择类目”按钮后,要展现出选择类目的弹框,而弹框中要显示的就是 商品分类信息的全部的树结构EasyUITree。
所以我要弄一个EasyUITree对象,在它里面封装一些信息。
封装什么信息呢?
由上面的demo可知,一个tree是由很多节点组成的,而一个节点有3个属性:
节点的id
节点的名称 text
节点的状态(开启/关闭) state
所以EasyUITree的VO要这么写:
//这个对象的主要目的是为了展现树形结构的数据。
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class EasyUITree implements Serializable {
//由于在本例中我是用EasyUI这个框架中的树功能,人家的JS中规定树的节点就叫id,节点的名称就叫text,节点的状态就叫state。
//我不能起别的名字,所以这里这3个变量名只能写id,text,state
private Long id; //商品分类的Id信息
private String text; //商品分类的name
private String state; //节点是闭合的形式还是展开的形式。
//由是否为父级决定的。如果是父级,就是closed。如果是子级,就是open。
}
5.5.3 新增商品 item-add.jsp —点击“选择类目”后,展现商品类型的树形结构 ---------前端JS
item-add.jsp中代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<link href="/js/kindeditor-4.1.10/themes/default/default.css" type="text/css" rel="stylesheet">
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/kindeditor-all-min.js"></script>
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/lang/zh_CN.js"></script>
<div style="padding:10px 10px 10px 10px">
<form id="itemAddForm" class="itemForm" method="post">
<table cellpadding="5">
<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>
<tr>
<td>商品标题:</td>
<td><input class="easyui-textbox" type="text" name="title" data-options="required:true" style="width: 280px;"></input></td>
</tr>
<tr>
<td>商品卖点:</td>
<td><input class="easyui-textbox" name="sellPoint" data-options="multiline:true,validType:'length[0,150]'" style="height:60px;width: 280px;"></input></td>
</tr>
<tr>
<td>商品价格:</td>
<td><input class="easyui-numberbox" type="text" name="priceView" data-options="min:1,max:99999999,precision:2,required:true" />
<input type="hidden" name="price"/>
</td>
</tr>
<tr>
<td>库存数量:</td>
<td><input class="easyui-numberbox" type="text" name="num" data-options="min:1,max:99999999,precision:0,required:true" /></td>
</tr>
<tr>
<td>条形码:</td>
<td>
<input class="easyui-textbox" type="text" name="barcode" data-options="validType:'length[1,30]'" />
</td>
</tr>
<tr>
<td>商品图片:</td>
<td>
<a href="javascript:void(0)" class="easyui-linkbutton picFileUpload">上传图片</a>
<input type="hidden" name="image"/>
</td>
</tr>
<tr>
<td>商品描述:</td>
<td>
<textarea style="width:800px;height:300px;visibility:hidden;" name="itemDesc"></textarea>
</td>
</tr>
<tr class="params hide">
<td>商品规格:</td>
<td>
</td>
</tr>
</table>
<input type="hidden" name="itemParams"/>
</form>
<div style="padding:5px">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="submitForm()">提交</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="clearForm()">重置</a>
</div>
</div>
<script type="text/javascript">
var itemAddEditor ;
$(function(){
//和form下的itemDesc组件绑定
itemAddEditor = KindEditorUtil.createEditor("#itemAddForm [name=itemDesc]");
KindEditorUtil.init({fun:function(node){
KindEditorUtil.changeItemParam(node, "itemAddForm");
}});
});
function submitForm(){
//表单校验
if(!$('#itemAddForm').form('validate')){
$.messager.alert('提示','表单还未填写完成!');
return ;
}
//转化价格单位,将元转化为分
$("#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);
/*$.post/get(url,JSON,function(data){....})
?id=1&title="天龙八部&key=value...."
*/
//alert($("#itemAddForm").serialize());
$.post("/item/save",$("#itemAddForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','新增商品成功!');
}else{
$.messager.alert("提示","新增商品失败!");
}
});
}
function clearForm(){
$('#itemAddForm').form('reset');
itemAddEditor.html('');
}
</script>
注意:选择类目 的标签中有easyui-linkbutton selectItemCat这么两个属性。
其中selectItemCat这个属性的具体定义是在common.js中。
// 初始化选择类目组件
initItemCat : function(data){
$(".selectItemCat").each(function(i,e){//i= index 下标,e:element:元素
var _ele = $(e);
if(data && data.cid){
_ele.after("<span style='margin-left:10px;'>"+data.cid+"</span>");
}else{
_ele.after("<span style='margin-left:10px;'></span>");
}
_ele.unbind('click').click(function(){
$("<div>").css({padding:"5px"}).html("<ul>")
.window({
width:'500',
height:"450",
modal:true,
closed:true,
iconCls:'icon-save',
title:'选择类目',
onOpen : function(){ //当窗口打开后执行
var _win = this;
$("ul",_win).tree({
url:'/item/cat/list',
animate:true,
onClick : function(node){
if($(this).tree("isLeaf",node.target)){
// 填写到cid中
_ele.parent().find("[name=cid]").val(node.id);
_ele.next().text(node.text).attr("cid",node.id);
$(_win).window('close');
if(data && data.fun){
data.fun.call(this,node);
}
}
}
});
},
onClose : function(){
$(this).window("destroy");
}
}).window('open');
});
});
},
其中onOpen属性就是在实现 商品分类信息树,也就是EasyUITree,的展现。
看到了url:’/item/cat/list’ 说明这是在发送ajax请求,当服务器返回EasyUITree对象时,客户端的页面上就可以显示出这个树的样子。
5.5.4 新增商品 : 点击“选择类目”后,展现商品类型的树形结构 ItemCatController---------服务端Controller
前端发送来了 展现EasyUITree的请求,那我Controller就得接住。
这样,我在ItemCatController中要添加一个方法。
/**
* 分析业务:实现商品分类的树结构的展现
* 1.url地址:http://localhost/item/cat/list
* 2.参数: parentId 查询商品分类菜单
* 3.返回值结果: List<EasyUITree>
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(Long id){
//当初始时 树形结构 还没加载呢,不会传递ID,所以此时ID为null,所以此时parentId为0
Long parentId = (id==null)?0:id; //异步树控件加载
return itemCatService.findItemCatList(parentId);
}
知识点:这里用到了 异步树控件加载
jQuery中 EasyUI的API中 对异步树控件加载的解释:
树控件读取URL。子节点的加载依赖于父节点的状态。当展开一个封闭的节点,如果节点没有加载子节点,它将会把节点id的值作为http请求参数并命名为’id’,通过URL发送到服务器上面检索子节点。
简单总结:
(前提:得在页面上先至少展现出父级节点)当点击父级类目的节点时,会向后台继续传递该节点的id,以查询它下一级的类目的信息。
每点一次父级类目的名字,就进行了一次查询。
5.5.5 新增商品 : 点击“选择类目”后,展现商品类型的树形结构 ItemCatService---------服务端Service
ItemCatController层发来了命令,我就得在ItemCatService去新建个findItemCatList()方法。
public interface ItemCatService {
String findItemCatNameById(Long itemCatId);
List<EasyUITree> findItemCatList(Long parentId);
}
5.5.6 新增商品 : 点击“选择类目”后,展现商品类型的树形结构 ItemCatServiceImpl---------服务端Service
所以,在ItemCatServiceImpl中要实现EasyUITree树的对象的数据获得了。
增加方法:
/**
* 思路:
* 1.通过parentId查询数据库信息,返回值结果类型是List<ItemCat>
* 2.将ItemCat信息转化为EasyUITree对象
* 3.返回的是List<EasyUITree>
* @param parentId
* @return
*/
@Override
public List<EasyUITree> findItemCatList(Long parentId) {
//1.根据父级分类id,查询数据
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",parentId);
List<ItemCat> catList = itemCatMapper.selectList(queryWrapper);
//2.将上一步查询到的数据catList进行转化,挑选想要的信息,封装成EasyUITree,添加到集合treeList中
List<EasyUITree> treeList = new ArrayList<>();
for (ItemCat itemCat: catList){
Long id = itemCat.getId();
String text = itemCat.getName();
//是父级,节点就打开,否则节点关闭
String state = itemCat.getIsParent()?"closed":"open";
EasyUITree easyUITree = new EasyUITree(id,text,state);
//3.将众多easyUITree对象 封装到treeList中
treeList.add(easyUITree);
}
return treeList;
}
至此,点击“选择类目”后,在弹出框中展示“商品分类的树结构”EasyUITree 功能完成!!!
客户端的页面显示如下:
5.5.7 商品新增:商品信息入库-------------------需求
在“新增商品”页面,需要做的主要有以下几点:
1.点“选择类目”后,展现个弹出框,在弹出框中展现商品类目的树形结构
2.“商品标题”,“商品卖点”,“商品价格”,“库存数量”,“条形码”均为Item信息,对应的是tb_item表。
3.“上传图片”功能先搁置,后面再实现。
4.商品描述为ItemDesc信息,对应的是tb_item_desc表
业务处理一般都会通过JSON串的形式告知客户端 程序是否完成了。通过一个VO对象来返回回执信息。
这个VO对象要封装的就是:业务是否正确 / 业务处理信息 / 业务处理后返回的数据
5.5.7.1 商品新增:Item信息的新增------封装SysResult 这个VO对象
无论是“新增”,“修改”还是“删除”,我在点完“提交”后,通常都需要弹出一个框,告诉我一下,我到底“新增”,“修改”,“删除”成功没有?
这个框中的信息通常包括:
status(状态,用数字表示,如200,201等)
msg(提示信息,如:服务调用成功)
但注意:页面上显示的这句话是在item-add.jsp中定义的
data(就是需要返回给客户端的数据,有时候是需要返回这个的)
所以就需要把这3个属性封装到一个VO对象中,本例中取名为SysResult。
由于SysResult这个对象是系统级别的,不仅jt-manage子系统会用到,如果有个别的jt子系统也可能会用到,所以要放在jt-common中。
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
/**
* 定义状态信息:
* 200:业务处理成功
* 201:业务处理失败
*/
private Integer status;
/*服务器返回的提示信息 比如:如果status为201时,异常,则在控制台中会显示对应的msg*/
private String msg;
/**服务器返回的业务数据 */
private Object data;
//写一些静态方法,方便后期别人就可以用这个对象.这些方法,告诉客户端,我服务器这边这次干活是成功了还是失败了。
//由于是static静态的,别人就可以直接调用
//服务器干活失败了
public static SysResult fail(){
return new SysResult(201,"调用服务器失败",null);
}
//服务器干活成功了
public static SysResult success(){
return new SysResult(200, "业务执行成功!",null);
}
//服务器干活成功了,并且把业务数据返回给客户端
public static SysResult success(Object data){
return new SysResult(200, "业务执行成功!",data);
}
}
5.5.7.2 商品新增:Item信息的新增------前端JS
暂时先只实现上半部Item信息的新增入库
由于后端的代码我还没写,前端的JS代码老师已经替我写好了。
此时在点击“提交”后,会报404
由此可知,客户端发送的Ajax请求的
url: http://localhost:8091/item/save
请求方式: POST
在页面中再往下翻,就能看见客户端提交ajax请求的参数
在item-add.jsp中也可以看到,客户端发送ajax请求的代码
在item-add.jsp中往上翻,可以看到“提交”标签的所有属性
显然,submitForm() 是老师自定义的一个点击事件方法,点击完“提交”之后,会触发这个方法。
具体这个方法是什么,需要看下面的定义:
function submitForm(){
//表单校验
if(!$('#itemAddForm').form('validate')){
$.messager.alert('提示','表单还未填写完成!');
return ;
}
//转化价格单位,将元转化为分
$("#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);
$.post("/item/save",$("#itemAddForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','新增商品成功!');
}else{
$.messager.alert("提示","新增商品失败!");
}
});
}
注意1:eval的作用是规定做算数运算。
eg: 通常 我输入 20*50 代表的就是 两个数字相乘 。
但在js中 我输入 20 * 50 ,js不一定觉得我输入的是数字,也可能是字符串,那么 字符串20 * 数字50 结果会是error
而用eval()括起来的,就能保证是数字了,即使是这样eval( “20” ) 这个依然会被解析为 数字 20。
注意2:$("#itemAddForm").serialize() 是ajax请求中发送的参数
ajax请求中 参数的提交有2种方式:
1.json方式 $.post("/item/save",{“key1”:“value1”,“key2”:“value2”},回调函数)
2.key1=value1&key2=value2 $.post("/item/save",key1=value1&key2=value2,回调函数)
但如果客户端一次性提交了几百个数据,我也不能写几百个key呀==!
针对这个需求,jQuery中提供了一个函数叫 serialize() ,作用就是将“#itemAddForm”中的数据,(本例中就是表单中所有的数据),封装成key1=value1&key2=value2&key3=value3…的形式,如图:
注意3:$.post("/item/save",$("#itemAddForm").serialize(), function(data){…
中回调函数的参数data就是服务器端返回的VO对象 SysResult ,方法中就是用SysResult对象的status属性值是不是200,201来判断的。
注意4:由于“新增商品”和“商品查询”是两个页面,它们是兄弟关系,所以“新增商品”后,不会直接自动去“商品查询”。
5.5.7.3 商品新增:Item信息的新增------ItemController-----后端Controller
在ItemController中添加对应的方法。
/**
* 业务需求:完成商品入库操作,返回系统vo对象SysResult
* 客户端发送的请求url:http://localhost:8091/item/save
* 参数:整个form表单中的数据,都在item对象中。
* 返回值:SysResult对象
*/
@RequestMapping("/save")
public SysResult saveItem(Item item){
itemService.saveItem(item);
return SysResult.success();
//由于我不能保证用户输入的数据能100%入库成功,服务器或者数据库闹一些幺蛾子呢~~~所以得try{}catch(){}一下
//但这是很古老的方式了,后来就用定义全局异常处理类的方式 代替了这个方法
// try{
// itemService.saveItem(item);
// return SysResult.success();
// }catch (Exception e){
// e.printStackTrace();
// return SysResult.fail();
// }
}
注意:这里用我自定义个全局异常处理类的方式,代替了try{}catch(){}的写法
全局异常处理类
全局异常处理的作用:
如果在每个方法中都添加异常处理的try{}catch(){},则会导致整个代码非常冗余啰嗦,机构混乱。
所以全局异常处理类就可以帮我统一管理程序中出现的异常。
全局异常处理的原理:
AOP中的异常通知的原理,在Spring中是利用AOP的方式实现的。
因为是“全局的”,所以应该写在jt-common中。
package com.jt.aop;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice //作用:标识我是一个通知方法,并且只拦截Controller层的异常,(Service层和Mapper层的异常抛给Controller层就行了),并且返回JSON
public class SysResultException {
//需要定义一个全局的方法,返回指定的报错信息。
//ExceptionHandler 配置异常的类型,这个例子中规定的是:遇到了运行时异常RuntimeException,就执行这个exception方法
@ExceptionHandler(RuntimeException.class)
public Object exception(Exception e){
e.printStackTrace();
return SysResult.fail();//SysResult.fail()会显示201,调用服务器失败。即告诉用户,我服务器这边出错了。
//但在页面上显示的信息是:新增商品失败! 是因为在item-add.jsp中第115行规定的。
}
}
5.5.7.4 商品新增:Item信息的新增------ItemService-----后端Service
在ItemService中新增方法,来接收ItemController中新的任务
//===增加:点击商品管理中的“新增商品”后,按要求输入好商品信息,点击“提交”后,将商品信息保存到数据库中
void saveItem(Item item);
5.5.7.5 商品新增:Item信息的新增------ItemServiceImpl-----后端ServiceImpl
实现新任务:
注意1:当在页面上点击了“提交”后,客户填写了Item的这几个信息,差不多跟Item的pojo的属性对应着呢。
但 仔细观察后发现,id和status没有传。
id在入库后会主键自增,不需要传。
而新增一个商品,提交后,一般状态都是是“上架”,所以可以在这里给status一个默认值 1 。
这个与common.js中的这里是对应的
//===增加:点击商品管理中的“新增商品”后,按要求输入好商品信息,点击“提交”后,将商品信息保存到数据库中
//===现在需要将上半部的商品信息item和下半部的商品详情信息itemDesc同时存入数据库中
@Transactional //控制事务
@Override
public void saveItem(Item item,) {
//1.设置默认商品的为上架状态
//item.setStatus(1).setCreated(new Date()).setUpdated(new Date());
//由于有了自动填充created和updated的配置,我就只给新增的商品设置个默认的上架状态就行了。(见下面的小优化)
item.setStatus(1);
//把页面上半部分的item信息入库了。
itemMapper.insert(item);
//下面这句代码 就是在验证Spring中的事务控制
//int a = 1/0; 有这句话时,就执行全局异常处理类中的异常处理方法
}
注意点:@Transactional 控制事务
如果方法上没有加@Transactional 来控制事务,当我代码的最后加了这句话
int a = 1/0;
就会发生:
我在点击“提交”后,给出了提示
但是,我再重新看我的数据表时,发现 我刚才的数据居然添加上了。
也就是说 “新增”这个操作应该回滚,而没回滚。
这就是没加
@Transactional 来控制事务的 弊端。
并且Spring一般只能控制运行时异常,检查异常还需要手动去封装提示的信息。
@Transactional的属性一般有如下5个:
@Transactional(timeout = 30,
readOnly = false,
isolation = Isolation.READ_COMMITTED,
rollbackFor = Throwable.class,
propagation = Propagation.REQUIRED)
结论:一般“增,删,改”都需要“控制事务”,而“查询”一般不需要。
5.6 商品信息修改 UPDATE
5.6.1 工具栏的介绍
在“商品查询”页面上有这么一行东西:
它就叫 工具栏 。
它是在item_list.jsp中,<table>中的data-options中定义的一个属性:toolbar:toolbar
<table class="easyui-datagrid" id="itemList" title="商品列表"
data-options="singleSelect:false,fitColumns:true,collapsible:true,pagination:true,url:'/item/query',method:'get',pageSize:20,toolbar:toolbar">
右侧的toolbar是我自己定义的一个函数,在item_list.jsp下面有这个函数的具体内容
var toolbar = [{
text:'新增',
iconCls:'icon-add',
handler:function(){
$(".tree-title:contains('新增商品')").parent().click();
}
},{
text:'编辑',
iconCls:'icon-edit',
handler:function(){
//获取用户选中的数据
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','必须选择一个商品才能编辑!');
return ;
}
if(ids.indexOf(',') > 0){
$.messager.alert('提示','只能选择一个商品!');
return ;
}
$("#itemEditWindow").window({
onLoad :function(){
//回显数据
//选中当前表格中选中的那个对象
var data = $("#itemList").datagrid("getSelections")[0];
//通过这个data可以获取当前行的任意数据
console.info(data);
//将价格转化为小数,展现给客户
data.priceView = KindEditorUtil.formatPrice(data.price);
//.form方法是easyUI框架提供的专门用来实现数据回显的函数,将表格数据回显到form表单中。
$("#itemeEditForm").form("load",data);
// 加载商品描述
//_data = SysResult.ok(itemDesc)
$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});
//加载商品规格
$.getJSON('/item/param/item/query/'+data.id,function(_data){
if(_data && _data.status == 200 && _data.data && _data.data.paramData){
$("#itemeEditForm .params").show();
$("#itemeEditForm [name=itemParams]").val(_data.data.paramData);
$("#itemeEditForm [name=itemParamId]").val(_data.data.id);
//回显商品规格
var paramData = JSON.parse(_data.data.paramData);
var html = "<ul>";
for(var i in paramData){
var pd = paramData[i];
html+="<li><table>";
html+="<tr><td colspan=\"2\" class=\"group\">"+pd.group+"</td></tr>";
for(var j in pd.params){
var ps = pd.params[j];
html+="<tr><td class=\"param\"><span>"+ps.k+"</span>: </td><td><input autocomplete=\"off\" type=\"text\" value='"+ps.v+"'/></td></tr>";
}
html+="</li></table>";
}
html+= "</ul>";
$("#itemeEditForm .params td").eq(1).html(html);
}
});
//我自己加的一段JS
//目的:实现商品类目名称的回显 而不是现实商品类目的id号
//1.通过 上面第62行的data 获取到商品分类id
var cid = data.cid;
alert("商品分类目录id:"+cid);
//2.获取到cid对应的商品名称 //注意:这个key要与ItemCatController中的26行方法中的参数名保持一致
$.get("/item/cat/queryItemName",{"itemCatId":cid},function(data){
//alert("动态获取商品分类的名称:"+data);
//3.将商品名称 回显到指定位置
//.siblings()方法是通过查询jQuery官网查出来的,prev()也是
$("#itemeEditForm input[name='cid']").siblings("span").text(data);
//$("#itemeEditForm input[name='cid']").prev().text(data);
});
KindEditorUtil.init({
"pics" : data.image,
"cid" : data.cid,
fun:function(node){
KindEditorUtil.changeItemParam(node, "itemeEditForm");
}
});
}
}).window("open");
}
},{
text:'删除',
iconCls:'icon-cancel',
handler:function(){
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','未选中商品!');
return ;
}
$.messager.confirm('确认','确定删除ID为 '+ids+' 的商品吗?',function(r){
if (r){
var params = {"ids":ids};
$.post("/item/delete",params, function(data){
if(data.status == 200){
$.messager.alert('提示','删除商品成功!',undefined,function(){
$("#itemList").datagrid("reload");
});
}else{
$.messager.alert("提示",data.msg);
}
});
}
});
}
},'-',{
text:'下架',
iconCls:'icon-remove',
handler:function(){
//获取选中的ID串中间使用","号分割
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','未选中商品!');
return ;
}
$.messager.confirm('确认','确定下架ID为 '+ids+' 的商品吗?',function(r){
if (r){
var params = {"ids":ids};
//原来是/item/instock 根据业务需要,要是想下架,就改成2,再加一个updateStatus,为了一眼看上去知道在做什么;
$.post("/item/updateStatus/2",params, function(data){
if(data.status == 200){
$.messager.alert('提示','下架商品成功!',undefined,function(){
$("#itemList").datagrid("reload");
});
}
});
}
});
}
},{
text:'上架',
iconCls:'icon-remove',
handler:function(){
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','未选中商品!');
return ;
}
$.messager.confirm('确认','确定上架ID为 '+ids+' 的商品吗?',function(r){
if (r){
var params = {"ids":ids};
//原来是/item/instock 根据业务需要,要是想上架,就改成1,再加一个updateStatus,为了一眼看上去知道在做什么;
$.post("/item/updateStatus/1",params, function(data){
if(data.status == 200){
$.messager.alert('提示','上架商品成功!',undefined,function(){
$("#itemList").datagrid("reload");
});
}
});
}
});
}
}];
其中这行代码
如果没写.parent() 那我只能点击这部分才能跳转到“新增商品”页面:
加上了.parent() 那我点击这一条 都能跳转到“新增商品”页面:
5.6.2 点击“编辑”,将当前选中的Item信息展现在弹出框中
这个功能就是在item_list.jsp中的toolbar函数中,“编辑”这里规定的:
点击“编辑”后,首先通过.window()展现这么一个大白框
然后通过item-list.jsp中这个标签中的href属性,将编辑页面item-edit展现在上面的大白框中。
<div id="itemEditWindow" class="easyui-window" title="编辑商品" data-options="modal:true,closed:true,iconCls:'icon-save',href:'/page/item-edit'" style="width:80%;height:80%;padding:10px;">
</div>
{
text:'编辑',
iconCls:'icon-edit',
handler:function(){
//获取用户选中的数据
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','必须选择一个商品才能编辑!');
return ;
}
if(ids.indexOf(',') > 0){
$.messager.alert('提示','只能选择一个商品!');
return ;
}
$("#itemEditWindow").window({
onLoad :function(){
//回显数据
//选中当前表格中选中的那个对象
var data = $("#itemList").datagrid("getSelections")[0];
//通过这个data可以获取当前行的任意数据
console.info(data);
//将价格转化为小数,展现给客户
data.priceView = KindEditorUtil.formatPrice(data.price);
//.form方法是easyUI框架提供的专门用来实现数据回显的函数,将表格数据回显到form表单中。
$("#itemeEditForm").form("load",data);
// 加载商品描述
//_data = SysResult.ok(itemDesc)
$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});
//加载商品规格
$.getJSON('/item/param/item/query/'+data.id,function(_data){
if(_data && _data.status == 200 && _data.data && _data.data.paramData){
$("#itemeEditForm .params").show();
$("#itemeEditForm [name=itemParams]").val(_data.data.paramData);
$("#itemeEditForm [name=itemParamId]").val(_data.data.id);
//回显商品规格
var paramData = JSON.parse(_data.data.paramData);
var html = "<ul>";
for(var i in paramData){
var pd = paramData[i];
html+="<li><table>";
html+="<tr><td colspan=\"2\" class=\"group\">"+pd.group+"</td></tr>";
for(var j in pd.params){
var ps = pd.params[j];
html+="<tr><td class=\"param\"><span>"+ps.k+"</span>: </td><td><input autocomplete=\"off\" type=\"text\" value='"+ps.v+"'/></td></tr>";
}
html+="</li></table>";
}
html+= "</ul>";
$("#itemeEditForm .params td").eq(1).html(html);
}
});
KindEditorUtil.init({
"pics" : data.image,
"cid" : data.cid,
fun:function(node){
KindEditorUtil.changeItemParam(node, "itemeEditForm");
}
});
}
}).window("open");
}
}
通过以上JS 就可以在弹框中显示出当前选中的Item的信息,供我以后修改商品信息时使用。
5.6.3 实现商品分类中文名称的回显
当我选中一条商品信息,点击“编辑”后,在弹出框页面中,各个响应的位置上显示着这条商品Item的信息。
但注意:
“选择类目”右边目前显示的不是当前这个Item的分类的中文名字,而是对应的分类信息的id号。
那不行啊,用户哪知道这个id号表示啥分类啊。
所以当前的需求就是:
将这个商品分类的id号替换成对应的商品分类的中文名称。
因为在商品列表展示页面,已经服务器端已经将足够的信息返回给客户端了。
当我点进编辑页面后,选择类目右面显示的是分类的id ,而不是分类的名称。
如果再从服务器去查,再返回,会给服务器带来额外的活。效率低。
其实,这个 分类的中文名称 是已经传给客户端了,只要在页面上通过前端JS代码就能查出来。
思路:
1.通过前端的JS的选择器动态地获取 商品分类的ID:3
2.发起Ajax请求,查询商品分类的ID:3 所对应的 商品分类的名称
3.在 指定的位置 将 商品分类的名称 替换原来的 3。
实现,:
$("#itemEditWindow").window({
onLoad :function(){
//回显数据
//选中当前表格中选中的那1个对象 datagrid是数据表格的意思
var data = $("#itemList").datagrid("getSelections")[0];
//通过这个data可以获取当前行的任意数据
console.info(data);
//将价格转化为小数,展现给客户
data.priceView = KindEditorUtil.formatPrice(data.price);
//.form方法是easyUI框架提供的专门用来实现数据回显的函数,将表格数据回显到form表单中。
$("#itemeEditForm").form("load",data);
// 加载商品描述
//_data = SysResult.ok(itemDesc)
$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});
//加载商品规格
$.getJSON('/item/param/item/query/'+data.id,function(_data){
if(_data && _data.status == 200 && _data.data && _data.data.paramData){
$("#itemeEditForm .params").show();
$("#itemeEditForm [name=itemParams]").val(_data.data.paramData);
$("#itemeEditForm [name=itemParamId]").val(_data.data.id);
//回显商品规格
var paramData = JSON.parse(_data.data.paramData);
var html = "<ul>";
for(var i in paramData){
var pd = paramData[i];
html+="<li><table>";
html+="<tr><td colspan=\"2\" class=\"group\">"+pd.group+"</td></tr>";
for(var j in pd.params){
var ps = pd.params[j];
html+="<tr><td class=\"param\"><span>"+ps.k+"</span>: </td><td><input autocomplete=\"off\" type=\"text\" value='"+ps.v+"'/></td></tr>";
}
html+="</li></table>";
}
html+= "</ul>";
$("#itemeEditForm .params td").eq(1).html(html);
}
});
//目的:实现商品类目名称的回显 而不是现实商品类目的id号
//1.通过 上面第62行的data 获取到商品分类id
//是无法从data中直接获取商品分类的中文名字的!!!
var cid = data.cid;
//alert("商品分类目录id:"+cid);
//2.获取到cid对应的商品名称 //注意:这个key要与ItemCatController中的26行方法中的参数名保持一致
//客户端通过下面这样一个ajax请求,就可以从服务器端获取到商品类目的名称。并且这个ajax请求的实现在之前点击“商品查询”展现“叶子类目”时,已经写过了,服务器那边就不用再写了。
$.get("/item/cat/queryItemName",{"itemCatId":cid},function(data){
//alert("动态获取商品分类的名称:"+data);
//3.将商品名称 回显到指定位置
//.siblings()方法是通过查询jQuery官网查出来的,prev()也是,这两种写法哪一种都可以。siblings意思就是兄弟姐妹
//需要通过父的iditemeEditForm 再加上子的input标签 再通过找叫“span”的兄弟,磁能准确定位上,要显示商品中文名字这块地方
$("#itemeEditForm input[name='cid']").siblings("span").text(data);
//$("#itemeEditForm input[name='cid']").prev().text(data);
});
5.6.4 商品信息的修改---------前端JS
发送请求的客户端的JS代码如下:
$("#itemeEditForm [name=itemParams]").val(paramJson);
//alert($("#itemeEditForm").serialize());//为的是看一下 参数都有哪些
$.post("/item/update",$("#itemeEditForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','修改商品成功!','info',function(){
$("#itemEditWindow").window('close');
$("#itemList").datagrid("reload");
});
}else{
$.message.alert("提示",data.msg);
}
});
}
</script>
由此可知,客户端发送的ajax请求的url为/item/update
5.6.5 商品信息的修改---------ItemController------服务器端controller
/**
* 业务需求:完成商品信息的修改,返回系统vo对象SysResult
* 客户端发送的请求url: http://localhost:8091/item/update
* 参数:整个form表单中的数据,都在item对象中,所以将item当做参数,由客户端传递给服务器端就可以了
* 返回值:SysResult对象
*
* 由于又要同时修改itemDesc的信息,所以要把itemDesc传进来
*
*/
@RequestMapping("/update")
public SysResult updateItem(Item item){
itemService.updateItem(item);
return SysResult.success();//这个Json对象中封装着status等信息,客户端会根据这个对象中的status等信息判断服务器这边的活有米有干成功。
}
5.6.6 商品信息的修改---------ItemService------服务器端service
//===修改:在点击完“查询商品”后,在右侧页面选中一个商品,点击“编辑”,填写好相应的信息后,点击“提交”,将新的商品信息保存到数据库中。
void updateItem(Item item, ItemDesc itemDesc);
5.6.7 商品信息的修改---------ItemServiceImpl------服务器端serviceImpl
这个业务中引入的一个亮点就是:时间数据的填充
“新增”和“修改”时都会涉及到created(新增时间)和updated(修改时间)的编写。
“新增”涉及到 created和updated,
“修改”只涉及到“updated”。
如果每次都在业务层Impl中写.setUpdated(new date()); .setCreated(new date()); 会非常麻烦。
那么怎么才能写一次,以后就不用写了呢,做到一劳永逸?
实现方法:
在jt-common中找到奥BasePojo,
在created和updated这两个属性上添加某种注解,实现这个功能
//新增时 有效
@TableField(fill = FieldFill.INSERT)
private Date created;
//新增/修改时 有效
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated;
对,就是这个@TableField注解,FieldFill.后面接的就是 做什么业务时 ,这个注解开始工作。
光写这个注解还不够
另外,我还得写一个配置类,规定这个@TableField注解工作时,把created和updated赋上了什么新的数据。
这个@TableField是MP提供的一个注解,所以在它的官网上也能收到这个配置类怎么写。
我不知道这个配置怎么写,那就查官网把
这个配置类是从MP的官网中 ----插件扩展----自动填充功能找到的。
//实现 创建时间 和 修改时间 的自动填充功能
//编辑自动赋值处理器 从MP官网上CV过来的
@Component //将MyMetaObjectHandler对象交给Spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 我在BasePojo中,添加了 新增/更新的注解@TableField(fill = FieldFill.INSERT)和@TableField(fill = FieldFill.INSERT_UPDATE)
* 说明Pojo的属性有了新的使命,必须要把它们身上的新的值赋值到数据库中对应的字段上,created和updated
* 所以必须要明确说明数据库中的字段名,和值是多少
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
//设定自动填充的属性名和属性值
//this就代表MyMetaObjectHandler对象
//新增Item时,要created和updated都赋值
// 第一个参数created和updated就是tb_item表中字段的名称 metaObject是MP定义的一个规范,啥作用?不清楚。写上就行了。
this.setInsertFieldValByName("created", new Date(),metaObject);
this.setInsertFieldValByName("updated", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//而更新时 只需更新updated即可
//这里的方法也可以写.setInsertFieldValByName,但 更新嘛 最好还是写.setUpdateFieldValByName
this.setUpdateFieldValByName("updated", new Date(), metaObject);
}
}
================
有了以上的这个知识,那么ItemServiceImpl中的关于商品信息修改的代码就可以简化了。
//===修改:在点击完“查询商品”后,在右侧页面选中一个商品,点击“编辑”,填写好相应的信息后,点击“提交”,将新的商品信息保存到数据库中。
//===又要将itemDesc也一同修改
@Transactional //控制事务
@Override
public void updateItem(Item item) {
//修改 更新时间
//由于有了自动更新时间的配置,我就不用自己去更新updated了
//item.setUpdated(new Date()); //.setUpdated原本是BasePojo中的方法,而Item继承了BasePojo,所以item也可以.setUpdated
//引用MP中的.updateById(Item entity)方法
//由于在修改时,item信息回显时,id也回传了,只是在js中hidden了。但是是有这个id的,所以我就可以根据id去修改Item
//根据对象中不为null的属性充当set条件(eg:setTitle()...),主键id 充当where条件
itemMapper.updateById(item);
}
至此 商品的修改 功能 暂时告一段落,还没彻底完成,还差商品描述信息的修改ItemCatDesc。
未完,接CGBIV-JT项目-lession4-jt项目正式开启-下
版权声明:本文标题:CGBIV-JT项目-lession4-jt项目正式开启-上 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1727201219a1102130.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论