1.数据库准备

部门tbl_dept

员工tbl_emp

建立员工和部门的外键

2.在IDEA创建SSM项目环境

2.1配置Web模块

最上面的图是错误示范,注意!!! 在Tomcat配置了项目路径,就不需要再webapp这里配置项目路径,不然是找不到这里面的资源的!!!!!!!

2.2 引入Maven的SSM相关依赖

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.apache.maven.plugins</groupId>
  5. <artifactId>maven-compiler-plugin</artifactId>
  6. <version>2.3.2</version>
  7. <configuration>
  8. <source>1.8</source>
  9. <target>1.8</target>
  10. <encoding>UTF-8</encoding>
  11. </configuration>
  12. </plugin>
  13. <!-- mvn mybatis-generator:generate -->
  14. <plugin>
  15. <groupId>org.mybatis.generator</groupId>
  16. <artifactId>mybatis-generator-maven-plugin</artifactId>
  17. <version>1.3.2</version>
  18. <configuration>
  19. <verbose>true</verbose>
  20. <overwrite>true</overwrite>
  21. <!--这个是指代从哪里加载generatorConfig文件-->
  22. <configurationFile>
  23. src/main/resources/generator/generatorConfig.xml
  24. </configurationFile>
  25. </configuration>
  26. <dependencies>
  27. <!-- 数据库驱动 -->
  28. <!--mysql-->
  29. <dependency>
  30. <groupId>mysql</groupId>
  31. <artifactId>mysql-connector-java</artifactId>
  32. <version>${mysql.version}</version>
  33. </dependency>
  34. </dependencies>
  35. </plugin>
  36. </plugins>
  37. </build>
  38. <properties>
  39. <project.build.source>UTF-8</project.build.source>
  40. <!--版本同一管理-->
  41. <mybatis.version>3.2.8</mybatis.version>
  42. <mybatis.spring.version>1.2.2</mybatis.spring.version>
  43. <mysql.version>5.1.32</mysql.version>
  44. </properties>
  45. <dependencies>
  46. <dependency>
  47. <groupId>org.springframework</groupId>
  48. <artifactId>spring-webmvc</artifactId>
  49. <version>3.2.17.RELEASE</version>
  50. </dependency>
  51. <!--spring-tx-->
  52. <dependency>
  53. <groupId>org.springframework</groupId>
  54. <artifactId>spring-tx</artifactId>
  55. <version>3.2.17.RELEASE</version>
  56. </dependency>
  57. <!--jdbc-->
  58. <dependency>
  59. <groupId>org.springframework</groupId>
  60. <artifactId>spring-jdbc</artifactId>
  61. <version>3.2.1.RELEASE</version>
  62. </dependency>
  63. <!--面向切面AOP依赖-->
  64. <dependency>
  65. <groupId>org.springframework</groupId>
  66. <artifactId>spring-aspects</artifactId>
  67. <version>3.1.0.RELEASE</version>
  68. </dependency>
  69. <dependency>
  70. <groupId>javax.servlet</groupId>
  71. <artifactId>jstl</artifactId>
  72. <version>1.2</version>
  73. </dependency>
  74. <dependency>
  75. <groupId>javax.servlet</groupId>
  76. <artifactId>javax.servlet-api</artifactId>
  77. <version>3.0.1</version>
  78. </dependency>
  79. <!--连接池-->
  80. <dependency>
  81. <groupId>c3p0</groupId>
  82. <artifactId>c3p0</artifactId>
  83. <version>0.9.1.2</version>
  84. </dependency>
  85. <!--mysql-->
  86. <dependency>
  87. <groupId>mysql</groupId>
  88. <artifactId>mysql-connector-java</artifactId>
  89. <version>${mysql.version}</version>
  90. </dependency>
  91. <!--mybatis-->
  92. <!-- MyBatis -->
  93. <dependency>
  94. <groupId>org.mybatis</groupId>
  95. <artifactId>mybatis</artifactId>
  96. <version>${mybatis.version}</version>
  97. </dependency>
  98. <dependency>
  99. <groupId>org.mybatis</groupId>
  100. <artifactId>mybatis-spring</artifactId>
  101. <version>${mybatis.spring.version}</version>
  102. </dependency>
  103. <!--日志用log4j-->
  104. <dependency>
  105. <groupId>org.slf4j</groupId>
  106. <artifactId>slf4j-api</artifactId>
  107. <version>1.7.25</version>
  108. </dependency>
  109. <dependency>
  110. <groupId>org.slf4j</groupId>
  111. <artifactId>slf4j-log4j12</artifactId>
  112. <version>1.7.25</version>
  113. </dependency>
  114. <dependency>
  115. <groupId>log4j</groupId>
  116. <artifactId>log4j</artifactId>
  117. <version>1.2.17</version>
  118. </dependency>
  119. <!--junit测试-->
  120. <dependency>
  121. <groupId>junit</groupId>
  122. <artifactId>junit</artifactId>
  123. <version>4.9</version>
  124. </dependency>
  125. </dependencies>

注意1:spring的相关依赖版本要一致,不然maven加载其他相关依赖的时候,会有不同版本的相同依赖也会加载进来,很可能会冲突。而且从maven仓库又下载就会很慢。

注意2: 当你在spring配置文件中死活都不出来某个标签的时候,很可能你在maven里导错包,首先需要重新导入正确的依赖。然后把这个xml上面错误的xsd约束删除掉,就可以了。

applicationContext-service.xml

  1. <context:component-scan base-package="cn.zhanp.ssm_crud.service"></context:component-scan>
  2. <!--配置事务-->
  3. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  4. <property name="dataSource" ref="dataSource"></property>
  5. </bean>
  6. <aop:config>
  7. <aop:pointcut id="txPointCut" expression="execution(* cn.zhanp.ssm_crud.service..*(..))"></aop:pointcut>
  8. <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
  9. </aop:config>
  10. <tx:advice id="txAdvice">
  11. <tx:attributes>
  12. <tx:method name="get*" read-only="true"/>
  13. </tx:attributes>
  14. </tx:advice>

注意点3:当你在IDEA中无法识别另外一个如applicationContext-dao中的数据源的引用bean时,应该在IDEA的spring Module中把spring配置文件都添加进去,同一个applicationContext就可以识别了

2.3 部署到Tomcat

2.4 配置MBG插件,逆向工程

  1. <!-- mvn mybatis-generator:generate -->
  2. <plugin>
  3. <groupId>org.mybatis.generator</groupId>
  4. <artifactId>mybatis-generator-maven-plugin</artifactId>
  5. <version>1.3.2</version>
  6. <configuration>
  7. <verbose>true</verbose>
  8. <overwrite>true</overwrite>
  9. <configurationFile>src/main/resources/generator/generatorConfig.xml</configurationFile>
  10. </configuration>
  11. <dependencies>
  12. <!-- 数据库驱动 -->
  13. <!--mysql-->
  14. <dependency>
  15. <groupId>mysql</groupId>
  16. <artifactId>mysql-connector-java</artifactId>
  17. <version>${mysql.version}</version>
  18. </dependency>
  19. </dependencies>
  20. </plugin>

2.5 编写SSM各层的配置文件和数据库文件以及日志文件

springmvc.xml

  1. <!--加载spring的配置文件-->
  2. <context-param>
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>classpath:</param-value>
  5. </context-param>
  6. <!--监听器,加载context-param里的配置文件-->
  7. <listener>
  8. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  9. </listener>
  10. <servlet>
  11. <servlet-name>dispatcherServlet</servlet-name>
  12. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  13. <init-param>
  14. <!--加载springmvc的配置文件-->
  15. <param-name>contextConfigLocation</param-name>
  16. <param-value>classpath:</param-value>
  17. </init-param>
  18. <load-on-startup>1</load-on-startup>
  19. </servlet>
  20. <servlet-mapping>
  21. <servlet-name>dispatcherServlet</servlet-name>
  22. <url-pattern>/</url-pattern>
  23. </servlet-mapping>

总结:别忘了监听器,不配置监听器,根本就没人去帮你加载那几个配置文件

