SSM 项目学习(Vue3+ElementPlus+Axios+SSM)

发布于:2024-04-09 ⋅ 阅读:(366) ⋅ 点赞:(0)

1 项目介绍

1.1 项目功能/界面

在这里插入图片描述
● 技术栈
说明: 前后端分离开发, 前端框架 Vue + 后端框架 SSM

  1. 前端框架 Vue
  2. 后台框架-SSM(SpringMVC+Spring+MyBatis)
  3. 数据库-MySQL
  4. 项目的依赖管理-Maven
  5. 分页-pagehelper
  6. 逆向工程-MyBatis Generator
  7. 其它…

2 项目基础环境搭建

2.1 创建项目

  1. 创建 Maven 项目-提醒, 配置 maven 的仓库镜像
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 手动创建 java 和 test 相关目录
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    最终的项目目录样子:
    在这里插入图片描述
  3. 引入项目依赖的 jar 包, 先引入基本的包,开发中, 需要什么包再导入即可
    在这里插入图片描述
    修改整个项目的pom.xml文件:
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.maven.quickstart</groupId>
  <artifactId>furn-ssm</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>furn-ssm Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!--引入springmvc依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.8</version>
    </dependency>

    <!--引入spring-jdbc 支持事务相关-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.8</version>
    </dependency>

  <!-- 引入spring aspects 切面编程需要的库-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.8</version>
    </dependency>

  <!--  引入mybatis库/jar-->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.1.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
    </dependency>

    <!-- 引入 mybatis 整合 spring 适配包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>

  <!--  引入druid数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.6</version>
    </dependency>



    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

    <!--引入myabtis的逆向工程-->
    <!--这里和讲的不一样,要导入下面两个依赖。-->
    <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator -->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.4.0</version>
      <type>pom</type>
    </dependency>

    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.0</version>
    </dependency>


  </dependencies>




  <build>
    <finalName>furn-ssm</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>7</source>
          <target>7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>


</project>

  1. 给项目配置 Tomcat, 方式和前面将 javaweb 一样, 配置一个 Local Tomcat Server,
    在这里插入图片描述
    在这里插入图片描述

2.2 项目全局配置 web.xml

  1. 配置 D:\idea_java_projects\furns_ssm\src\main\webapp\WEB-INF\web.xml , 和项目全局相关的【适当的想一想前面我们学习的 spring , springmvc, mybatis, 不需要背, 把每步骤看懂,到时知道到哪里去找配置】
<web-app>
  <display-name>Archetype Created Web Application</display-name>


  <!-- 1、配置启动 Spring 容器:
  主要配置和业务逻辑有关的,比如数据源,事务控制等-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!-- 1、ContextLoaderListener 监听器作用是启动 Web 容器时,
  自动装配 ApplicationContext 的配置信息
  2、它实现了 ServletContextListener 接口,
  在 web.xml 配置该监听器,启动容器时,会默认执行它实现的方法
  -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    <listener-class> furn.controller.ContextFinalizer </listener-class>
  </listener>
  <!--
  2、SpringMVC 的前端控制器,拦截所有请求
  老韩解读
  1. springmvc 中央控制器
  2. 负责处理所有的应用请求
  3. 如果没有指定 spirngmvc 的配置文件,默认为 servlet-name 的值-servlet.xml
  4. 如果指定了 springmvc 的配置文件,则以指定的为准, 在讲解 springmvc 时讲解过
  -->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!--
   3、字符编码过滤器,一定要放在所有过滤器的最前面
   源码解读
   (1) 如 果 forceRequestEncoding 设 置 为 true, 执 行 request.setCharacterEncoding(encoding)
   (2) 如 果 forceResponseEncoding 设 置 为 true, 执 行 response.setCharacterEncoding(encoding);
 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>-
    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- 4、配置 HiddenHttpMethodFilter, 使用 Rest 风格的 URI,可以把页面发过来的 post
  请求转为指定的 delete 或者 put 请求-->
  <filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>


  <!--
   3、字符编码过滤器,一定要放在所有过滤器的最前面
   源码解读
   (1) 如 果 forceRequestEncoding 设 置 为 true, 执 行 request.setCharacterEncoding(encoding)
   (2) 如 果 forceResponseEncoding 设 置 为 true, 执 行 response.setCharacterEncoding(encoding);
 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>-
    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- 4、配置 HiddenHttpMethodFilter, 使用 Rest 风格的 URI,可以把页面发过来的 post
  请求转为指定的 delete 或者 put 请求-->
  <filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

2.3 SpringMVC 配置

1、创建 SpringMVC 的配置文件dispatcher-servlet.xml: 主要包含网站跳转逻辑的控制

<?xml version="1.0" encoding="UTF-8"?>
<!--springMVC的配置文件-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    
</beans>

2 、 创 建D:\idea_java_projects\furns_ssm\src\main\webapp\WEB-INF\dispatcher-servlet.xml , 并加入必要的命名空间
在这里插入图片描述
3、创建项目相关的包
在这里插入图片描述
4、 在dispatcher-servlet.xml 中 配置扫描 com.hspedu 包 的控制器

 <!--
    use-default-filters="false" 禁用默认过滤规则
    context:include-filter 配置说明 只是扫描控制器
    -->
    <context:component-scan base-package="furn" use-default-filters="false">

        <!-- 配置SpringMVC只扫描Controller -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

5、 配置视图解析器

    <!-- 配置视图解析器,指定页面返回 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".html"></property>
    </bean>

6、 两个常规配置

    <!-- 两个常规配置 -->
    <!-- 将 SpringMVC 不能处理的请求交给 Tomcat, 比如请求 css,js 等 -->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
    <!-- 能支持 SpringMVC 高级功能,比如 JSR303 校验,映射动态请求 -->
    <mvc:annotation-driven/>

完成后的dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--springMVC的配置文件-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--
    use-default-filters="false" 禁用默认过滤规则
    context:include-filter 配置说明 只是扫描控制器
    -->
    <context:component-scan base-package="furn" use-default-filters="false">

        <!-- 配置SpringMVC只扫描Controller -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 配置视图解析器,指定页面返回 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".html"></property>
    </bean>

    <!-- 两个常规配置 -->
    <!-- 将 SpringMVC 不能处理的请求交给 Tomcat, 比如请求 css,js 等 -->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
    <!-- 能支持 SpringMVC 高级功能,比如 JSR303 校验,映射动态请求 -->
    <mvc:annotation-driven/>
</beans>

7、完成测试

  1. 创 建 D:\hspedu_ssm_ssm 整 合_temp\furns-ssm\src\main\java\com\hspedu\furns\controller\TestController.java
package furn.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/19 21:22
 */
@Controller
public class TestController {

    @RequestMapping("/hi")
    public String hi() {
        // .setCharacterEncoding("UTF-8");
        System.out.println("TestController_Hi");
        return "hi";
    }
}

  1. 创 建 D:\hspedu_ssm_ssm 整 合_temp\furns-ssm\src\main\webapp\WEB-INF\views\hi.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>嗨,欢迎光临,男宾一位!</h1>

</body>
</html>
  1. 启动 Tomcat , 浏览器输入 http://localhost:8080/ssm/hi
    在这里插入图片描述

2.4 配置 Spring 和 MyBatis , 并完成整合

1、创建 spring 的配置文件 applicationContext.xml : 主要配置和业务逻辑有关的,比如数
据源,事务控制等

2、创建 D:\idea_java_projects\furns_ssm\src\main\resources\applicationContext.xml , 并加入必要的命名空间, 提示:同样适用前面的方式创建: 右键->New->XML configuration ->Spring Config

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- spring 的配置文件 : 主要配置和业务逻辑有关的,比如数据源,事务控制等 -->
</beans>

3、配置扫描 com.hspedu 包,但是不扫描控制器, 控制器由 springmvc 管理

   <!--
        1. 扫描 furn包 [包括子包]
        2. context:exclude-filter 配置说明 不扫描控制器。 控制器的扫描已经交给了WEB-INF/views/dispatcher-servlet的SpringMVC
    -->
    <context:component-scan base-package="furn">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
                       <!--exclude-filter是spring里面的内容-->
    </context:component-scan>

4 、 创 建 D:\idea_java_projects\furns_ssm\src\main\resources\jdbc.properties , 配 置 连 接mysql 的信息
在这里插入图片描述

jdbc.user=root
jdbc.pwd=liuadfsdfaf
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/furns_ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT

5、在Spring的配置文件applicationContext.xml中,配置数据源配置


<!--引入外部的jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSoruce Druid数据源-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="pooledDataSource">
        <!--给数据源对象配置属性值-->
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
    </bean>

6、在Spring的配置文件applicationContext.xml中,配置 spring 与 mybatis 的整合【手写下】

