CGBIV-JT项目-lession4-jt项目正式开启-上

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.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/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文件

引入jt.common依赖,并添加build标签

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/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.org/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对象 ------jt.common中

//这个对象是根据客户端的请求分析出来,客户端想要的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项目正式开启-下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值