mybatis-config.xml

  1. <configuration>
  2. <settings>
  3. <!--全局下划线转驼峰命名-->
  4. <setting name="mapUnderscoreToCamelCase" value="true"></setting>
  5. <!-- mybatis3的 SQL日志打印方式 -->
  6. <!--<setting name="logImpl" value="log4j"/>-->
  7. </settings>
  8. <!--其余配置都在spring的xml中配置,通过属性注入到sqlSessionFactory的bean中,不在这里写-->
  9. </configuration>

applicationContext-dao.xml

  1. <!--配置数据源-->
  2. <context:property-placeholder location="classpath:db/db.properties"></context:property-placeholder>
  3. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  4. <property name="driverClass" value="${jdbc.driver}"></property>
  5. <property name="jdbcUrl" value="${jdbc.url}"></property>
  6. <property name="user" value="${jdbc.username}"></property>
  7. <property name="password" value="${jdbc.password}"></property>
  8. <!-- c3p0连接池的私有属性 -->
  9. <property name="maxPoolSize" value="30" />
  10. <property name="minPoolSize" value="10" />
  11. <!-- 关闭连接后不自动commit -->
  12. <property name="autoCommitOnClose" value="false" />
  13. <!-- 获取连接超时时间 -->
  14. <property name="checkoutTimeout" value="10000" />
  15. <!-- 当获取连接失败重试次数 -->
  16. <property name="acquireRetryAttempts" value="2" />
  17. </bean>
  18. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  19. <property name="dataSource" ref="dataSource"></property>
  20. <property name="typeAliasesPackage" value="cn.zhanp.ssm_crud.model"></property>
  21. <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
  22. <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
  23. </bean>
  24. <!--注册一个特殊的sqlSession用于测试的时候支持批量插入-->
  25. <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  26. <constructor-arg ref="sqlSessionFactory"></constructor-arg>
  27. <constructor-arg value="BATCH"></constructor-arg>
  28. </bean>
  29. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  30. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
  31. <property name="basePackage" value="cn.zhanp.ssm_crud.dao"></property>
  32. </bean>
  33. </beans>

总结:sqlSessionTemplate,用于注册一些特殊的sqlSession

  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
  2. this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  3. }

总结2: error-------通配符的匹配很全面, 但无法找到元素 'aop:config' 的声明。

解决:依旧是把xsd依赖重新写入(一定要注意mvc和aop这些的xsd约束引入是否正确)

总结3: 提示Error creating bean with name 'org.springframework.cache.interceptor.CacheInterceptor

又是xsd依赖引入错误

2.6 把依赖添加入到Artifact中

3.Spring单元测试模拟数据测试mapper

总结:

  1. <!--在spring单元测试中,由于引入validator而导致的Tomcat7及以下的EL表达式版本不一致-->
  2. <dependency>
  3. <groupId>org.apache.tomcat</groupId>
  4. <artifactId>tomcat-el-api</artifactId>
  5. <version>8.5.24</version>
  6. <scope>provided</scope>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.apache.tomcat</groupId>
  10. <artifactId>tomcat-jasper-el</artifactId>
  11. <version>8.5.24</version>
  12. <scope>provided</scope>
  13. </dependency>

4. 配置pageHelper,实现分页

参考官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

mybatis-config.xml

  1. <!--配置分页插件,也就是指定拦截器-->
  2. <plugins>
  3. <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
  4. </plugins>
  1. <!--分页插件-->
  2. <dependency>
  3. <groupId>com.github.pagehelper</groupId>
  4. <artifactId>pagehelper</artifactId>
  5. <version>5.1.2</version>
  6. </dependency>

5. 分页的后台代码

  1. @RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
  2. public ModelAndView getEmps(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
  3. // 实现分页,默认大小一页显示5条
  4. PageHelper.startPage(page_num, 5);
  5. List<EmployeeCustom> list = employeeService.getAllWithDept();
  6. // 用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
  7. PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);
  8. ModelAndView modelAndView = new ModelAndView();
  9. modelAndView.setViewName("empList");
  10. modelAndView.addObject("pageInfo",pageInfo);
  11. return modelAndView;
  12. }

总计:

  1. 在controller的方法里 要有一个@RequestParam(value="",defaultValue="") xxx

    要求从前端传入第几页pageNumber, 又或者传两个,一个是页码,一个是每页的数目size,

    因为我们很可能会首页就跳转到这里查看,所以很可能没携带页码参数,所以设置一个默认值

    defaultValue,这样就可以实现了。

  2. PageHelper.startPage(page_num, 5);

    用静态方法去让mybatis设置的pageHelper的拦截器对紧跟着的第一个select()方法进行分页,并设置size

  3. PageInfo pageInfo = new PageInfo<>(list,5);

    用pageInfo 来包装查询后的结果,这样可以把结果包装进pageInfo对象中,

    并且第二个参数是navigatepageNums: 是以当前分页为基数,计算导航页码的个数

    举个例子:

    比如当前页码为1,那么navigatepageNums五个,就是1,2,3,4,5。

    当前页码为5,那么navigatepageNums五个,就是3,4,5,6,7 尽可能地以5为中心展开分页导航

    并且能够获取相关分页的其他参数,比如total啊,判断是不是首页啊,有没有上一页,下一页啊这些方法。

    1. //PageInfo包含了非常全面的分页属性
    2. assertEquals(1, page.getPageNum());
    3. assertEquals(10, page.getPageSize());
    4. assertEquals(1, page.getStartRow());
    5. assertEquals(10, page.getEndRow());
    6. assertEquals(183, page.getTotal());
    7. assertEquals(19, page.getPages());
    8. assertEquals(1, page.getFirstPage());
    9. assertEquals(8, page.getLastPage());
    10. assertEquals(true, page.isFirstPage());
    11. assertEquals(false, page.isLastPage());
    12. assertEquals(false, page.isHasPreviousPage());
    13. assertEquals(true, page.isHasNextPage());

6. bootstrap后台界面显示

总结的小问题:一开始不出现字体图标,为什么呢?

答:因为我只在项目中引入了bootstrap的css和js,而字体图标虽然是以class来标识,但是底层实现是要根据

fonts来匹配图标文件的,后缀为这种:

所以要引入bootstrap的fonts文件,并且和js文件夹在同等级目录结构