<!--配置mybatis和spring整合
    1、在项目中引入 mybatis整合到spring的适配库/包
    2. 这里爆红,是因为你还没有相应的文件, 当有文件时,就不会爆红
    -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <!--指定mybatis全局配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--指定数据源-->
        <property name="dataSource" ref="pooledDataSource"/>
        <!--指定mybatis的mapper文件[Mapper.XML]位置
        1、我们在开发中, 通常将mapper.xml放在类路径 resources/mapper
        2. 所以老韩这里指定的value 是 classpath:mapper/*.xml
        -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

7 、 在 类 路 径 resources 下 创 建D:\idea_java_projects\furns_ssm\src\main\resources\mybatis-config.xml
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--
指定mybatis全局配置的文件。 
注意这里是configuration,不是beans
-->
<configuration>


 

</configuration>

8、在类路径下创建 mapper 目录,存放 mapper 的 xml 文件
在这里插入图片描述
9、配置将 mybatis 接口实现加入到 ioc 容器, 在 applicationContext.xml 配置

  <!-- 配置扫描器,将mybatis接口的实现加入到ioc容器中
    1、我们的mapper接口放在com.hspedu.furn.dao
    2. mybatis就是处于DAO层, 操作DB
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--
            1. 扫描所有的dao接口的实现,加入到ioc容器中
            2. 这里dao接口,就是mapper接口
        -->
        <property name="basePackage" value="furn.dao"/>
    </bean>

10、配置事务控制, 在 applicationContext.xml 配置

    <!--配置事务管理器-对象
    1. DataSourceTransactionManager 这个对象是进行事务管理
    2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制
    -->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="pooledDataSource"/>
    </bean>

11、 配置开启基于注解的事务(使用 XML 配置+切入表达式),并指定切入点.

<!--配置启动基于注解的声明式事务管理功能
    这是老韩以前的玩法, 使用XML配置+切入表达式来玩
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--
        老韩解读
        1. 开启基于注解的事务,并指定切入点
        2. execution(* com.hspedu.furns.service..*(..)):
           表示对com.hspedu.furns.service包所有类的所有方法控制事务
        3. tx:advice : 配置事务增强, 也就是指定事务如何切入
        4. 不需要背,但是能看到,能修改,能维护
    -->
    <aop:config>
        <!-- 切入点表达式 -->
        <aop:pointcut id="txPoint" expression="execution(* furn.service..*(..))"/>
        <!-- 配置事务增强/规则: 使用txAdvice 指定规则对 txPoint进行切入-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>
    <!--    配置事务增强【指定事务规则】,也就是指定事务如何切入-->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <!-- *代表所有方法都是事务方法-->
            <tx:method name="*"/>
            <!-- 以get开始的所有方法 ,我们认为是只读,进行调优-->
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

完整的Spring的配置文件:applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!--
创建 spring 的配置文件 applicationContext.xml : 主要配置和业务逻辑有关的,比如数据源,事务控制等
-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!-- spring的配置文件 : 主要配置和业务逻辑有关的,比如数据源,事务控制等 -->

    <!--
        1. 扫描 furn包 [包括子包]
        2. context:exclude-filter 配置说明 不扫描控制器。 控制器Controller的扫描已经交给了WEB-INF/views/dispatcher-servlet的SpringMVC
    -->
    <context:component-scan base-package="furn">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>


    <!--引入外部的jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSoruce Druid数据源-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="pooledDataSource">
        <!--给数据源对象配置属性值-->
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.pwd}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
    </bean>

    <!--配置mybatis和spring整合
    1、在项目中引入 mybatis整合到spring的适配库/包
    2. 这里爆红,是因为你还没有相应的文件, 当有文件时,就不会爆红
    -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <!--指定mybatis全局配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--指定数据源-->
        <property name="dataSource" ref="pooledDataSource"/>
        <!--指定mybatis的mapper文件[Mapper.XML]位置
        1、我们在开发中, 通常将mapper.xml放在类路径 resources/mapper
        2. 所以老韩这里指定的value 是 classpath:mapper/*.xml -->
       
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- 配置扫描器,将mybatis接口的实现加入到ioc容器中
    1、我们的mapper接口放在com.hspedu.furn.dao
    2. mybatis就是处于DAO层, 操作DB
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--
            1. 扫描所有的dao接口的实现,加入到ioc容器中
            2. 这里dao接口,就是mapper接口
        -->
        <property name="basePackage" value="furn.dao"/>
    </bean>

    <!--配置事务管理器-对象
    1. DataSourceTransactionManager 这个对象是进行事务管理
    2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制
    -->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="pooledDataSource"/>
    </bean>

    <!--配置启动基于注解的声明式事务管理功能
    这是老韩以前的玩法, 使用XML配置+切入表达式来玩
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--
        老韩解读
        1. 开启基于注解的事务,并指定切入点
        2. execution(* com.hspedu.furns.service..*(..)):
           表示对com.hspedu.furns.service包所有类的所有方法控制事务
        3. tx:advice : 配置事务增强, 也就是指定事务如何切入
        4. 不需要背,但是能看到,能修改,能维护
    -->
    <aop:config>
        <!-- 切入点表达式 -->
        <aop:pointcut id="txPoint" expression="execution(* furn.service..*(..))"/>
        <!-- 配置事务增强/规则: 使用txAdvice 指定规则对 txPoint进行切入-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>
    <!--    配置事务增强【指定事务规则】,也就是指定事务如何切入-->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <!-- *代表所有方法都是事务方法-->
            <tx:method name="*"/>
            <!-- 以get开始的所有方法 ,我们认为是只读,进行调优-->
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

</beans>

12 、 完 成 测 试 : 创 建 D:\hspedu_ssm_ssm 整 合_temp\furns-ssm\src\test\java\com\hspedu\furns\test\T1.java

package com.test;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class T1 {

    @Test
    public void t1() {
        // 看看spring配置的Bean是否可以获取到

        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("ioc=" + ioc);   // DefaultSqlSessionFactory
        // 获取Bean
        Object o = ioc.getBean("pooledDataSource");
        System.out.println(o);
        System.out.println(ioc.getBean("sqlSessionFactory"));
    }
}

13、看看是否能够得到 Spring 容器的数据源对象和会话工厂对象生成的结果:

ioc=org.springframework.context.support.ClassPathXmlApplicationContext@8239c8, started on Sat Mar 23 10:33:24 CST 2024
{
	CreateTime:"2024-03-23 10:33:24",
	ActiveCount:0,
	PoolingCount:0,
	CreateCount:0,
	DestroyCount:0,
	CloseCount:0,
	ConnectCount:0,
	Connections:[
	]
}
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@122ef0c

Process finished with exit code 0

注意:要让这个测试成功运行,需要将前面的applicationContext.xml 文件中的

<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <!--指定mybatis全局配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--指定数据源-->
        <property name="dataSource" ref="pooledDataSource"/>
        <!--指定mybatis的mapper文件[Mapper.XML]位置
        1、我们在开发中, 通常将mapper.xml放在类路径 resources/mapper
        2. 所以老韩这里指定的value 是 classpath:mapper/*.xml*/
        
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

<property name="mapperLocations" value="classpath:mapper/*.xml"/>这一行给注释掉。
不然会报错找不到mapper里面的xml文件。

2.5 创建表,使用逆向工程生成 Bean、XxxMapper 和 XxxMapper.xml

  1. 创建 furns_ssm 数据库和 furns 表
-- 创建 furns_ssm
DROP DATABASE IF EXISTS furns_ssm;
CREATE DATABASE furns_ssm;
USE furns_ssm;
 -- 创建家居表
CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT, ## id
`name` VARCHAR(64) NOT NULL, ## 家居名
`maker` VARCHAR(64) NOT NULL, ## 厂商
`price` DECIMAL(11,2) NOT NULL, ## 价格
`sales` INT(11) NOT NULL, ## 销量
`stock` INT(11) NOT NULL, ## 库存
`img_path` VARCHAR(256) NOT NULL ## 照片路径
);
-- 初始化家居数据
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 北 欧 风 格 小 桌 子 ' , ' 熊 猫 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/1.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 简 约 风 格 小 椅 子 ' , ' 熊 猫 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/2.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 典 雅 风 格 小 台 灯 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/3.jpg');
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , ' 温 馨 风 格 盆 景 架 ' , ' 蚂 蚁 家 居 ' , 180 , 666 , 7 ,
'assets/images/product-image/4.jpg');
SELECT * FROM furn;
  1. 使用 MyBatis Generator 逆向工程生成 bean mapper 接口和 mapper.xml , 当然也可以自己写
  • 老韩建议,如果在开发中, 逆向工程生成的代码, 不能满足需要,再自己编写

    1. 修改 mybatis-config.xml , 增加 typeAliases 配置.
    <!--
    类型别名简写
	如果一个包下有很多类,我们可以直接引入包
	这样该包下面的所有类名,可以直接使用。不需要全类名。
	-->
    <typeAliases>
        <package name="furn.bean"/>
    </typeAliases>
  1. 引入 MyBatis Generator 包, 在 pom.xml 配置
 <!--引入myabtis的逆向工程-->
    <!--这里和讲的不一样,要导入下面两个依赖。-->
    <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator -->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.4.0</version>
      <type>pom</type>
    </dependency>

    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.4.0</version>
    </dependency>

  1. 创 建 D:\idea_java_projects\furns_ssm\mbg.xml , 并 参 考 文 档https://mybatis.org/generator/ 进行配置 , 这里老韩给出了一个模板 xml ,你们在上面修改即可