注意:SpringMVC静态文件的配置,一定要配置过滤器,不要拦截这个

  1. <servlet>
  2. <servlet-name>dispatcherServlet</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <!--加载springmvc的配置文件-->
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>classpath*:spring/springmvc.xml</param-value>
  8. </init-param>
  9. <load-on-startup>1</load-on-startup>
  10. </servlet>
  11. <servlet-mapping>
  12. <servlet-name>dispatcherServlet</servlet-name>
  13. <url-pattern>/</url-pattern>
  14. </servlet-mapping>
  15. <servlet-mapping>
  16. <servlet-name>default</servlet-name>
  17. <url-pattern>*.js</url-pattern>
  18. <url-pattern>*.css</url-pattern>
  19. <url-pattern>/assets/*"</url-pattern>
  20. <url-pattern>/images/*</url-pattern>
  21. <url-pattern>/static/*</url-pattern>
  22. </servlet-mapping>

作用:在web.xml文件中经常看到这样的配置default,这个配置的作用是:对客户端请求的静态资源如图片、JS文件等的请求交由默认的servlet进行处理,也就是不经过DispatcherServlet的拦截

  1. <div class="row">
  2. <table class="table table-hover">
  3. <tr>
  4. <th>#</th>
  5. <th>empName</th>
  6. <th>gender</th>
  7. <th>email</th>
  8. <th>deptName</th>
  9. <th>操作</th>
  10. </tr>
  11. <c:forEach var="page" items="${pageInfo.list}">
  12. <tr>
  13. <td>${page.empId}</td>
  14. <td>${page.empName}</td>
  15. <td>${page.gender}</td>
  16. <td>${page.email}</td>
  17. <td>${page.department.deptName}</td>
  18. <td>
  19. <button type="button" class="btn btn-sm btn-primary">
  20. <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
  21. 修改
  22. </button>
  23. <button type="button" class="btn btn-sm btn-danger">
  24. <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
  25. 删除
  26. </button>
  27. </td>
  28. </tr>
  29. </c:forEach>
  30. </table>
  31. </div>
  32. <div class="row">
  33. <div class="col-md-6">
  34. 当前总记录数:${pageInfo.total}
  35. </div>
  36. <div class="col-md-6" style="text-align: center">
  37. <nav class="pagination">
  38. <ul class="pagination">
  39. <li>
  40. <a href="${pageContext.request.contextPath}/emps?pn=1">首页</a>
  41. </li>
  42. <%--判断上一页是否可用--%>
  43. <c:if test="${pageInfo.hasPreviousPage==true}">
  44. <li>
  45. <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.prePage}" aria-label="Previous">
  46. <span aria-hidden="true">&laquo;</span>
  47. </a>
  48. </li>
  49. </c:if>
  50. <c:forEach items="${pageInfo.navigatepageNums}" var="pageNum">
  51. <li>
  52. <a href="${pageContext.request.contextPath}/emps?pn=${pageNum}">${pageNum}</a>
  53. </li>
  54. </c:forEach>
  55. <c:if test="${pageInfo.hasNextPage==true}">
  56. <li>
  57. <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.nextPage}" aria-label="Previous">
  58. <span aria-hidden="true">&raquo;</span>
  59. </a>
  60. </li>
  61. </c:if>
  62. <li>
  63. <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.pages}">末页</a>
  64. </li>
  65. </ul>
  66. </nav>
  67. </div>
  68. </div>

7.统一JSON 响应类的编写

前提:

  1. 为什么要JSON?因为可以针对不同的前端,不管是浏览器,小程序,安卓移动端等等,都可以通过JSON

数据来达到数据交互的效果。而不用拘泥于以前的浏览器页面Html和jsp这些。

  1. 有了JSON,就可以在实际项目中快速分工。后端传JSON数据,前端通过Ajax向后端提供的接口获取JSON数据,解析JSON数据,渲染进页面中,即可完成交互。而不需要像以前那样,通过JSP等后端模板引擎,来在服务端渲染。造成前后端的开发耦合。
  1. // 要导入jackson包,SpringMVC的ResponseBody才能起作用
  2. @RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
  3. @ResponseBody
  4. public Msg getEmpsWithJson(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
  5. // 实现分页,默认大小一页显示5条
  6. PageHelper.startPage(page_num, 5);
  7. List<EmployeeCustom> list = employeeService.getAllWithDept();
  8. // 用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
  9. PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);
  10. // 封装进JSON 响应类
  11. Msg msg = Msg.success().add("pageInfo", pageInfo);
  12. return msg;
  13. }
  1. public class Msg {
  2. private int code; //代码
  3. private String msg; //信息
  4. //用户要返回给浏览器的数据,最好用一个map对象,前端可以直接取出这个map['key']这样的方式来取出需要的数据
  5. //而且设置为Map的好处,通过返回Msg对象,可以进行链式添加,map中有多个键值对,可以传多个对象
  6. private Map<String,Object> extend = new HashMap<>();
  7. //返回Msg对象,进行链式操作
  8. public static Msg success(){
  9. Msg msg = new Msg();
  10. msg.setCode(200);
  11. msg.setMsg("处理成功");
  12. return msg;
  13. }
  14. public static Msg fail(){
  15. Msg msg = new Msg();
  16. msg.setCode(500);
  17. msg.setMsg("处理失败");
  18. return msg;
  19. }
  20. //添加要返回的主体数据(先通过success和fail,拿到Msg,就可以进行链式操作了)
  21. public Msg add(String key,Object value){
  22. this.getExtend().put(key,value);
  23. return this;
  24. }
  25. //此处省略getter,setter

8.用vue重构Jquery拼接的前端分离页面

  1. window.baseURL = "http://localhost:8080/ssm_crud/"
  2. $(document).ready(function () {
  3. window.empList = new Vue({
  4. el:"#empList",
  5. data:{
  6. emps:[]
  7. },
  8. method:{
  9. },
  10. created(){
  11. window.getEmpsIndex();
  12. // this.$forceUpdate() ;
  13. }
  14. })
  15. })
  16. function getEmpsIndex() {
  17. console.log(baseURL+"emps");
  18. $.get(baseURL+"emps",
  19. {
  20. pn:1
  21. },
  22. function (result) {
  23. //一定要加上设置为window.xxx ,不然他就是一个新的引用,而不是上面的vue对象
  24. window.empList.emps = result.extend.pageInfo.list;
  25. });
  26. }

总结:

  1. 为了让页面一开始就触发函数,获取首页数据,需要用到vue的生命周期函数

  2. 而且需要注意到JS的作用域,如果写成empList.emps = result.extend.pageInfo.list;

    而不是window.empList.emps,那么JS就会新建一个empList对象,这样vue对象实际上没有获取到值。

    这也是我以前用的方法,用window,保证操作的是同一个vue对象。

  3. 上面的方法的原因其实是:我忘记了ES5的特性,因为在里面的那个function函数中,this已经指向了全局变量window, 而不是当前的vue实例了,所以设置不了data值。

    要用let self = this来保存this的引用,又或者用ES6的箭头函数

  1. methods:{
  2. getEmps:function (pn) {
  3. $.get(baseURL+"emps",
  4. {
  5. pn:pn
  6. },
  7. function (result) {
  8. console.log(this); //查看可知这个this又指向了window对象
  9. self.emps = result.extend.pageInfo.list;
  10. self.pageInfo = result.extend.pageInfo;
  11. console.log("pages"+self.pageInfo.pages); //测试
  12. console.log("pn"+pn);
  13. });
  14. }
  15. }
  1. <div id="empList" class="container">
  2. <h1>SSM_CRUD</h1>
  3. <div class="row" style="text-align: center">
  4. <div class="col-md-offset-8">
  5. <button class="btn btn-info">新增</button>
  6. <button class="btn btn-danger">删除</button>
  7. </div>
  8. </div>
  9. <div class="row">
  10. <table class="table table-hover">
  11. <tr>
  12. <th>#</th>
  13. <th>empName</th>
  14. <th>gender</th>
  15. <th>email</th>
  16. <th>deptName</th>
  17. <th>操作</th>
  18. </tr>
  19. <!--用vue重构列表渲染-->
  20. <!--<c:forEach var="page" items="${pageInfo.list}">-->
  21. <tr v-for="emp in emps">
  22. <td>{{emp.empId}}</td>
  23. <td>{{emp.empName}}</td>
  24. <td>{{emp.gender}}</td>
  25. <td>{{emp.email}}</td>
  26. <td>{{emp.department.deptName}}</td>
  27. <td>
  28. <button type="button" class="btn btn-sm btn-primary">
  29. <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
  30. 修改
  31. </button>
  32. <button type="button" class="btn btn-sm btn-danger">
  33. <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
  34. 删除
  35. </button>
  36. </td>
  37. </tr>
  38. <!--</c:forEach>-->
  39. </table>
  40. </div>
  41. <!--分页界面代码-->
  42. <div class="row">
  43. <div class="col-md-6">
  44. 当前总记录数:{{pageInfo.total}}
  45. </div>
  46. <div class="col-md-6" style="text-align: center">
  47. <nav class="pagination">
  48. <ul class="pagination">
  49. <li>
  50. <span @click="getEmps(1)">首页</span>
  51. </li>
  52. <li v-if="pageInfo.hasPreviousPage">
  53. <span aria-hidden="true" @click="getEmps(pageInfo.prePage)">&laquo;</span>
  54. </li>
  55. <li v-for="pageNum in pageInfo.navigatepageNums">
  56. <span @click="getEmps(pageNum)">{{pageNum}}</span>
  57. </li>
  58. <li v-if="pageInfo.hasNextPage">
  59. <span aria-hidden="true" @click="getEmps(pageInfo.nextPage)">&raquo;</span>
  60. </li>
  61. <li>
  62. <span @click="getEmps(pageInfo.pages)">末页</span>
  63. </li>
  64. </ul>
  65. </nav>
  66. </div>
  67. </div>
  68. </div>

实现选中当前分页,当前分页高亮加深的效果。

  1. <li v-for="pageNum in pageInfo.navigatepageNums" :class="{active:pageNum==pageInfo.pageNum}">
  2. <span @click="getEmps(pageNum)">{{pageNum}}</span>
  3. </li>

总结 : 动态class的语法一定要加{}

9. 添加员工

简介:通过以模态框的方式来编写 添加员工的界面。 在点击新增按钮后,让模态框显示。

怎么做出来的:通过bootstrap官方文档,拉取组件样式和 模态框model 的JS插件来完成这个界面。

细节:部门名称:是以下拉菜单的形式,所以要ajax获取部门数据。

总结:

  1. 因为有些我不用完全的form表单提交的方式,比如部门数据,是用的ul配li,所以在JS里面获取前端的所有表单数据,然后再手动submit上去校验。也不难,用下JQuery获取值和DOM结点即可

  2. 需要注意的技巧是:

    1. 获取单选按钮选中后的value值,用$('input[name="gender"]:checked').attr('value');

    2. 获取下拉菜单(但又不是select这种写法)选中的值,view层的绑定事件

      1. <ul class="dropdown-menu" aria-labelledby="dLabel">
      2. <!--要获取部门数据id,和name-->
      3. <li v-for="dept in depts" class="pointer" @click="getSelectedDept(dept.deptId,dept.deptName)">
      4. <span>{{dept.deptName}}</span>
      5. </li>
      6. </ul>
    3. 选择部门下拉菜单我是以按钮的形式

      1. <button class="btn btn-sm btn-primary" id="dedt_btn"
      2. type="button" data-toggle="dropdown"
      3. aria-haspopup="true" aria-expanded="false">
      4. 选择部门
      5. <span class="caret"></span>
      6. </button>

配合JS

  1. //获取选择的部门id
  2. getSelectedDept:function (dept_id,dept_name) {
  3. this.selected_dept_id = dept_id;
  4. //按钮提示的回显
  5. $("#dedt_btn").text(dept_name);
  6. console.log(this.selected_dept_id)
  7. },
  8. ```

界面代码:

  1. <style type="text/css">
  2. li span{
  3. /*设置悬浮可点*/
  4. cursor: pointer;
  5. }
  6. #add_emp li:hover{
  7. display: block;
  8. background: #8c8c8c;
  9. padding: 10px;
  10. /*设置悬浮可点*/
  11. cursor: pointer;
  12. }
  13. </style>
  14. <!-- Modal -->
  15. <div class="modal fade" id="add_emp" tabindex="-1" role="dialog" aria-labelledby="add_empLabel">
  16. <div class="modal-dialog" role="document">
  17. <div class="modal-content">
  18. <div class="modal-header">
  19. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  20. <h4 class="modal-title" id="myModalLabel">添加员工</h4>
  21. </div>
  22. <div class="modal-body">
  23. <form class="form-horizontal">
  24. <div class="form-group">
  25. <label for="inputName" class="col-sm-2 control-label">名字</label>
  26. <div class="col-sm-10">
  27. <input type="text" class="form-control" id="inputName" name="name" placeholder="name">
  28. </div>
  29. </div>
  30. <div class="form-group">
  31. <label class="col-sm-2 control-label">性别</label>
  32. <div class="col-sm-10">
  33. <label class="radio-inline">
  34. <input type="radio" name="gender" value="M">
  35. </label>
  36. <label class="radio-inline">
  37. <input type="radio" name="gender" value="F">
  38. </label>
  39. </div>
  40. </div>
  41. <div class="form-group">
  42. <label for="inputEmail" class="col-sm-2 control-label">Email</label>
  43. <div class="col-sm-10">
  44. <input type="email" class="form-control" id="inputEmail" placeholder="Email">
  45. </div>
  46. </div>
  47. <!--下拉菜单-->
  48. <div class="form-group">
  49. <label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
  50. <div class="dropdown col-sm-10">
  51. <button class="btn btn-sm btn-primary" id="dedt_btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  52. 选择部门
  53. <span class="caret"></span>
  54. </button>
  55. <ul class="dropdown-menu" aria-labelledby="dLabel">
  56. <!--要获取部门数据id,和name-->
  57. <li v-for="dept in depts" class="pointer" @click="getSelectedDept(dept.deptId,dept.deptName)">
  58. <span>{{dept.deptName}}</span>
  59. </li>
  60. </ul>
  61. </div>
  62. </div>
  63. </form>
  64. </div>
  65. <div class="form-group">
  66. <div class="modal-footer">
  67. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  68. <button type="button" class="btn btn-primary" @click="add_emp_submit">提交</button>
  69. </div>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  1. //切换模态框的显示
  2. add_emp_toggle:function () {
  3. this.getDepts();
  4. $("#add_emp").modal({
  5. //设置 点击除模态框以外的其他地方,不会让模态框消失掉
  6. backdrop:"static"
  7. });
  8. },
  9. //获取部门信息
  10. getDepts:function () {
  11. let self = this;
  12. $.get(baseURL+"depts",
  13. function (result) {
  14. self.depts = result.extend.depts;
  15. })
  16. },
  17. //获取选择的部门id
  18. getSelectedDept:function (dept_id,dept_name) {
  19. this.selected_dept_id = dept_id;
  20. //按钮提示的回显
  21. $("#dedt_btn").text(dept_name);
  22. console.log(this.selected_dept_id)
  23. },
  24. //增加员工 提交
  25. add_emp_submit:function () {
  26. let empName = $('#inputName').val();
  27. let gender = $('input[name="gender"]:checked').attr('value');
  28. let email = $('#inputEmail').val();
  29. let dId = this.selected_dept_id;
  30. console.log(empName)
  31. console.log(gender)
  32. console.log(email)
  33. console.log(dId)
  34. $.post(baseURL+"/emps/add",
  35. {
  36. empName:empName,
  37. gender:gender,
  38. email:email,
  39. dId:dId
  40. },
  41. function (result) {
  42. if(result.code==200){
  43. //重新获取第一页数据,刷新页面
  44. window.getEmpsIndex();
  45. $("#add_emp").modal('hide');
  46. }
  47. })
  48. }