在这里插入图片描述
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 生成没有注释的 bean-->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!-- 配置数据库连接信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/furns_ssm?characterEncoding=utf8" userId="root" password="liu1457154996">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 指定 javaBean 生成的位置-->
        <javaModelGenerator targetPackage="furn.bean"
                            targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 指定 sql 映射文件生成的位置-->
        <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 指定 dao 接口生成的位置, 也就是 mapper 接口-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="furn.dao"
                             targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 指定要逆向生成的表和生成策略-->
        <table tableName="furn" domainObjectName="Furn"></table>
    </context>
</generatorConfiguration>
  1. 创 建 文 件D:\idea_java_projects\furns_ssm\src\test\java\com\hspedu\furns\test\MBGTest.java RunningMyBatis Generator , 生成相关 bean, mapper 接口和 mapper.xml 参考官方问题来修改, 并完成测试
    运行下面的文件,即可实现逆向,生成JavaBean,Mapper,以及Mapper.xml。这里需要注意,只运行一次就可以!不然会报错!详情看:文章详情
package com.test;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/22 21:00
 */
public class MBGTest {

    @Test
    public void generator() throws XMLParserException, IOException, SQLException, InterruptedException, InvalidConfigurationException {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
        System.out.println("123");
    }
}

5、 查看结果:
之前bean、dao下没有东西。
在这里插入图片描述

运行上面的测试文件后,出现下面的结果:生成Furn FurnExample FurnMapper, FurnMapper.xml配置文件。
在这里插入图片描述

  1. 使用 Junit 测试 Spring 和 MyBatis 是否整合成功, 能通过 MyBatis 添加 furn 到数据库
    1. 修改 bean: Furn.java
      在这里插入图片描述

    2. 创 建D:\idea_java_projects\furns_ssm\src\test\java\com\hspedu\furns\dao\FurnMapperTest.java完成对 furn 表的 crud 测试操作

package com.test;

import furn.bean.Furn;
import furn.dao.FurnMapper;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.math.BigDecimal;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/22 22:30
 *
 * 演示FurnMapper的常用方法
 *
 */
public class FurnMapperTest {

    /*
    * 演示插入对象 增
    * */
    @Test
    public void insertSelectiveTest() {
        // 1. 获取到Spring容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 获取FurnMapper对象
        FurnMapper furnMapper = ioc.getBean(FurnMapper.class);
        // 3. 添加数据
        Furn furn = new Furn(null, "北欧盆景", "顺平家居", new BigDecimal(90), 666, 7, "assets/images/product-image/4.jpg");
        int affectedRows = furnMapper.insertSelective(furn);
        System.out.println(affectedRows);
    }

    // 测试删除 删
    @Test
    public void deleteByPrimaryKeyTest() {
        // 1. 获取容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 获取FurnMapper对象
        FurnMapper furnMapper = ioc.getBean(FurnMapper.class);
        int affectedRows = furnMapper.deleteByPrimaryKey(4);
        System.out.println(affectedRows);
    }


    // 测试  修改 改
    @Test
    public void updateByPrimaryKeySelectiveTest() {
        // 1. 获取容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 获取FurnMapper对象
        FurnMapper furnMapper = ioc.getBean(FurnMapper.class);

        Furn furn = new Furn();
        furn.setId(5);
        furn.setName("广汽传祺家具");
        int affectedRow = furnMapper.updateByPrimaryKeySelective(furn); // 不是全部设置 而是选择性设置一部分:update furn SET name = ? where id = ?
        System.out.println(affectedRow);
        System.out.println("操作完成");
    }

    // 查
    @Test
    public void selectByPrimaryKeyTest() {
        // 1. 获取容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 获取FurnMapper对象
        FurnMapper furnMapper = ioc.getBean(FurnMapper.class);
        Furn furn = furnMapper.selectByPrimaryKey(5);
        System.out.println(furn);

        // 底层会自己关闭SqlSession
    }
}

2.6 注意事项和细节说明

1、 insertSelective 和 insert 的区别

  1. insertSelective–选择性保存数据;
    比如 User 里面有三个字段:id,name,age,password
    但是只设置了一个字段;
    User u=new user();
    u.setName(“张三”);
    insertSelective(u);
  2. insertSelective 执行对应的 sql 语句的时候,只插入对应的 name 字段;(主键是自动添加
    的,默认插入为空)
    insert into tb_user (id,name) value (null,“张三”);
  3. 而 insert 则是不论设置多少个字段,统一都要添加一遍,不论你设置几个字段,即使是
    一个
    User u=new user();
    u.setName(“张三”);
    insert(u);
    insert into tb_user (id,name,age,password) value (null,“张三”,null,null)
  1. FurnExample是生成的是实现复杂查询的。

3 实现功能 01-搭建 Vue 前端工程

3.1 需求分析/图解

1、使用 Vue3 的脚手架 Vue-cli 工具, 创建 ssm 的前端项目基础开发环境
2、Vue-cli 主要的功能是 自动生成 Vue 的项目模板, 提高开发效率
3、Vue-cli 工具, 我们在前面使用过, 同学们可以去回顾一下

3.2 代码实现

3.2.1 搭建 Vue 前端工程

  1. 先下载 node.js LTS 并安装 : node.js 的 npm,用于管理前端项目包依赖, 老韩说明: 前面我们讲解前端技术栈的时候用过 node.js(10.16.3), 这里我们先卸载一把, 安装 14.17.3 这个版本, 方便我们使用 vue3
    在这里插入图片描述2) 验证 node.js 是否安装成功
    在这里插入图片描述
  1. 全局安装 Vue 插件 cli : npm install -g @vue/cli , 这样我们就可以创建 Vue 工程
    在这里插入图片描述
  1. 创建 Vue 项目 - 老师说明 因为我们是前后端分离,所以老师新建一个前端项目
    首先创建文件夹:E:\JavaCode\furn-smm_Vue
    然后在这个文件夹下,创建vue项目:
    在这里插入图片描述
    在这里插入图片描述
  1. 选择你需要的插件
    在这里插入图片描述
    在这里插入图片描述
  2. 选择路由模式
    在这里插入图片描述
  3. 选择项目依赖包管理方式
    在这里插入图片描述
  4. 选择是否保存本次设置
    在这里插入图片描述
    在这里插入图片描述
  5. 回车开始创建项目,成功会提示如下界面
    在这里插入图片描述
  6. 启动项目-按给出指令执行即可
    在这里插入图片描述
  7. 启动项目成功, 会提示如下界面
    在这里插入图片描述
  8. 完成测试,浏览器访问
    在这里插入图片描述
  1. 使用 idea 打开 ssm_vue 项目, 并配置项目启动
  1. 直接将 ssm_vue 项目拖到 idea
    在这里插入图片描述
    在这里插入图片描述
  2. 配置 ssm_vue 使用 npm 方式启动
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

3.2.2 Vue3 项目目录结构梳理

● Vue3 项目结构介绍

  1. 老韩梳理 Vue3 最重要的路由机制, 理解后同学们就清晰很多

在这里插入图片描述
2) index.html 页面说明
在这里插入图片描述
3) assets 目录和 components 目录说明
在这里插入图片描述

3.3 配置 Vue 服务端口

1 、 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\vue.config.js
为什么要设置代理:如果要访问的url是 /api/save, 那么就会倍代理到 http://localhost:8080/ssm/save

因为会出现一个拦截问题。你的前端页面是10000端口,然后想要访问后端的8080端口,会报错。
所以这里用一个代理,从8080跳转到10000

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer:{
    port:10000,  //设置启动窗口为10000
    // 解读: 如果要访问的url是 /api/save, 那么就会倍代理到 http://localhost:8080/ssm/save
    proxy:{     // 设置代理, 必须填
      "/api":{  // 设置拦截器 拦截器格式:斜杠+拦截器名字, 名字可以自己定.
        target:"http://localhost:8080/furn_ssm", // 代理的目标地址, 就是 /api 代替 http://localhost:8080/furn_ssm
        changeOrigin:true,                // 是否设置同源, 设置为true则表示允许跨域访问
        pathRewrite:{                     // 路径重写(如果拦截器里有api就忽略)
          '/api':''                       // 选择忽略拦截器里面的单词.
        }
      }
    }
  },

})

2、启动测试, 可以看到现在是 10000 端口了
在这里插入图片描述

3.4 Element Plus 和 Element UI

Element UI 官方文档:https://element.eleme.cn/#/zh-CN
Element Plus 官方文档:https://element-plus.gitee.io/zh-CN/

一句话: Element Plus:Element UI for Vue 3.0

1、Element Plus 是 Element 对 Vue 3.0 的升级适配
2、Element 诞生于 2016 年,起初是饿了么内部的业务组件库,开源后深受广大前端开发者的喜爱,成为 Vue 生态中最流行的 UI 组件库之一。
3、Element Plus 是重构的全新项目。Element 团队重写了 Element 的代码,用于支持 Vue3
4、Element UI 还在维护和升级,因为 Vue2 仍然有项目在使用, Vue3 支持的浏览器范围有所减少, 这是一个大的改变, 所以在一段时间内, Vue2 仍然会在项目使用.

3.5安装 element-plus 插件

● 安装 element-plus 插件
我们会使用到 element-plus ,停止项目,安装 element-plus 插件, element-plus 官方文档https://element-plus.gitee.io/#/zh-CN/component/layout
在这里插入图片描述

4 实现功能 02-创建项目基础界面

4.1 需求分析/图解

在这里插入图片描述

4.2 思路分析

  1. 使用 Vue3+ElementPlus 完成

  2. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\App.vue 成如下形式, 会删除部分用不上的代码,增加

<template>
  <!--
  本页面App.vue 常用于布局页面
  <router-view/> 就是路由指令。会把路由到的页面内容,展示到<router-view/>
  例如url地址是 http://localhost:8080/ , 那么路由的path 就是 /
  例如url地址是 http://localhost:8080/about , 那么路由的path 就是 /about
  <nav>
    <router-link to="/">Home-10000</router-link> |
    <router-link to="/about">About</router-link>
      hello hsp
  </nav>
  <router-view/>
  -->
  <div>
  <!--引入Header组件/界面-->
      <Header/>
<!--      引入侧边栏-->
      <div style="display: flex">
          <Aside/>
<!--          内容区域,这里直接通过路由展示,我们就路由到HomeView.vue-->
          <router-view style="flex: 1"/>
      </div>

  </div>
</template>
<script>
  import Header from "@/components/Header.vue";
  import Aside from "@/components/Aside.vue";
  export default {
      name: "Layout",
      components: {
          Header,
          Aside
      },

  }
</script>
<style>
</style>

  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目temp\ssm_vue\src\views\HomeView.vue ,
<template>
<!-- 去掉 class="home"-->
<div>
<!-- <img alt="Vue logo" src="../assets/logo.png">-->
<!-- <HelloWorld msg="Welcome to Your Vue.js App"/>-->
</div>
</template>
<script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue' export default {
name: 'HomeView', components: {
// HelloWorld
}
}
</script>
  1. 创 建 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\components\Header.vue

这里直接去element-plus里搜索,找测边框

<template>
    <div style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc;
display: flex">
        <div style="width: 200px; padding-left: 30px; font-weight: bold; color:
dodgerblue">后台管理</div>
        <div style="flex: 1"></div>
        <div style="width: 80px">

            <el-dropdown>
                <el-button type="primary" >
                    个人中心<el-icon class="el-icon--right"><arrow-down /></el-icon>
                </el-button>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item>个人信息</el-dropdown-item>
                        <el-dropdown-item>退出登陆</el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
        </div>
    </div>
</template>
<script>
export default {
    name: "Header"
}
</script>
<style scoped>
</style>
  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\App.vue , 引入 Header 组件
    在这里插入图片描述
  2. 创建全局的 global.css(先准备着, 后面有用) , 以后有全局样式就可以写在这里 ,
    C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\assets\css\global.css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\main.js , 引入 global.css
    在这里插入图片描述

  2. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\main.js, 引 入 Element Plus , 并 测 试 ,
    如 何 引 入 : 文 档 https://element-plus.gitee.io/zh-CN/guide/quickstart.html
    在这里插入图片描述

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/css/global.css'
// 引入Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'


// 下面加上  .use(ElementPlus)
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')

  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\components\Header.vue , 引 入 下 拉 框 , 文 档
    https://doc-archive.element-plus.org/#/zh-CN/component/dropdown 【是旧版对应的文档】
    在这里插入图片描述
<template>
    <div style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc;
display: flex">
        <div style="width: 200px; padding-left: 30px; font-weight: bold; color:
dodgerblue">后台管理</div>
        <div style="flex: 1"></div>
        <div style="width: 80px">

            <el-dropdown>
                <el-button type="primary" >
                    个人中心<el-icon class="el-icon--right"><arrow-down /></el-icon>
                </el-button>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item>个人信息</el-dropdown-item>
                        <el-dropdown-item>退出登陆</el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
        </div>
    </div>
</template>
<script>
export default {
    name: "Header"
}
</script>
<style scoped>
</style>
  1. 创 建 侧 边 栏 组 件 , 并 引 入 导 航 菜 单 组 件C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\components\Aside.vue , 参 考 文 档https://doc-archive.element-plus.org/#/zh-CN/component/menu
    在这里插入图片描述
    在这里插入图片描述
<template>
  <div>
<!--      引入导航菜单-->
      <el-menu
          active-text-color="#ffd04b"
          background-color="#545c64"
          class="el-menu-vertical-demo"
          default-active="2"
          text-color="#fff"
      >
          <el-sub-menu index="1">
              <template #title>
                  <el-icon><location /></el-icon>
                  <span>导航1</span>
              </template>
              <el-menu-item-group title="组一">
                  <el-menu-item index="1-1">选项1</el-menu-item>
                  <el-menu-item index="1-2">选项2</el-menu-item>
              </el-menu-item-group>
              <el-menu-item-group title="组二">
                  <el-menu-item index="1-3">选项3</el-menu-item>
              </el-menu-item-group>
              <el-sub-menu index="1-4">
                  <template #title>选项4</template>
                  <el-menu-item index="1-4-1">选项1</el-menu-item>
              </el-sub-menu>
          </el-sub-menu>
          <el-menu-item index="2">
              <el-icon><icon-menu /></el-icon>
              <span>导航2</span>
          </el-menu-item>
          <el-menu-item index="3" disabled>
              <el-icon><document /></el-icon>
              <span>导航3</span>
          </el-menu-item>
          <el-menu-item index="4">
              <el-icon><setting /></el-icon>
              <span>导航4</span>
          </el-menu-item>
      </el-menu>
  </div>
</template>

<script>
export default {
    name: "Aside"
}
</script>

<style scoped>

</style>
  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\App.vue, 将页面分成三个部分
<template>
  <!--
  本页面App.vue 常用于布局页面
  <router-view/> 就是路由指令。会把路由到的页面内容,展示到<router-view/>
  例如url地址是 http://localhost:8080/ , 那么路由的path 就是 /
  例如url地址是 http://localhost:8080/about , 那么路由的path 就是 /about
  <nav>
    <router-link to="/">Home-10000</router-link> |
    <router-link to="/about">About</router-link>
      hello hsp
  </nav>
  <router-view/>
  -->
  <div>
  <!--引入Header组件/界面  (头部)-->
      <Header/>
        <!-- 主体 -->
      <div style="display: flex">
          <!--      引入侧边栏-->
          <Aside/>
<!--          内容区域,这里直接通过路由展示,我们就路由到HomeView.vue (这部分是由HomeView.vue组件来的)-->
          <router-view style="flex: 1"/>
      </div>

  </div>
</template>
<script>
  import Header from "@/components/Header.vue";
  import Aside from "@/components/Aside.vue";
  export default {
      name: "Layout",
      components: {
          Header,
          Aside
      },

  }
</script>
<style>
</style>

  1. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\views\HomeView.vue, 加入一个 el-button,进行测试
    在这里插入图片描述
  2. 看看主页面的效果, 基本结构已经出来了
    在这里插入图片描述
  3. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
    -temp\ssm_vue\src\views\HomeView.vue , 引入表格,后面来显示数据 , 参考文档Element-plus:
    在这里插入图片描述
        <el-table :data="tableData" stripe style="width: 100%">
            <el-table-column
                    prop="id"
                    label="ID" sortable
            >
            </el-table-column>
            <el-table-column
                    prop="name"
                    label="家居名">
            </el-table-column>
            <el-table-column
                    prop="maker"
                    label="厂家">
            </el-table-column>
            <el-table-column
                    prop="price"
                    label="价格">
            </el-table-column>
            <el-table-column
                    prop="sales"
                    label="销量">
            </el-table-column>
            <el-table-column
                    prop="stock"
                    label="库存">
            </el-table-column>
            <el-table-column fixed="right" label="操作" width="115">
                <template #default="scope">
                    <el-button @click="handleEdit(scope.row)" type="text">编辑</el-button>
                    <!--
                    如果点击的是 确定 , 则会触发handleDel
                    如果点击的是 取消 , 就不会触发handleDel
                    -->
                    <el-popconfirm title="确认删除吗?" @confirm="handleDel(scope.row.id)">
                        <template #reference>
                            <el-button type="danger" size="small">删除</el-button>
                        </template>
                    </el-popconfirm>
                </template>
            </el-table-column>
        </el-table>