添加员工 提交数据后 回显最后一页的实现:

  1. //这个是添加员工的提交后的 回调函数
  2. function (result) {
  3. if(result.code==200){
  4. //获取最后一页数据,刷新页面
  5. let lastPage = result.extend.lastPage;
  6. console.log("lastPage:"+lastPage)
  7. self.getEmps(lastPage);
  8. $("#add_emp").modal('hide');
  9. }
  10. })
  1. //要拿到最后一页
  2. @Override
  3. public int getLastPage(int size) {
  4. EmployeeExample employeeExample = new EmployeeExample();
  5. EmployeeExample.Criteria criteria = employeeExample.createCriteria();
  6. criteria.andEmpIdIsNotNull();
  7. int num = employeeMapper.countByExample(employeeExample);
  8. if(num%size==0){
  9. return num/size;
  10. }else{
  11. return num/size+1;
  12. }
  13. }
  1. @RequestMapping(value = "/emps/add",method = RequestMethod.POST)
  2. @ResponseBody
  3. public Msg addEmp(Employee employee){
  4. employeeService.addEmployee(employee);
  5. //要拿到最后一页
  6. //总员工数
  7. int lastPage = employeeService.getLastPage(5);
  8. return Msg.success().add("lastPage",lastPage);
  9. }

9.1 添加员工的校验状态

名字和邮箱的校验和前端显示

前端校验页面的用法

bootstrap摘录:

  1. <div class="form-group has-success">
  2. <label class="control-label" for="inputSuccess1">Input with success</label>
  3. <input type="text" class="form-control" id="inputSuccess1" aria-describedby="helpBlock2">
  4. <span id="helpBlock2" class="help-block">A block of help text that breaks onto a new line and may extend beyond one line.</span>
  5. </div>
  6. <div class="form-group has-warning">
  7. <label class="control-label" for="inputWarning1">Input with warning</label>
  8. <input type="text" class="form-control" id="inputWarning1">
  9. </div>
  10. <div class="form-group has-error">
  11. <label class="control-label" for="inputError1">Input with error</label>
  12. <input type="text" class="form-control" id="inputError1">
  13. </div>
  1. //模态框显示的事件中
  2. add_emp_toggle:function () {
  3. this.getDepts();
  4. $("#add_emp").modal({
  5. //点击背景不删除
  6. backdrop:"static"
  7. });
  8. /**
  9. * 前端JQuery校验
  10. * 1.用户名交给后端检查校验正则表达式,再查看是否重名
  11. * 2.其他的前端校验正则即可
  12. */
  13. $("#inputName").focusout(function () {
  14. let self = this;
  15. let name = $(this).val();
  16. $.post(baseURL+"namechecked",{
  17. empName:name
  18. },
  19. function (result) {
  20. //因为修改的时候,是添加样式,所以可能重复has-success和has-error一起,是不正确的
  21. $(self).parent().removeClass("has-success has-error");
  22. $(self).next().text("");
  23. if(result.code==200){
  24. window.empList.vali_name = true;
  25. //说明成功,正则匹配,且用户名可用
  26. $(self).parent().addClass("has-success");
  27. let msg = result.extend.message;
  28. $(self).next().text(msg);
  29. }else{
  30. window.empList.vali_name = false;
  31. //说明不成功,正则不匹配或者用户名不可用,要拿出错误信息,添加相关样式,以及不许提交
  32. console.log($(this));
  33. $(self).parent().addClass("has-error");
  34. let msg = result.extend.message;
  35. $(self).next().text(msg);
  36. }
  37. });
  38. });
  39. //正则表达式判断其他表单参数
  40. $("#inputEmail").focusout(function () {
  41. $(this).parent().removeClass("has-success has-error");
  42. $(this).next().text("");
  43. let email = $(this).val();
  44. let regx = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
  45. // 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
  46. console.log(regx.test(email));
  47. if(regx.test(email)){
  48. window.empList.vali_email = true;
  49. //符合
  50. //1. 给父元素添加bootstrap 校验成功样式
  51. $(this).parent().addClass("has-success")
  52. }else{
  53. window.empList.vali_email = false;
  54. //不符合
  55. // 1. 给父元素添加bootstrap 校验错误样式
  56. $(this).parent().addClass("has-error");
  57. // 2. 在其下面添加span的提示内容
  58. $(this).next().text("邮件格式错误!首字母必须是字母或数字");
  59. }
  60. });
  61. },
  1. @RequestMapping(value = "/namechecked",method = RequestMethod.POST)
  2. @ResponseBody
  3. public Msg nameChecked(@RequestParam(value = "empName",required = true) String name){
  4. //1. 先正则 2. 后判断是否重复
  5. String regx = "^[[[\u4e00-\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}";
  6. if(name.matches(regx)){
  7. boolean nameChecked = employeeService.getNameChecked(name);
  8. if(nameChecked){
  9. Msg msg = Msg.success().add("message", "用户名可用");
  10. return msg;
  11. }else{
  12. Msg msg = Msg.fail().add("message", "用户名不可用");
  13. return msg;
  14. }
  15. }else{
  16. Msg msg = Msg.fail().add("message", "请填写2-7位中文或者数字或英文");
  17. return msg;
  18. }
  19. }

选择部门和提交表单时候的校验

  1. //获取选择的部门id
  2. getSelectedDept:function (dept_id,dept_name) {
  3. this.selected_dept_id = dept_id;
  4. //按钮的回显
  5. $("#dedt_btn").text(dept_name);
  6. //删掉span提示信息
  7. $("#dept_span").text('');
  8. console.log(this.selected_dept_id)
  9. },
  10. //增加员工 提交
  11. add_emp_submit:function () {
  12. let self = this;
  13. let empName = $('#inputName').val();
  14. let gender = $('input[name="gender"]:checked').attr('value');
  15. let email = $('#inputEmail').val();
  16. let dId = this.selected_dept_id;
  17. if(self.selected_dept_id==''){
  18. $("#dedt_btn").next().css("color","red");
  19. $("#dedt_btn").next().text("请选择部门");
  20. self.vali_dept = false;
  21. }else{
  22. self.vali_dept = true;
  23. //全都校验成功,才可以提交
  24. if(self.vali_name&&self.vali_dept&&self.vali_email){
  25. $.post(baseURL+"emps/add",
  26. {
  27. empName:empName,
  28. gender:gender,
  29. email:email,
  30. dId:dId
  31. },
  32. function (result) {
  33. if(result.code==200){
  34. //获取最后一页数据,刷新页面
  35. let lastPage = result.extend.lastPage;
  36. console.log("lastPage:"+lastPage)
  37. self.getEmps(lastPage);
  38. $("#add_emp").modal('hide');
  39. //清空表单数据
  40. $("#add_emp_form")[0].reset();
  41. $("#add_emp_form").find("*").removeClass("has-error has-success");
  42. $("#add_emp_form").find(".help-block").text('');
  43. //清空校验状态位
  44. self.vali_name = false;
  45. self.vali_dept = false;
  46. self.vali_email = false;
  47. //清空选中的部门
  48. self.selected_dept_id = "";
  49. $("#dedt_btn").text("选择部门");
  50. }
  51. })
  52. }else{
  53. alert("请正确填写表单中的信息,再提交");
  54. }
  55. //提交表单
  56. }
  57. // console.log(empName)
  58. // console.log(gender)
  59. // console.log(email)
  60. // console.log(dId)
  61. }