<!--数据中写入上面的TableData-->
export default {
    name: 'HomeView',
    components: {},
    data() {
        return {
            dialogVisible: false,
            form: {},    // 定义一个空表单
            tableData: []            
    },
    created() {
        this.list();
    },
    methods: {
    }
}


  1. 可 以 看 到 , element-plus 默 认 是 英 文 的 , 我 们 将 其 国 际 化 一 下https://doc-archive.element-plus.org/#/zh-CN/component/i18n , 修 改C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整合项目-temp\ssm_vue\src\main.js
    在这里插入图片描述
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/css/global.css'
// 引入Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// 处理中文国际化
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

// 下面加上  .use(ElementPlus)
createApp(App).use(store).use(router).use(ElementPlus, {locale: zhCn,}).mount('#app')

在这里插入图片描述
18. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目
-temp\ssm_vue\src\views\HomeView.vue, 从官网设置一些测试数据 , 并支持日期排序
在这里插入图片描述
这样写:
在这里插入图片描述
在这里插入图片描述
19. 修 改 C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\views\HomeView.vue, 增加相关的操作按钮和搜索框 , 参考 el-input组件文档, 表格的固定列文档
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.4 项目前后端分离情况

在这里插入图片描述

4.5 注意事项和细节

1、flex: 1 布局说明
https://www.cnblogs.com/LangZ-/p/12703858.html

2、box-sizing: border-box
box-sizing: border-box 就是将 border 和 padding 数值包含在 width 和 height 之内,这样的好处就是修改 border 和 padding 数值盒子的大小不变
https://blog.csdn.net/qq_26780317/article/details/80736514

3、引入 Element-Plus , 启动可能出现的问题和解决方案
在这里插入图片描述

5 实现功能 04-添加家居信息

在这里插入图片描述

5.2 思路分析

  1. 完成后台代码从 dao -> serivce -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http post 请求完成测试
  2. 完成前端代码, 使用 axios 发送 ajax(json 数据)给后台, 实现添加家居信息

5.3 代码实现

  1. 创 建 src\main\java\com\hspedu\furns\service\FurnService.java 和src\main\java\com\hspedu\furns\service\FurnServiceImpl.java, 增加添加方法
package furn.service;

import furn.bean.Furn;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/25 20:40
 * Service层是
 */
// 不是在这里添加Service 注解。表明这是Service层, 而是在FurnServiceImp中添加注解。

public interface FurnService {
    // 添加
    public void save(Furn furn);
}

package furn.service.impl;

import furn.bean.Furn;
import furn.bean.FurnExample;
import furn.dao.FurnMapper;
import furn.service.FurnService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.annotation.Resources;
import java.util.List;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/25 20:41
 * Service是最终处理数据的。其实就是调用的Dao层的方法。
 */
// 是在这里添加Service 注解。表明这是Service层, 不是在FurnService添加@Service
@Service
public class FurnServiceImp implements FurnService {
    // 注入FurnMapper接口对象(代理对象)
    @Resource		// @Resource和@Autowired都是自动装配的意思。spring里的知识。
    private FurnMapper furnMapper;
    @Override
    public void save(Furn furn) {
        furnMapper.insertSelective(furn);	// //因为 id 是自增长的,所以是部分字段,选择 insertSelective
    }
}

番外篇:老韩说明: 修改 Furn.java , 当创建 Furn 对象 imgPath 为 null 时, imgPath 给默认值

修改全参构造器

    public Furn(Integer id, String name, String maker, BigDecimal price, Integer sales, Integer stock, String imgPath) {
        this.id = id;
        this.name = name;
        this.maker = maker;
        this.price = price;
        this.sales = sales;
        this.stock = stock;
        if(StringUtils.hasText(imgPath))
            this.imgPath = imgPath;
    }
  1. 创 建D:\idea_java_projects\ssm_vue\ssm\ssm\src\test\java\com\hspedu\furns\service\FurnServiceTest.java ,测试方法
package com.test.servicec;

import furn.bean.Furn;
import furn.service.FurnService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.math.BigDecimal;
import java.util.List;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/25 20:49
 */
public class FurnServiceTest {
    private ApplicationContext ioc;
    private FurnService furnService;

    @Before
    public void init() {
        ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        furnService = ioc.getBean(FurnService.class);
        System.out.println("furnService=" + furnService.getClass());    // furnService=class com.sun.proxy.$Proxy23
    }

    @Test
    public void saveTest() {
        // 测试FurnService中的save()方法
        Furn furn = new Furn(null, "小MacBook", "Apple", new BigDecimal(90), 666, 7, "assets/images/product-image/4.jpg");

        // 这里的furnService是一个代理对象,通过控制反转IOC注入到BeanFactory中,然后调用真正执行这个 save方法
        furnService.save(furn);
        System.out.println("添加成功");
    }
}

  1. 创 建D:\idea_java_projects\ssm_vue\ssm\ssm\src\main\java\com\hspedu\furns\bean\Msg.java
    用来返回 json 的数据的通用类(这里是前端和后端的数据传输,通过json格式的数据进行传送)
package furn.bean;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/25 22:32
 * 后端程序返回给前端Json数据的Msg对象
 * 本质就是一个协议、规则、规范。
 */
public class Msg {
    // 200 表示成功 400 表示失败
    private int code;
    // 返回的信息
    private String msg;
    // 返回给浏览器的数据
    private Map<String, Object> map = new HashMap<>();
    // 成功时返回的Msg
    public static Msg success () {
        Msg msg1 = new Msg();
        msg1.setCode(200);
        msg1.setMsg("success");
        return msg1;
    }
    // 失败时返回的对象
    public static Msg fail() {
        Msg msg1 = new Msg();
        msg1.setCode(400);
        msg1.setMsg("fail!");
        return msg1;
    }
    public Msg add(String key, Object value) {
        map.put(key,value);
        return this;    // 返回的就是当前的msg。
    }
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Map<String, Object> getMap() {
        return map;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }


}

  1. 创建 FurnController.java , 处理添加请求
package furn.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import furn.bean.Furn;
import furn.bean.Msg;
import furn.service.FurnService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;

/**
 * @Author: GQLiu
 * @DATE: 2024/3/27 9:17
 */
@Controller
public class FurnController {
    // required = false 表示自动装配,找不到不会报错,直接设置为Null
    @Autowired(required = false)
    private FurnService furnService;

    // 保存家具信息
    @PostMapping("/save")    // 访问路径 加上 save
    @ResponseBody                  // 将JavaBean以Json格式返回给前端(这里前端也会收到写入的数据) 如果没有这个, 会认为你去找一个页面,而不是json数据. 因此去找save.html页面
    //Errors errors这个地方全忘了
    public Msg save(@Validated @RequestBody Furn furn, Errors errors) {
        // furnService.save(furn);
        // System.out.println("发送成功");
        // return Msg.success();
        HashMap<String, Object> map = new HashMap<>();
        List<FieldError> fieldErrors = errors.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            map.put(fieldError.getField(), fieldError.getDefaultMessage());
        }
        if(map.isEmpty()) {
            // 没有错误,才提交。
            furnService.save(furn);
            return Msg.success();
        } else {
            return Msg.fail().add("errorMsg", map);
        }

    }

}

  1. 使用 Postman 来完成 Controller 层的测试, 通过 Postman 添加 Furn 数据

使用 Postman 测试时,因为我们前台是发送的 json 数据,被服务器接收到后,转成
javabean 数据,因此 pom.xml 需要引入 jackson,处理 json 数据, 否则后台会报错 .Content
type ‘application/json;charset=UTF-8’ not supported

在pom.xml maven配置文件中添加

<!-- 引入 jackson,处理 json 数据 -->
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.12.4</version>
</dependency>

在这里插入图片描述
在这里插入图片描述
6. 点 击 添 加 按 钮 , 可 以 出 现 添 加 家 居 的 对 话 框 , 修 改C:\Users\Administrator\Desktop\desktop\d\SSM-Vue 整 合 项 目-temp\ssm_vue\src\views\HomeView.vue , 老韩说明 el-dialog 从 Dialog 对话框获取, 表单代码从 Form 表单获取,组合一下 并调整一下即可
在这里插入图片描述

        说明:
        1. el-dialog :v-model="dialogVisible" 表示对话框, 和 dialogVisible 变量双向
        绑定,控制是否显示对话框
        2. el-form :model="form" 表示表单 ,数据和 form 数据变量双向绑定
        3. el-input v-model="form.name" 表示表单的 input 控件,名字为 name 需要和
        后台 Javabean【Furn】 属性一致
        4. 前端中,对象的属性是可以动态生成的。比如下面的form初始为空,下面直接设置就会直接出现form.name、form.maker等等
        -->
        <el-dialog title="提示" v-model="dialogVisible" width="30%">
            <el-form :model="form" label-width="120px" :rules="rules" ref="form">
                <el-form-item label="家居名" prop="name">
                    <el-input v-model="form.name" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="厂商" prop="maker">
                    <el-input v-model="form.maker" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="价格" prop="price">
                    <el-input v-model="form.price" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="销量" prop="sales">
                    <el-input v-model="form.sales" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="库存" prop="stock">
                    <el-input v-model="form.stock" style="width: 80%"></el-input>
                </el-form-item>
            </el-form>
            <template #footer>
                <span class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                    <!-- @click="dialogVisible = false" 按说应该写一个方法, 因为是一句话,所以可以这样直接写-->
                <el-button type="primary" @click="save">确 定</el-button>
                </span>
            </template>
        </el-dialog>


//增加数据, 一定要, 否则你会发现,在后面 弹出的表单不能输入数据
data() {
        return {
            dialogVisible: false,
            form: {},    // 定义一个空表单, 用来接收save 的元素。
            tableData: [],

}

//增加方法
methods: {
	add() {
		this.dialogVisible = true
		this.form = {}
	}
}

//增加点击新增的按钮事件
<div style="margin: 10px 0">
	<el-button type="primary" @click="add">新增</el-button>
	<el-button>其它</el-button>
</div>
  1. 完成测试: 看看点击新增按钮,能否正常的弹窗添加家居的对话框(含有表单)
    在这里插入图片描述

  2. 项目前端 安装 axios, 用于发送 Ajax 请求给后台, 一定要注意
    在这里插入图片描述

  3. 创 建 工 具 文 件 D:\idea_java_projects\ssm_vue\src\utils\request.js , 用 于 创 建 axios request 对象

//
// 这一部分是前端搞的,后端不用学的太明白
// 直接钞!
// 引入axios包
import axios from 'axios'
// 通过axios 创建对象
const request = axios.create({
    timeout:5000
})

// 设置 request 拦截器
// 1. 可以对请求做一些处理
// 2. 比如统一加token, Content-type等等
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    return config;
}, error => {
    return Promise.reject(error)
});

// 设置 response 拦截器
// 可以在调用接口相应后, 统一的处理返回结果.
request.interceptors.response.use(
    response =>{
        let res = response.data;
        // 如果返回的是文件, 就直接返回.
        if(response.config.responseType === 'blob')
            return res;
        // 如果是 string, 就转为json对象
        if(typeof res === 'string'){
            // 如果res 不为null, 就转换成json对象
            res = res ? JSON.parse(res) : res;
        }
        return res;
    }, error => {
        console.log("error=", error);
        return Promise.reject(error);
    }
)


// 导出
export default request  // 是一个axios对象

在这里插入图片描述
9. 创建 D:\idea_java_projects\ssm_vue\vue.config.js 解决跨域问题, 因为修改了配置文件npm serve 需要重启, 否则不能识别

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer:{
    port:10001,  //设置启动窗口为10000
    // 解读: 如果要访问的url是 /api/save, 那么就会倍代理到 http://localhost:8080/ssm/save
    proxy:{     // 设置代理, 必须填
      "/api":{  // 设置拦截器 拦截器格式:斜杠+拦截器名字, 名字可以自己定.
        target:"http://localhost:8080/furn_ssm", // 代理的目标地址, 就是 /api 代替 http://localhost:8080/furn_ssm
        changeOrigin:true,                // 是否设置同源, 设置为true则表示允许跨域访问
        pathRewrite:{                     // 路径重写(如果拦截器里有api就忽略)
          '/api':''                       // 选择忽略拦截器里面的单词.
        }
      }
    }
  },

})


  1. 修改 Home.vue, 使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功
        // save() 将填写的表单对象发送给后端
save() {
	// =======说明====... request.post("/api/save", this.form).then(res => {
	console.log(res)
	this.dialogVisible = false
	})
}
  1. 老韩提醒, 这里容易出现的问题
  1. 一定要确定 request.post(“/api/save”) 被代理后的 url , 是项目后台服务对应提供的 API接口 url, 否则报 404
  2. 当跨域执行时请求,浏览器还是提示 http://localhost:5927/api/xxx , 所以不要认为是 api没有替换你的配置

5.4 注意事项和细节

  1. Postman 测试时, 要指定 content-type ,否则会报错 415
    在这里插入图片描述

  2. 如果需要将提交的 json 数据, 封装到对应的 Javabean, 需要配置@RequestBody , 否则会报错 500

  3. 如果需要返回 json 数据, 需要在方法上 , 配置@ResponseBody , 否则会报错 404

6 实现功能 05-显示家居信息

在这里插入图片描述

6.2 思路分析

  1. 完成后台代码从 dao -> serivce -> controller , 并对每层代码进行测试
  2. 完成前台代码, 使用 axios 发送 http 请求,返回所有家居数据, 将数据绑定显示

6.3 代码实现

  1. 修改 FurnService.java 和 FurnServiceImpl.java, 增加 findAll 方法
/**
* 查询所有家居
* @return
*/
public List<Furn> findAll();
/**
* 返回所有家居数据,传入 null 即可
* @return
*/
@Override
public List<Furn> findAll() {
	return furnMapper.selectByExample(null);
}
  1. 修改 FurnServiceTest.java ,测试 findAll.
    @Test
    // 查找所有的Furn
    public void findAllTest() {
        List<Furn> furns = furnService.findAll();
        for (Furn furn : furns) {
            System.out.println(furn);
        }
    }

测试成功//。。。。

  1. 修改 后端 FurnController.java , 处理显示请求, 并使用 Postman 完成测试
    @RequestMapping("/showAll")
    @ResponseBody
    public Msg showAll() {
        List<Furn> furns = furnService.findAll();
        Msg msg = Msg.success();
        msg.add("furnList", furns);
        return msg;
    }

  1. 修改 HomeView.vue , 编写 list 方法
    在这里插入图片描述
//在 created() 调用 list() 完成页面数据获取
created() {
	this.list()
}

在methods中,调用list

        list() {
             用于显示所有的家具的list.
             request.post("/api/showAll").then(res => {
                 // 输出一下看看
                 console.log("res=", res);
                 // 这里为什么是 res.data.map.furnList , 可以根据上面的控制台的输出先进行调试 寻找. 然后调试在修改下面的真正路径.
                 // 由于使用拦截器对response进行了统一拦截处理, 让res = res.data, 所以这里就可以直接用res.map.furnList.
                 this.tableData = res.map.furnList;
             })

           
        },
//修改save,在 save() 调用后,调用 list() 刷新页面
save() {
	// =======说明====... 
	request.post("/api/save", this.form).then(res => {
	console.log(res)
	this.dialogVisible = false
	this.list()
	})
}
  1. 完成测试,看看是否可以显示家居列表信息.
    在这里插入图片描述
  2. 修改 src\utils\request.js 增加 response 拦截器, 统一处理响应后结果
//
// 这一部分是前端搞的,后端不用学的太明白
// 直接钞!
// 引入axios包
import axios from 'axios'
// 通过axios 创建对象
const request = axios.create({
    timeout:5000
})

// 设置 request 拦截器
// 1. 可以对请求做一些处理
// 2. 比如统一加token, Content-type等等
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    return config;
}, error => {
    return Promise.reject(error)
});

// 设置 response 拦截器
// 可以在调用接口相应后, 统一的处理返回结果.
request.interceptors.response.use(
    response =>{
        let res = response.data;
        // 如果返回的是文件, 就直接返回.   (blob表示是文件)
        if(response.config.responseType === 'blob')
            return res;
        // 如果是 string, 就转为json对象
        if(typeof res === 'string'){
            // 如果res 不为null, 就转换成json对象
            res = res ? JSON.parse(res) : res;
        }
        return res;
    }, error => {
        console.log("error=", error);
        return Promise.reject(error);
    }
)


// 导出
export default request  // 是一个axios对象

  1. 修改 Home.vue , 简化返回处理
    在这里插入图片描述
  2. 完成测试.
    在这里插入图片描述

7 实现功能 06-修改家居信息

在这里插入图片描述

7.2 思路分析

  1. 完成后台代码从 dao(mapper) -> serivce -> controller , 并对每层代码进行测试
  2. 完成前台代码, 回显家居信息,再使用 axios 发送 http / ajax 请求,更新数据, 将数据绑
    定显示

7.3 代码实现

  1. 修改 FurnService.java 和 FurnServiceImpl.java, 增加 update 方法
public void update(Furn furn);
================================
@Override
public void update(Furn furn) {
	//因为传入的 furn 的字段不一定是完整的,所以使用 updateByPrimaryKeySelective
	furnMapper.updateByPrimaryKeySelective(furn);
}
  1. 修改 FurnServiceTest.java ,测试 update
@Test
public void update() {
Furn furn =
	new Furn(1, "优雅风格沙发", "顺平家居", new BigDecimal(10), 666, 7,null);
	furnService.update(furn);
	System.out.println("update ok");
}
  1. 修改 FurnController.java , 处理修改请求, 并使用 Postman 完成测试
    @PutMapping("/update")  // 修改一般是用put请求
    @ResponseBody
    public Msg update(@RequestBody Furn furn) {
        furnService.update(furn);
        return Msg.success();
    }
  1. 修改 HomeView.vue , 编写 handleEdit 方法, 回显数据 并测试

4.1 方法一
修改FurnController,增加根据id选择JavaBean

    @GetMapping("/select/{id}")  // 查询一般用Get
    @ResponseBody       // 返回数据是一个json, 所以要用ResponseBody
    public Msg select(@PathVariable Integer id) {	//@PathVariable将URL中的id传递给这个形参id
        System.out.println("查询的id=" + id);
        Furn furn = furnService.select(id);
        System.out.println("查到的furn=" + furn);
        Msg msg = Msg.success();
        msg.add("furn", furn);
        return msg;
    }

修改HomeView.vue


        handleEdit(row) {
            // console.log("row--", row);    // 可以在控制台中显示
            // 方式1: 可以通过row.id 到后端DB获取对应的家具信息, 返回后将其绑定到this.form
            // 好处;永远取出最新的。
            // 坏处:每次都要去后端DB获取,去查询数据库,所以很慢!
            // console.log("row=",row.id);
            // console.log(typeof row.id);
            request.get(`/api/select/` + row.id).then(res => {
                // 得到数据:
                console.log("res.map.furn=", res.map.furn);
                console.log("typeof res", typeof res);
                this.form = res.map.furn;
            });
            this.dialogVisible = true;

            // 方式2: 把获取到的前端的row的数据通过处理, 直接绑定到this.form进行显示,不需要再找到后端。
            // 好处:快。坏处:拿到的数据可能不是最新的。
            // JSON.stringify(row) 将row转成json字符串.
            // JSON.parse() 表示将json字符串转为json对象
            // this.form = JSON.parse(JSON.stringify(row));
            // this.dialogVisible = true;
        },