总结:校验页面的编写

  1. 用正则(前后端都可以,用后端进行重名校验

  2. 通过后端或者前端校验结束后拿到提示信息,根据匹配情况给前端页面设置样式(比如success,error)以及

    span里的提示信息

  3. 然后还要注意当重新聚焦到(通过聚焦focusOut()判断正则和重名)的时候,要注意先清空success,error的class样式,还有清空span里的提示信息

  4. 我是通过标志位判断每个表单参数是否匹配,都匹配了才可以提交Ajax请求提交表单。

  5. 记住:提交完表单后,要在回调函数里清空表单的数据,以及前端界面的一些校验提示的样式和提示信息。

    还要清空我设置的校验标志位,以及这里的下拉菜单选中的部门id

9.2JSR303后端校验

防君子,不防小人的前端JS校验,要通过后端的校验来保证表单参数的正确性。这个时候JSR303应运而生!

  1. <!--JSR303校验-->
  2. <dependency>
  3. <groupId>org.hibernate.validator</groupId>
  4. <artifactId>hibernate-validator</artifactId>
  5. <version>6.0.13.Final</version>
  6. </dependency>

这里要注意点:如果使用Tomcat7及以下的版本,那么validator和Tomcat lib中的EL表达式语法是不匹配的。会提示找不到Class EL....之类的异常

解决方法:下载

往tomcat的lib中添加了el-api-3.0.0的jar包

  1. public class Employee {
  2. private Integer empId;
  3. @Pattern(regexp = "^[[[\\u4e00-\\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}",message = "请填写2-7位中文或者数字或英文")
  4. private String empName;
  5. private String gender;
  6. @Pattern(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$",message = "邮件格式错误!首字母必须是字母或数字")
  7. private String email;
  1. @RequestMapping(value = "/emps/add",method = RequestMethod.POST)
  2. @ResponseBody
  3. public Msg addEmp(@Valid Employee employee, BindingResult result) {
  4. HashMap<String, Object> map = new HashMap<>();
  5. //正则校验有误
  6. if (result.hasErrors()) {
  7. List<FieldError> errors = result.getFieldErrors();
  8. for (FieldError error : errors) {
  9. map.put(error.getField(), error.getDefaultMessage());
  10. }
  11. return Msg.fail().add("errorFields", map);
  12. }
  13. //重名校验有误
  14. String empName = employee.getEmpName();
  15. boolean nameChecked = employeeService.getNameChecked(empName);
  16. if (!nameChecked) {
  17. map.put("empNameDuplicated", "已存在该用户名");
  18. return Msg.fail().add("errorFields", map);
  19. } else {
  20. //校验正确
  21. employeeService.addEmployee(employee);
  22. //要拿到最后一页
  23. //总员工数
  24. int lastPage = employeeService.getLastPage(5);
  25. return Msg.success().add("lastPage", lastPage);
  26. }
  27. }

如何拿到返回的后端校验返回的信息,去做其他的处理

  1. //增加员工 提交
  2. add_emp_submit:function () {
  3. let self = this;
  4. let empName = $('#inputName').val();
  5. let gender = $('input[name="gender"]:checked').attr('value');
  6. let email = $('#inputEmail').val();
  7. let dId = this.selected_dept_id;
  8. if(self.selected_dept_id==''){
  9. $("#dedt_btn").next().css("color","red");
  10. $("#dedt_btn").next().text("请选择部门");
  11. self.vali_dept = false;
  12. }else{
  13. //全都校验成功,才可以提交
  14. if(self.vali_name&&self.vali_dept&&self.vali_email){
  15. $.post(baseURL+"emps/add",
  16. {
  17. empName:empName,
  18. gender:gender,
  19. email:email,
  20. dId:dId
  21. },
  22. function (result) {
  23. //先进入后端校验,防君子,不防小人,所以需要后端JSR303校验
  24. if(result.code==500){
  25. //后端校验,有误
  26. let error_emp_name = result.extend.errorFields.empName;
  27. let error_emp_email = result.extend.errorFields.email;
  28. let error_emp_name_duplicated = result.extend.errorFields.empNameDuplicated;
  29. //说明这个数据校验有误
  30. if(error_emp_name!=undefined){
  31. $("#inputName").parent().addClass("has-error");
  32. $("#inputName").next().text(error_emp_name);
  33. }
  34. if(error_emp_email!=undefined){
  35. $("#inputEmail").parent().addClass("has-error");
  36. $("#inputEmail").next().text(error_emp_email);
  37. }
  38. if(error_emp_name_duplicated!=undefined){
  39. $("#inputName").parent().addClass("has-error");
  40. $("#inputName").next().text(error_emp_name_duplicated);
  41. }
  42. } else if(result.code==200){
  43. //获取最后一页数据,刷新页面
  44. let lastPage = result.extend.lastPage;
  45. console.log("lastPage:"+lastPage)
  46. self.getEmps(lastPage);
  47. $("#add_emp").modal('hide');
  48. //清空表单数据
  49. $("#add_emp_form")[0].reset();
  50. $("#add_emp_form").find("*").removeClass("has-error has-success");
  51. $("#add_emp_form").find(".help-block").text('');
  52. //清空校验状态位
  53. self.vali_name = false;
  54. self.vali_dept = false;
  55. self.vali_email = false;
  56. //清空选中的部门
  57. self.selected_dept_id = "";
  58. $("#dedt_btn").text("选择部门");
  59. }
  60. })
  61. }else{
  62. alert("请正确填写表单中的信息,再提交");
  63. }

10. 修改员工

10.1 模态框

总结:我上面的添加员工 那个下拉菜单有问题,,,是自己写的,而不是用select组件,所以浪费了很多时间获取数据和绑定事件。下面对修改框做了调整

  1. <!-- 修改员工Modal -->
  2. <div class="modal fade" id="update_emp_modal" tabindex="-1" role="dialog" aria-labelledby="update_empLabel">
  3. <div class="modal-dialog" role="document">
  4. <div class="modal-content">
  5. <div class="modal-header">
  6. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  7. <h4 class="modal-title">修改员工</h4>
  8. </div>
  9. <div class="modal-body">
  10. <form class="form-horizontal" id="update_emp_form">
  11. <div class="form-group">
  12. <label class="col-sm-2 control-label">名字</label>
  13. <div class="col-sm-10">
  14. <p class="form-control-static" id="update_Name">{{emp.empName}}</p>
  15. </div>
  16. </div>
  17. <div class="form-group">
  18. <label class="col-sm-2 control-label">性别</label>
  19. <div class="col-sm-10">
  20. <label class="radio-inline">
  21. <input type="radio" name="gender" value="M" :checked="'M'==emp.gender?'checked':''">
  22. </label>
  23. <label class="radio-inline">
  24. <input type="radio" name="gender" value="F" :checked="'F'==emp.gender?'checked':''">
  25. </label>
  26. </div>
  27. </div>
  28. <div class="form-group">
  29. <label for="update_Email" class="col-sm-2 control-label">Email</label>
  30. <div class="col-sm-10">
  31. <input type="email" class="form-control" name="email" id="update_Email" v-bind:value="emp.email">
  32. <span class="help-block"></span>
  33. </div>
  34. </div>
  35. <!--下拉菜单-->
  36. <div class="form-group">
  37. <label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
  38. <div class="col-sm-10">
  39. <select class="form-control" name="dId">
  40. <option v-for="dept in depts" v-bind:value="dept.deptId">{{dept.deptName}}</option>
  41. </select>
  42. </div>
  43. </div>
  44. </form>
  45. </div>
  46. <div class="form-group">
  47. <div class="modal-footer">
  48. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  49. <button type="button" class="btn btn-primary" id="update_emp_button" @click="update_emp_submit(pageInfo.pageNum)">提交</button>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. </div>

10.2 修改员工的JS

把校验函数封装一下,代码复用。增加,修改都可以用。通过传入相应的选择器即可。

  1. //封装前端校验邮件函数,通过传入相应的(输入框的)选择器
  2. validate_email:function(ele){
  3. //正则表达式判断其他表单参数
  4. $(ele).focusout(function () {
  5. $(ele).parent().removeClass("has-success has-error");
  6. $(ele).next().text("");
  7. let email = $(ele).val();
  8. let regx = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
  9. // 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
  10. console.log(regx.test(email));
  11. if(regx.test(email)){
  12. window.empList.vali_email = true;
  13. //符合
  14. //1. 给父元素添加bootstrap 校验成功样式
  15. $(ele).parent().addClass("has-success")
  16. }else{
  17. window.empList.vali_email = false;
  18. //不符合
  19. // 1. 给父元素添加bootstrap 校验错误样式
  20. $(ele).parent().addClass("has-error");
  21. // 2. 在其下面添加span的提示内容
  22. $(ele).next().text("邮件格式错误!首字母必须是字母或数字");
  23. }
  24. });
  25. },

模态框的显示:包含了修改员工的信息的回显,以及表单校验,和模态框显示

  1. //修改模态框的显示
  2. updateEmp:function(empId){
  3. let self = this;
  4. //获取该员工的信息
  5. $.get(baseURL+"emps/"+empId,
  6. function (result) {
  7. self.emp = result.extend.emp;
  8. let gender = result.extend.emp.gender;
  9. let dId = result.extend.emp.dId;
  10. console.log("gender:"+gender)
  11. //给下拉菜单赋值
  12. $("#update_emp select").val(dId);
  13. //要把empId传递到修改模态框的提交
  14. $("#update_emp_button").attr("edit-id",empId);
  15. });
  16. //获取部门数据
  17. this.getDepts();
  18. //显示模态框
  19. $("#update_emp_modal").modal({
  20. backdrop:"static"
  21. });
  22. //表单校验
  23. this.validate_email("#update_Email");
  24. },

提交修改员工表单: 前后端遵循Restful API风格,修改用put请求

  1. update_emp_submit:function(pageNum){
  2. let empId = $("#update_emp_button").attr("edit-id");
  3. let self = this;
  4. //邮件格式符合
  5. if(self.vali_email){
  6. $.ajax({
  7. url:baseURL+"emps/"+empId,
  8. type:"PUT",
  9. data:$("#update_emp_form").serialize(),
  10. success:function (result) {
  11. if(result.code==200){
  12. //修改成功
  13. //跳转到修改的员工的本页
  14. self.getEmps(pageNum);
  15. //清掉表单数据以及状态位
  16. self.vali_email = false;
  17. $("#update_emp_modal").find('*').removeClass("has-success has-error");
  18. $("#update_emp_modal .help-block").text("");
  19. $("#update_emp_modal").modal('hide');
  20. }else{
  21. //后端校验邮件。。。没必要啊
  22. }
  23. }
  24. });
  25. }else{
  26. //邮件有误,不得更新
  27. alert("请正确填写邮件!")
  28. }
  29. },

10.3 Restful API对put请求的使用

首先引入一些能支持Restful API的过滤器,比如通过携前端提交表单时带_method参数可以把表面上为POST请求转换处理成Rest风格的put或delete

  1. <!-- 浏览器不支持put,delete等method,由该filter将/xxx?_method=delete转换为标准的http delete方法 -->
  2. <filter>
  3. <filter-name>hiddenHttpMethodFilter</filter-name>
  4. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  5. </filter>
  6. <filter-mapping>
  7. <filter-name>hiddenHttpMethodFilter</filter-name>
  8. <url-pattern>/*</url-pattern>
  9. </filter-mapping>

因为Tomcat对于前端提交的表单,内部的实现是:将POST请求的报文,把请求体里的参数封装成一个map。

然后request.getParam("")就是从map里获取键值对。而通过查看Tomcat的源码可知:

  1. /**
  2. * Comma-separated list of HTTP methods that will be parsed according
  3. * to POST-style rules for application/x-www-form-urlencoded request bodies.
  4. */
  5. protected String parseBodyMethods = "POST";
  1. //当Tomcat判断不是POST请求,直接返回,不会对请求体进行处理。
  2. //所以request.getParam()根本获取不到参数
  3. if( !getConnector().isParseBodyMethod(getMethod()) ) {
  4. success = true;
  5. return;
  6. }

所以需要一个转换器,以过滤器的方式来增强request对象,让这个转换器替Tomcat识别put请求,并完成put请求报文的封装。让增强后的request调用getParameter()后能获取表单数据。

  1. <filter>
  2. <filter-name>HttpMethodPutFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>HttpMethodPutFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

控制器:

  1. //获取某一位员工的信息
  2. @RequestMapping(value = "/emps/{empId}",method = RequestMethod.GET)
  3. @ResponseBody
  4. public Msg getEmp(@PathVariable(value = "empId") int empId){
  5. Employee emp = employeeService.getEmp(empId);
  6. return Msg.success().add("emp",emp);
  7. }
  8. //修改一位员工的信息
  9. @RequestMapping(value = "/emps/{empId}",method = RequestMethod.PUT)
  10. @ResponseBody
  11. public Msg updateEmp(@PathVariable(value = "empId") int empId, Employee employee){
  12. employeeService.updateEmp(empId,employee);
  13. return Msg.success();
  14. }

11. 删除员工

  1. // 删除一位员工
  2. @RequestMapping(value = "/emps/{empId}",method = RequestMethod.DELETE)
  3. @ResponseBody
  4. public Msg deleteEmp(@PathVariable(value = "empId") int empId){
  5. employeeService.deleteEmp(empId);
  6. return Msg.success();
  7. }
  1. <button type="button" class="btn btn-sm btn-danger" @click="del_emp(emp.empId,emp.empName)">
  2. <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
  3. 删除
  4. </button>
  1. //删除单个员工
  2. del_emp:function (empId,empName) {
  3. let self = this;
  4. if(confirm("你确认删除【"+empName+"】吗?"))
  5. $.ajax({
  6. url:baseURL+"emps/"+empId,
  7. type:"DELETE",
  8. success:function (result) {
  9. if(result.code==200){
  10. //删除成功
  11. self.getEmps(self.pageInfo.pageNum);
  12. }
  13. }
  14. });
  15. }

12.批量删除

JS批量删除

  1. //批量删除
  2. del_emp_batch:function () {
  3. let self = this;
  4. let checkAll = $("#selectAll").prop("checked");
  5. //可以删除二合一。 多个用-相连接,后端再分离出每个要删除的员工Id
  6. let empIds = new Array();
  7. if(checkAll){
  8. for(let i=0;i<self.emps.length;i++){
  9. console.log(empIds)
  10. empIds.push(self.emps[i].empId);
  11. }
  12. console.log("empIds:"+empIds)
  13. let old_empIds = empIds.join(',');
  14. //用-连起来
  15. console.log(typeof old_empIds)
  16. console.log(typeof empIds)
  17. empIds = empIds.join('-');
  18. if(confirm("你确认删除【"+old_empIds+"】吗")){
  19. $.ajax({
  20. url:baseURL+"emps/batch/"+empIds,
  21. type:"DELETE",
  22. success:function (result) {
  23. if(result.code=200){
  24. self.getEmps(self.pageInfo.pageNum+1);
  25. //把选中框的状态关掉
  26. $("#selectAll").prop("checked",false);
  27. $("input[name=del_emp]:checked").prop("checked",false);
  28. }
  29. }
  30. });
  31. }
  32. }else{
  33. //拿到所有选中的节点
  34. let del_emps = $("input[name=del_emp]:checked");
  35. $.each(del_emps,function (index,del_emp) {
  36. empIds.push($(del_emp).attr("del-id"));
  37. })
  38. let old_empIds = empIds.join(',')
  39. empIds = empIds.join('-');
  40. if(confirm("你确认删除【"+old_empIds+"】吗")){
  41. $.ajax({
  42. url:baseURL+"emps/batch/"+empIds,
  43. type:"DELETE",
  44. success:function (result) {
  45. if(result.code=200){
  46. self.getEmps(self.pageInfo.pageNum);
  47. //把选中框的状态关掉
  48. $("#selectAll").prop("checked",false);
  49. $("input[name=del_emp]").prop("checked",false);
  50. }
  51. }
  52. });
  53. }
  54. }
  55. }

全选,非全选:

  1. //全选和全不选
  2. selectAll:function () {
  3. let checkAll = $("#selectAll").prop("checked");
  4. if(checkAll){
  5. //为该页多选框也选中
  6. $("input[name='del_emp']").prop("checked",true);
  7. }else{
  8. $("input[name='del_emp']").prop("checked",false);
  9. }
  10. },
  11. //选中某一个,当该页所有的都被选中,则全选框自动变checked
  12. selectOne:function (empName,empId) {
  13. let checkAll = $("input[name=del_emp]:checked").length == $("input[name=del_emp]").length;
  14. if(checkAll){
  15. $("#selectAll").prop("checked",true);
  16. }else{
  17. $("#selectAll").prop("checked",false);
  18. }
  19. },

JQuery相关语法

  1. <script type="text/javascript">
  2. var arr = new Array(3)
  3. arr[0] = "George"
  4. arr[1] = "John"
  5. arr[2] = "Thomas"
  6. document.write(arr.join())
  7. </script>
  1. <head>
  2. <title></title>
  3. <script src="jquery-1.9.0.min.js" type="text/javascript"></script>
  4. <script type="text/javascript">
  5. $(function () {
  6. $('input:hidden').each(function (index, obj) {
  7. alert(obj.name + "..." + obj.value);
  8. });
  9. });
  10. </script>
  11. </head>
  12. <body>
  13. <input type="hidden" value="1" name="a"/>
  14. <input type="hidden" value="2" name="b"/>
  15. <input type="hidden" value="3" name="c"/>
  16. </body>

上面这段代码用到了input集合的索引,有用到了input集合的dom对象,可以通过该对象,拿到其对应的属性如:name,value等;

$.each()方法

  1. 该方法处理一维数组,代码如下:
  1. $.each(["aaa","bbb","ccc"],function(index,value){
  2. alert(i+"..."+value);
  3. });

控制器:

  1. //批量删除按钮触发的
  2. @RequestMapping(value = "/emps/{empIds}",method = RequestMethod.DELETE)
  3. @ResponseBody
  4. public Msg deleteEmp(@PathVariable(value = "empIds") String empIds){
  5. if(empIds.contains("-")){
  6. ArrayList<Integer> ids = new ArrayList<>();
  7. String[] split = empIds.split("-");
  8. for(String empId:split){
  9. int id = Integer.parseInt(empId);
  10. ids.add(id);
  11. employeeService.deleteEmpList(ids);
  12. }
  13. }else{
  14. employeeService.deleteEmp(Integer.parseInt(empIds));
  15. }
  16. return Msg.success();
  17. }

总结:多个id用-连接起来,而不是封装成数组直接传过去,让Springmvc做参数绑定。记住这种技巧。

13.文件上传

总结:

  1. 要在spring配置多媒体解析器

    因为MultipartResolver依赖于Apache的这个jar包

    1. <!--文件上传的依赖-->
    2. <dependency>
    3. <groupId>commons-fileupload</groupId>
    4. <artifactId>commons-fileupload</artifactId>
    5. <version>1.3.3</version>
    6. </dependency>

    spring注册Bean

  1. <!--配置多媒体解析器-->
  2. <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  3. <property name="defaultEncoding" value="UTF-8"></property>
  4. <property name="maxInMemorySize" value="#{1024*1024*1024}"></property>
  5. <property name="maxUploadSize" value="#{1024*1024*5}"></property>
  6. </bean>
  1. @Controller
  2. public class FileController {
  3. @RequestMapping(value = "/file",method = RequestMethod.POST)
  4. public String uploadFile(@RequestParam(value = "file") MultipartFile multipartFile, Model model, HttpServletRequest request){
  5. String basePath = "E:/";
  6. if(multipartFile!=null&&!multipartFile.isEmpty()){
  7. //1.获取原始文件名
  8. String originalFilename = multipartFile.getOriginalFilename();
  9. //2.获取前缀
  10. String originalFilenamePrefix = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
  11. //3.封装新的文件名 名字+时间戳
  12. String newFileName = originalFilenamePrefix + new Date().getTime()
  13. + originalFilename.substring(originalFilename.lastIndexOf('.'));
  14. //4. 打开文件流
  15. File file = new File(basePath+newFileName);
  16. //5. 保存文件
  17. try {
  18. multipartFile.transferTo(file);
  19. model.addAttribute("fileName",originalFilename);
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. System.out.println("上传失败");
  23. }
  24. }
  25. return "upload/uploadSuc";
  26. }

14. 拦截器(登陆认证)

拦截器实现的原理:

  1. 实现HandlerInterceptor接口。编写拦截器
  2. 配置拦截器和HandlerMapping的映射关系
  3. 编写控制器
  1. <!--拦截器 和HandlerMapping之间的配置 在HandlerMapping找到相应的Handler之前,指定某个拦截器进行拦截-->
  2. <mvc:interceptors>
  3. <mvc:interceptor>
  4. <mvc:mapping path="/**"/>
  5. <bean class="cn.zhanp.ssm_crud.intercepetor.LoginInteceptor"></bean>
  6. </mvc:interceptor>
  7. </mvc:interceptors>
  1. public class LoginInteceptor implements HandlerInterceptor {
  2. /**
  3. * 这个方法 是在 进入Handler方法之前执行的
  4. * 应用:在这里进行身份认证,身份权限认证等等 不过要放行(登陆页面的url)
  5. */
  6. @Override
  7. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
  8. //1. 如果是登陆页面,放行
  9. String requestURI = httpServletRequest.getRequestURI();
  10. if(requestURI.contains("login") || requestURI.contains("toLogin")){
  11. return true;
  12. }else{
  13. HttpSession session = httpServletRequest.getSession();
  14. String username = (String)session.getAttribute("username");
  15. if(username!=null){
  16. return true;
  17. }else{
  18. // 回到登陆页面
  19. httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/toLogin");
  20. return false;
  21. }
  22. }
  23. }
  24. /**
  25. * 这个方法 是在Handler方法之后,返回ModelAndView之前执行的
  26. * 应用: 公用的一些数据模型(ModelAndView)可以在这里设置,比如导航菜单
  27. */
  28. @Override
  29. public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
  30. }
  31. /**
  32. * 这个方法是在 Handler执行完成后执行
  33. * 应用: 捕捉异常把
  34. */
  35. @Override
  36. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
  37. }

SSM框架CRUD小案例的更多相关文章

  1. JavaWeb_(Struts2框架)Ognl小案例查询帖子

    此系列博文基于同一个项目已上传至github 传送门 JavaWeb_(Struts2框架)Struts创建Action的三种方式 传送门 JavaWeb_(Struts2框架)struts.xml核 ...

  2. ssm框架的小总结

    一.mybatis框架 mybatis框架主要就是完成持久层的实现,简化了持久层的开发, 1.首先是配置文件的编写,我们这里就命名为mybatis-config.xml,先配置文件头,然后加载连接数据 ...

  3. angular前端框架简单小案例

    一.angular表达式 <head> <meta charset="UTF-8"> <title>Title</title> &l ...

  4. 一个DRF框架的小案例

    第一步:安装DRF DRF需要以下依赖: Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) Django (1.10, 1.11, 2.0) DRF是以Django扩展应用的 ...

  5. 一个ssm综合小案例-商品订单管理----写在前面

    学习了这么久,一直都是零零散散的,没有把知识串联起来综合运用一番 比如拦截器,全局异常处理,json 交互,RESTful 等,这些常见技术必须要掌握 接下来呢,我就打算通过这么一个综合案例把这段时间 ...

  6. ssm框架(Spring Springmvc Mybatis框架)整合及案例增删改查

    三大框架介绍 ssm框架是由Spring springmvc和Mybatis共同组成的框架.Spring和Springmvc都是spring公司开发的,因此他们之间不需要整合.也可以说是无缝整合.my ...

  7. ssm框架的搭建实现CRUD的操作

    最近在开发公司的一个系统,系统的框架是用ssm的框架搭建的,当然和这次写博客的不一样,它拥有很多的配置文件,企业级的开发所需要的配置文件是非常繁琐的,今天记录一下一个简单的SSM框架的搭建和实现一个C ...

  8. react框架实现点击事件计数小案例

    下面将以一个小案例来讲解react的框架的一般应用,重点内容在代码段都有详细的解释,希望对大家有帮助 代码块: 代码块: import React from 'react'; import React ...

  9. springmvc文件上传下载简单实现案例(ssm框架使用)

    springmvc文件上传下载实现起来非常简单,此springmvc上传下载案例适合已经搭建好的ssm框架(spring+springmvc+mybatis)使用,ssm框架项目的搭建我相信你们已经搭 ...

随机推荐

  1. Python入门基本语法

      Python入门 以下主要讲述Python的一些基础语法,包含行的缩进在python中的重要意义,python中常见的保留字和引号的使用,如何实现单行注释和多行注释. print("he ...

  2. leetcode-6-basic

    解题思路: 这道题真实地反映了我今晚有多脑残=.=只需要从根号N开始向前找,第一个能被N整除的数就是width,然后存到结果就 可以了.因为离根号N越近,width越大,与length的差越小. ve ...

  3. poj 3669 火星撞地球问题 bfs算法

    题意:火星撞地球,你要跑到一个永远安全的地方,求最短时间 思路:bfs+预处理 这题的数据量比较大,所以需要进行预处理 对每个位置设上时间(被撞的最早时间) 未被撞的设为-1 for (int j = ...

  4. 如何固定电脑IP

    百度经验里有:http://jingyan.baidu.com/article/2f9b480d579fc041cb6cc297.html 但是就关于如何填写DNS时,就不知道咋办了,特意问了一下IT ...

  5. servlet 作用

    什么是Servlet Servlet是一个Java编写的程序,此程序是基于Http协议的,在服务器端运行的(如tomcat),是按照Servlet规范编写的一个Java类. 在BS架构中,早期的Web ...

  6. jmeter进行dubbo接口测试

    最近工作中接到一个需求,需要对一个MQ消息队列进行性能测试,测试其消费能力,开发提供了一个dubbo服务来供我调用发送消息. 这篇博客,介绍下如何利用jmeter来测试dubbo接口,并进行性能测试. ...

  7. VMware Workstation 14 PRO 下安装Ubuntu 16.04 LTS教程

    一.准备好安装的VMware Workstation 14 PRO 1.VMware Workstation 14 PRO下载链接:http://rj.baidu.com/soft/detail/13 ...

  8. 【SDOJ 3741】 【poj2528】 Mayor's posters

    Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...

  9. 【转载】logistic回归

    原文地址:https://www.cnblogs.com/zichun-zeng/p/3824745.html 1. logistic回归与一般线性回归模型的区别: (1)     线性回归的结果变量 ...

  10. 2018"百度之星"程序设计大赛 - 资格赛

    调查问卷  Accepts: 1546  Submissions: 6596  Time Limit: 6500/6000 MS (Java/Others)  Memory Limit: 262144 ...