//触发 handleEdit 方法
// 修改上面的按钮
<el-button @click="handleEdit(scope.row)" type="text">编辑</el-button>

在这里插入图片描述

4.2方法二

这种方法主要将前端表格中的数据直接拿过来。不需要走后端DB,但是可能会导致拿到的数据不是最新的数据。
主要修改handleEdit方法:

        handleEdit(row) {
            // console.log("row--", row);    // 可以在控制台中显示

             ///方式2: 把获取到的前端的row的数据通过处理, 直接绑定到this.form进行显示,不需要再找到后端。
             //好处:快。坏处:拿到的数据可能不是最新的。
             JSON.stringify(row) 将row转成json字符串.
             JSON.parse() 表示将json字符串转为json对象
             this.form = JSON.parse(JSON.stringify(row));
             this.dialogVisible = true;
        },
  1. 修改HomeView.vue , 修改save方法, 处理修改请求, 说明 更新成功的消息框, 不需要做额外处理, 直接使用 this.$message 即可.
    在这里插入图片描述
        // save() 将填写的表单对象发送给后端
        save() {
            if (this.form.id) {
                // 表单中有id,说明是修改。 修改一般用put
                // request 本质是发出ajax请求-异步处理
                // 所以 this.dialogVisible = false; this.list(); 这两句不能写在if(res.code==200)和else后面。
                // 因为是异步,所以发出请求到后端时,前端早已经把后面的代码this.list()方法执行完毕了。所以说这样即使修改了数据库,也不会更新到前台。
                request.put("/api/update", this.form).then(res => {
                    if (res.code == 200) {
                        // 更改成功
                        this.$message({
                            // 弹出更新成功的消息框
                            type: "success",
                            message: "更新成功"
                        })
                        this.dialogVisible = false;
                        this.list();    //  刷新列表
                    } else {
                        // 更改失败
                        this.$message({
                            // 弹出更新失败的消息框
                            type: "error",
                            message: "更新失败"
                        })
                        this.dialogVisible = false;
                        this.list();    //  刷新列表
                    }

                })
            } else {
                // 表示是添加
                // 对表单数据校验,判断能否通过
                this.$refs['form'].validate((valid) => {
                    if(valid) {
                        // 表示是新增
                        // request 是一个axios对象,所以有各种各样的get post方法
                        // url:"http://localhost:8080/furn_ssm/"
                        // this.form 表示携带的数据
                        this.dialogVisible = true;
                        request.post("/api/save", this.form).then(res => {
                            console.log("res=", res);   // 这里用 , 不用 +
                            this.dialogVisible = false;   //
                        // 调用完save方法后, 需要调用一下list, 将刚刚添加进去的数据显示出来
                        this.list();
                        })
                    }
                    else {
                        // 不合法
                        // 弹出消息框,更新失败
                        this.$message({
                            type:"error",
                            message:"验证失败哦,不能提交啦"
                        })
                        return false;
                    }
                })
            }
        },
  1. 完成测试, 浏览器 http://localhost:10000/

7.4 注意事项和细节

  1. <template #default=“scope”>
    插槽机制:

  2. 调用 list() 刷新数据需要注意的地方说明

request 本质是发出ajax请求-异步处理
所以 this.dialogVisible = false; this.list(); 这两句不能写在if(res.code==200)和else后面,写一句,而应该在每个里面都写。

因为是异步,所以发出请求到后端时,前端早已经把后面的代码this.list()方法执行完毕了。所以说这样即使修改了数据库,也不会更新到前台。

8 实现功能 07-删除家居信息

在这里插入图片描述

8.2 思路分析

  1. 完成后台代码从 dao -> serivce -> controller , 并对每层代码进行测试
  2. 完成前台代码,使用 axios 发送 http Ajax 请求,删除数据, 将数据绑定显示

8.3 代码实现

  1. 修改 FurnService.java 和 FurnServiceImpl.java, 增加 del 方法
    // 删除 根据id
    public void del(Integer id);


    @Override
    public void del(Integer id) {
        furnMapper.deleteByPrimaryKey(id);
    }
  1. 修改 FurnServiceTest.java ,测试 del.
    这里测试Service层是否有用
    自信可以不写。
    但是要想确保这个模块没错,就得写
    在这里插入图片描述
  2. 修改 FurnController.java , 处理删除请求, 并使用 Postman 完成测试
    @DeleteMapping("/del/{id}")     // 访问这个网址:http://localhost:8080/furn_ssm/del/31
    @ResponseBody
    public Msg del(@PathVariable Integer id) {
        furnService.del(id);
        System.out.println("删除完成");
        return Msg.success();
    }

修改前端:
4. 修改 HomeView.vue , 编写 handleDel 方法, 完成删除

handleDel(id) {
            console.log("id=", id);
            request.delete("/api/del/" + id).then(res => {
                if (res.code === 200) {
                    // 删除成功了
                    this.$message(
                        {
                            type: "success",
                            message: "更新成功"
                        }
                    )
                } else {
                    // 删除失败
                    this.$message(
                        {
                            type: "error",
                            message: "更新失败"
                        }
                    )
                }

                // 刷新页面数据
                this.list();
            })
        }

                    <!--
                    @confirm:如果点击的是 确定 , 则会触发handleDel
                    如果点击的是 取消 , 就不会触发handleDel
                    -->
                    <el-popconfirm title="确认删除吗?" @confirm="handleDel(scope.row.id)">
                        <template #reference>
                            <el-button type="danger" size="small">删除</el-button>
                        </template>
                    </el-popconfirm>
  1. 完成测试测试
    在这里插入图片描述

9 实现功能 08-分页显示列表

在这里插入图片描述

9.2 思路分析

  1. 后台使用 MyBatis PageHelper 插件完成分页查询, 前端我们使用分页组件
  2. 修改 FurnController , 增加处理分页显示代码 API/接口
  3. 完成前台代码,加入分页导航,并将分页请求和后台接口结合

9.3 代码实现

  1. 修改 pom.xml 加入分页插件
    <!--引入mybatis pageHelper-->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.2.1</version>
    </dependency>
  1. 修改 mybatis-config.xml, 配置分页拦截器
    <!--配置分页拦截器-->
    <!--注意:plugins标签要放在typeAliases标签后面-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--分页合理化,如果pageNum > pages, 就让他查询最后一页-->
            <property name="reasonable" value="true"/>
        </plugin>

    </plugins>
  1. 修改 FurnController.java 增加分页查询处理
    @RequestMapping("/furnsByPage")
    @ResponseBody
    public Msg ListFurnsBypage(@RequestParam(defaultValue = "1") Integer pageNum,
                               @RequestParam(defaultValue = "5") Integer pageSize) { // @RequestParam可以从url中拿到参数
        // 查询前,需要调用PageHelper.startPage()
        // 参数:startPage(int pageNum, int pageSize)
        PageHelper.startPage(pageNum, pageSize);
        // 在PageHelper.startPage() 后调用findAll 就是分页查询(物理分页有limit)
        List<Furn> furns = furnService.findAll();
        /*
        分页查询完之后, 可以使用pageInfo来包装查询后的结果
        只需要将 pageInfo 交给页面即可.
        pageInfo 封装了详细的分页信息, 包括查询出来的数据. 如总共有多少页, 当前第几页等
        */
        PageInfo pageInfo = new PageInfo(furns, pageSize);
        return Msg.success().add("pageInfo", pageInfo);
    }
  1. 使用 Postman 进行测试,看看分页查询是否 OK
    在这里插入图片描述

前端:
5. 修改 HomeView.vue , 完成分页导航显示、分页请求
在这里插入图片描述
在这里插入图片描述


        <!--增加element-plus分页控件-->
        <div style="margin: 10px 0">
            <el-pagination
                    @size-change="handlePageSizeChange"
                    @current-change="handleCurrentChange"
                    :current-page="currentPage"
                    :page-sizes="[5,10]"
                    :page-size="pageSize"
                    layout="total, sizes, prev, pager, next, jumper"
                    :total="total">
            </el-pagination>
        </div>

data(){
	return{
		 // 增加分页相应的配置
            currentPage: 1,     // 当前页
            pageSize: 5,        // 每页显示的记录数
            total: 10,           // 总共有多少条记录
	}
}

//修改 list(), 换成分页请求数据
        list() {
            // 修改分页数据
             request.get("/api/furnsByPage", {
                 params: {    // 指定请求携带的参数
                     pageNum: this.currentPage,
                     pageSize: this.pageSize
                 }
             }).then(res => {    // 处理返回的分页信息
                 this.tableData = res.map.pageInfo.list
                 this.total = res.map.pageInfo.total
             })

        // 增加方法, 处理记录的变化。这两个方法是和分页空间绑定的。
        // 处理 每页显示多少条记录变化
        handlePageSizeChange(pageSize) {
            this.pageSize = pageSize;
            this.list();
        },
        // 处理当前页变化,如点击分页连接,或go to第几页
        handleCurrentChange(pageNum) {
            this.currentPage = pageNum;
            this.list();
        },

分页效果:
在这里插入图片描述

10 实现功能 09-带条件查询分页显示列表

在这里插入图片描述

10.2 思路分析

  1. 完成后台代码从 dao -> serivce -> controller , 并对每层代码进行测试
  2. 完成前台代码,使用 axios 发送 http 请求,完成带条件查询分页显示

10.3 代码实现

  1. 修改 FurnService.java 和 FurnServiceImpl.java , 增加条件查询
    // 增加 条件查询
    public List<Furn> findByCondition(String name);


    @Override
    public List<Furn> findByCondition(String name) {
        // FurnExample 可以带很多查询条件 的 查询
        FurnExample furnExample = new FurnExample();
        // BeanExample的查询条件通过Criteria设置
        FurnExample.Criteria criteria = furnExample.createCriteria();
        if(!(null == name || "".equals(name))) {
            criteria.andNameLike("%" + name + "%");
        }

        return furnMapper.selectByExample(furnExample);
    }
  1. 修改 FurnController.java , 处理带条件分页查询
    @RequestMapping("/furnsBySearchPage")
    @ResponseBody
    public Msg listFurnsByConditionPage(@RequestParam(defaultValue = "1") Integer pageNum,
                                        @RequestParam(defaultValue = "5") Integer pageSize,
                                        @RequestParam(defaultValue = "")  String search) {
        // 查询前,需要调用PageHelper.startPage()
        // 参数:startPage(int pageNum, int pageSize)
        PageHelper.startPage(pageNum, pageSize);
        // 在PageHelper.startPage() 后调用findByCondition
        List<Furn> furns = furnService.findByCondition(search);
        /*
        分页查询完之后, 可以使用pageInfo来包装查询后的结果
        只需要将 pageInfo 交给页面即可.
        pageInfo 封装了详细的分页信息, 包括查询出来的数据. 如总共有多少页, 当前第几页等
        */
        PageInfo pageInfo = new PageInfo(furns, pageSize);
        return Msg.success().add("pageInfo", pageInfo);
    }
  1. 使用 Postman 测试,是否通过
    > 在这里插入图片描述
    前端:
  2. 修改 HomeView.vue , 完成带条件分页查询
        <div style="margin: 10px 5px">
            <el-input v-model="search" style="width: 240px" placeholder="请输入关键字"/>
            <el-button style="margin-left: 10px" type="primary" @click="list">查询</el-button>
        </div>

=======在数据池,增加 search 变量=
在这里插入图片描述
========修改 list 方法,请求带条件分页的 API 接口=

	list() {
            // 改成按照条件查询
            request.get("/api/furnsBySearchPage", {
                params: {    // 指定请求携带的参数
                    pageNum: this.currentPage,
                    pageSize: this.pageSize,
                    search: this.search
                }
            }).then(res => {    // 处理返回的分页信息
                this.tableData = res.map.pageInfo.list
                this.total = res.map.pageInfo.total
            })
        },

10.4 测试分页条件查询

在这里插入图片描述

测试分页:
在这里插入图片描述

11 实现功能 10-添加家居表单前端校验

在这里插入图片描述
在这里插入图片描述
说明: 参考 element-plus 表单验证
在这里插入图片描述

11.2 思路分析

  1. 完成前台代码,使用 ElementPlus 的表单 rules 验证即可
  2. 参考 ElementPlus 的表单验证文档

11.3 代码实现

  1. 修改 HomeView.vue , 增加表单验证处理代码
 data() {
        return {
			// 增加对表单各个字段的校验规则
            rules: {
                name: [
                    {required: true, message: '请输入称家居名', trigger: 'blur'}
                ],
                maker: [
                    {required: true, message: '请输入称制造商', trigger: 'blur'}
                ],
                price: [
                    {required: true, message: '请输入价格', trigger: 'blur'}, {
                        pattern: /^(([1-9]\d*)|(0))(\.\d+)?$/,
                        message: '请输入数字',
                        trigger: 'blur'
                    }
                ],
                sales: [
                    {required: true, message: '请输入销量', trigger: 'blur'}, {
                        pattern: /^(([1-9]\d*)|(0))$/,
                        message: '请输入数字',
                        trigger: 'blur'
                    }
                ],
                stock: [
                    {required: true, message: '请输入库存', trigger: 'blur'},
                    {pattern: /^(([1-9]\d*)|(0))$/, message: '请输入数字', trigger: 'blur'}
                ]
            }

==指定将创建的规则应用到 form 表单, 注意名称要对应=
在这里插入图片描述
2. 测试,就可以看到验证规则生效了【是光标离开输出框时,出现校验效果,因为是 trigger:‘blur’ 事件】, 但是用户提交还是能成.
在这里插入图片描述
3. 修改 Homeview.vue 当表单验证不通过时,不提交表单

修改 save()===
首先添加下面这个,不然会报错:Cannot read properties of undefined (reading 'validate')
在这里插入图片描述
在这里插入图片描述

主要看后半段:
主要语句:this.$refs['form'].validate((valid) => {

save() {
            if (this.form.id) {
                // 表单中有id,说明是修改。 修改一般用put
                    // request 本质是发出ajax请求-异步处理
                    // 所以 this.dialogVisible = false; this.list(); 这两句不能写在if(res.code==200)和else后面。
                    // 因为是异步,所以发出请求到后端时,前端早已经把后面的代码this.list()方法执行完毕了。所以说这样即使修改了数据库,也不会更新到前台。
                    request.put("/api/update", this.form).then(res => {
                    if (res.code == 200) {
                        // 更改成功
                        this.$message({
                            // 弹出更新成功的消息框
                            type: "success",
                            message: "更新成功"
                        })
                        this.dialogVisible = false;
                        this.list();    //  刷新列表
                    } else {
                        // 更改失败
                        this.$message({
                            // 弹出更新失败的消息框
                            type: "error",
                            message: "更新失败"
                        })
                        this.dialogVisible = false;
                        this.list();    //  刷新列表
                    }

                })
            } else {
                // 表示是添加
                // 对表单数据校验,判断能否通过
                this.$refs['form'].validate((valid) => {
                    if(valid) {
                        // 表示是新增
                        // request 是一个axios对象,所以有各种各样的get post方法
                        // url:"http://localhost:8080/furn_ssm/"
                        // this.form 表示携带的数据
                        this.dialogVisible = true;
                        request.post("/api/save", this.form).then(res => {
                            console.log("res=", res);   // 这里用 , 不用 +
                            this.dialogVisible = false;   //
                        // 调用完save方法后, 需要调用一下list, 将刚刚添加进去的数据显示出来
                        this.list();
                        })
                    }
                    else {
                        // 不合法
                        // 弹出消息框,更新失败
                        this.$message({
                            type:"error",
                            message:"验证失败哦,不能提交啦"
                        })
                        return false;
                    }
                })
            }
        },

=修改 add()==

        add() {
            // 显示对话框
            this.dialogVisible = true;
            // 清空表单数据,防止上次的残留在这里
            this.form = {}

            // 清空上次校验的信息(不然还有个XX不能为空在里面)
            // 这里没有这个 if判断会报错:TypeError: Cannot read properties of undefined (reading 'resetFields')
            if (this.$refs['form'])
            {
                this.$refs['form'].resetFields();
            }

        },

11.4 完成测试

在这里插入图片描述

12 实现功能 11-添加家居表单后端校验

在这里插入图片描述
在这里插入图片描述
2. 后端校验-需求分析 , 当后端校验没有通过,会出现灰色框提示, 后台不真正入库数据
在这里插入图片描述

12.2 思路分析

  1. 后台 使用 JSR303 数据校验,引入 hibernate-validator.jar ,学 SpringMVC 讲过
  2. 前台 使用 ElementPlus 进行数据绑定,并显示错误信息

12.3 代码实现

  1. 修改 pom.xml 引入 hibernate-validator jar 文件
    <!-- JSR303 数据校验支持
    引入 hibernate-validator
    -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.1.0.Final</version>
    </dependency>
  1. 修改 Furn.java , 使用 hibernate-validator
    在这里插入图片描述
    在这里插入图片描述
  2. 修改 FurnController.java , 对 save 方法进行完善
    没有error才会提交
    @PostMapping("/save")    // 访问路径 加上 save
    @ResponseBody                  // 将JavaBean以Json格式返回给前端(这里前端也会收到写入的数据) 如果没有这个, 会认为你去找一个页面,而不是json数据. 因此去找save.html页面
    //Errors errors这个地方全忘了
    public Msg save(@Validated @RequestBody Furn furn, Errors errors) {
        // furnService.save(furn);
        // System.out.println("发送成功");
        // return Msg.success();
        HashMap<String, Object> map = new HashMap<>();
        List<FieldError> fieldErrors = errors.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            map.put(fieldError.getField(), fieldError.getDefaultMessage());
        }
        if(map.isEmpty()) {
            // 没有错误,才提交。
            furnService.save(furn);
            return Msg.success();
        } else {
            return Msg.fail().add("errorMsg", map);
        }

    }