本节以一个员工管理系统的实例在前两节的基础上继续讲解一些功能。
1.初始化
-
依然是上一个项目,但这次我们引入一个员工管理系统的模板,将页面和静态资源分别导入到项目中,并使用Thymeleaf模板解析器(模板资源链接:https://pan.baidu.com/s/15L8ByzIqhKzaCMo2Re_F3g 提取码:8lbu ):
-
按照上一节在每一个页面引入Thymeleaf,并修改源html标签属性为Thymeleaf,以
index.html
文件为例(修改完成之后页面中就可显示样式了):<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <!-- /表示静态资源根目录 --> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link th:href="@{/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> <label class="sr-only">Username</label> <input type="text" class="form-control" placeholder="Username" required="" autofocus=""> <label class="sr-only">Password</label> <input type="password" class="form-control" placeholder="Password" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body> </html>
-
-
在
MyMvcConfig
文件中重写addViewControllers
方法,也就是实现路由跳转功能(注:此功能不实现国际化跳转就会失败):@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); }
2.国际化
如上图所示,所谓国际化即所有文字能够拥有中文和英文两种格式,以及能够通过下方的链接刷新页面文字显示。
-
在
resources
目录下新建i18n
目录,并在其下新建login.properties
文件和login_zh_CN.properties
文件和login_en_US.properties
文件(IDEA会自动生成文件夹),如图: -
并且点击图中资源包->左上角+号添加属性特性,如图所示建立以下属性对应
index.html
中所有文字显示(注:提前配置好IDEA编码设置,新建文件以UTF-8格式编码): -
探索源码,在
MessageResourceProperties
文件中发现basename
变量,默认值为message
,在application.properties
文件中修改该值,使其默认位置为i18n.login
:# 修改消息默认路径,即国际化语言路径配置 spring.messages.basename=i18n.login
-
修改
index.html
文件中文字(已可以正常使用,不展示图片):<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <!-- /表示静态资源根目录 --> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link th:href="@{/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1> <label class="sr-only">Username</label> <input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus=""> <label class="sr-only">Password</label> <input type="password" class="form-control" th:placeholder="#{login.password}" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me" th:text="#{login.remember}"> </label> </div> <!-- [[]]是行内写法,#是消息语法 --> <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body> </html>
-
查看源码,重写一个地区解析器:
-
查看
WebMvcAutoConfiguration
文件中LocaleResolver
方法:@Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") // 地区解析器,如果用户自己配置则使用用户配置,没有则使用默认配置 public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } // 查看默认配置AcceptHeaderLocaleResolver类 AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; }
-
查看默认配置
AcceptHeaderLocaleResolver
类,发现这个类继承自LocaleResolver
接口,并且重写了接口中相关方法,所以我们类比该默认配置也配置自己的地区解析器并且重写相关方法; -
在
config
目录下新建MyLocaleResolver
文件并继承LocaleResolver
接口重写resolveLocale
和setLocale
方法,重写过程参考AcceptHeaderLocaleResolver
中对应重写方法:<!-- 设置链接,thymeleaf中使用括号传递参数lang --> <a class="btn btn-sm" th:href="@{/index.html(lang='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(lang='en_US')}">English</a>
package com.kun.demoweb.config; import org.springframework.web.servlet.LocaleResolver; import org.thymeleaf.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Locale; public class MyLocaleResolver implements LocaleResolver { // 解析请求 @Override public Locale resolveLocale(HttpServletRequest request) { // 获取请求中的语言参数lang String language = request.getParameter("lang"); System.out.println("Debug==>"+language); // 先获取默认地区,如果没有语言参数则使用默认地区 Locale locale = Locale.getDefault(); // 如果有语言参数,则返回新地区 if(!StringUtils.isEmpty(language)){ String[] langSplit = language.split("_"); locale = new Locale(langSplit[0],langSplit[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }
- 并且在
MyMvcConfig
文件中将该配置注入@Bean
(已生效不展示):
package com.kun.demoweb.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); } // 将自定义的地区解析器注入Bean,即生效 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } }
- 并且在
-
3.登录
-
修改登录按钮表单提交之后的动作:
<!-- 提交表单并请求/user/login路径,由LoginController匹配接收 --> <form class="form-signin" th:action="@{/user/login}">
-
新建一个
LoginController
文件:package com.kun.demoweb.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.thymeleaf.util.StringUtils; import javax.servlet.http.HttpSession; @Controller public class LoginController { // model给视图返回属性数据,session设置会话属性并赋值为username用于dashboard页面显示 // RequestMapping对请求的路径进行识别,/user/login路径则进入login方法 @RequestMapping("/user/login") public String login( @RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){ // 业务功能 if(!StringUtils.isEmpty(username)&&"123456".equals(password)){ session.setAttribute("loginUser",username); // 重定向到/main.html路径 return "redirect:/main.html"; }else { // 传递消息,登录失败 model.addAttribute("msg","用户名或密码错误"); return "index"; } } }
-
在
MyMvcConfig
文件中增加视图映射:// 视图映射 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); // 将所有/main.html路径全部映射到dashboard页面,实质上网页显示路径只是映射视图,并不一定是真实页面名! registry.addViewController("/main.html").setViewName("dashboard"); }
-
新建
LoginHandlerInterceptor
拦截器:package com.kun.demoweb.config; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // 拦截器 public class LoginHandlerInterceptor implements HandlerInterceptor { //重写preHandle方法,在进入跳转之前就拦截 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 登陆成功之后,获取用户session Object loginUser = request.getSession().getAttribute("loginUser"); // 未登陆状态 if(loginUser==null){ // 设置属性提示 request.setAttribute("msg","没有权限,请先登录"); // 转发该请求回到首页 request.getRequestDispatcher("/index.html").forward(request,response); return false; }else{ return true; } } }
-
在
MyMvcConfig
文件中重写拦截器方法:// 重写拦截器方法,addPathPatterns方法拦截所有路径请求,excludePathPatterns方法放行一些请求 // 注意,css等静态资源不放行时则页面无法加载该资源,该资源会被拦截 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","/img/**"); }
-
在
dashboard.html
文件中传递显示session
属性,即登录者信息:<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
完成了登录设置、登录失败提示、跳转路由(取缔登录成功时参数可视)、拦截器(防止未登录直接进入主页)功能。
4.数据设置与显示
- 由于尚未连接数据库,暂时在后台自定义一些数据(注:有些内容涉及Spring,暂未解释)。
-
新建
dao
和pojo
两个包,dao
包围数据访问层,即通过该包下类访问数据库(此处为自定义数据);pojo
包为实体类,即对象存储区:添加依赖:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
Department.java
:package com.kun.demoweb.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; // 部门表 // 创建无参方法 @Data @AllArgsConstructor @NoArgsConstructor public class Department { private Integer id; private String departmentName; }
Employee.java
:package com.kun.demoweb.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; // 员工表 @Data @NoArgsConstructor public class Employee { private Integer id; private String lastName; private String email; // 0 女 1 男 private Integer sex; private Department department; private Date birth; // 自建一个有参构造,不传入出生日期,直接默认生成 public Employee(Integer id, String lastName, String email, Integer sex, Department department) { this.id = id; this.lastName = lastName; this.email = email; this.sex = sex; this.department = department; this.birth = new Date(); } }
DepartmentDao.java
:package com.kun.demoweb.dao; import com.kun.demoweb.pojo.Department; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.HashMap; import java.util.Map; // 部门dao @Repository public class DepartmentDao { // 模拟数据库中数据 private static Map<Integer, Department> departments = null; static { // 创建一个部门表 departments = new HashMap<Integer,Department>(); departments.put(101,new Department(101,"教学部")); departments.put(102,new Department(102,"市场部")); departments.put(103,new Department(103,"教研部")); departments.put(104,new Department(104,"运营部")); departments.put(105,new Department(105,"后勤部")); } // 获取所有部门信息 public Collection<Department> getDepartments(){ return departments.values(); } // 通过id得到部门 public Department getDepartmentById(Integer id){ return departments.get(id); } }
EmployeeDao.java
:package com.kun.demoweb.dao; import com.kun.demoweb.pojo.Department; import com.kun.demoweb.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.Collection; import java.util.HashMap; import java.util.Map; // 员工dao @Repository public class EmployeeDao { // 模拟数据库中数据 private static Map<Integer, Employee> employees = null; // 员工有所属的部门 @Autowired private DepartmentDao departmentDao; static { // 创建一个部门表 employees = new HashMap<Integer,Employee>(); employees.put(1001,new Employee(1001,"AA","A123456789@qq.com",1,new Department(101,"教学部"))); employees.put(1002,new Employee(1002,"BB","B123456789@qq.com",0,new Department(102,"市场部"))); employees.put(1003,new Employee(1003,"CC","C123456789@qq.com",1,new Department(103,"教研部"))); employees.put(1004,new Employee(1004,"DD","D123456789@qq.com",0,new Department(104,"运营部"))); employees.put(1005,new Employee(1005,"EE","E123456789@qq.com",1,new Department(105,"后勤部"))); } // 主键自增 private static Integer initId = 1006; // 增加一个员工 public void addEmployee(Employee employee){ if(employee.getId()==null){ employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId())); employees.put(employee.getId(),employee); } // 查看所有员工信息 public Collection<Employee> getAllEmployees(){ return employees.values(); } // 通过id查询员工 public Employee getEmployeeById(Integer id){ return employees.get(id); } // 通过id删除员工 public void deleteEmployee(Integer id){ employees.remove(id); } }
-
新建
EmployeeController
文件,将员工数据传给前端list.html
文件:在
templates
目录下新建employee
目录存储所有员工页面,并将list.html
文件存入其中;EmployeeController.java
:package com.kun.demoweb.controller; import com.kun.demoweb.dao.EmployeeDao; import com.kun.demoweb.pojo.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Collection; @Controller public class EmployeeController { @Autowired EmployeeDao employeeDao; @RequestMapping("/employees") public String list(Model model){ Collection<Employee> employees = employeeDao.getAllEmployees(); model.addAttribute("emps",employees); return "employee/list"; } }
-
组件拆分与引用,在
templates
目录下新建common
目录,用于存储组件,并创建common.html
文件,从dashboard.html
文件中将顶部导航栏和侧边栏作为组件引入:common.html
:<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- 导航栏,设置为Thymeleaf fragment组件 --> <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar"> <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a> <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search"> <ul class="navbar-nav px-3"> <li class="nav-item text-nowrap"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a> </li> </ul> </nav> <!-- 侧边栏 --> <nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <!-- 判断侧边栏传入的参数进行高亮标记,使用了三元运算符 --> <a th:class="${active=='main'?'nav-link active':'nav-link'}" th:href="@{/main.html}"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"> <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path> <polyline points="9 22 9 12 15 12 15 22"></polyline> </svg> 首页 <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"> <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path> <polyline points="13 2 13 9 20 9"></polyline> </svg> Orders </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart"> <circle cx="9" cy="21" r="1"></circle> <circle cx="20" cy="21" r="1"></circle> <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path> </svg> Products </a> </li> <li class="nav-item"> <!-- 判断侧边栏传入的参数进行高亮标记,使用了三元运算符 --> <a th:class="${active=='employees'?'nav-link active':'nav-link'}" th:href="@{/employees}"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"> <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path> <circle cx="9" cy="7" r="4"></circle> <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path> <path d="M16 3.13a4 4 0 0 1 0 7.75"></path> </svg> 员工管理 </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2"> <line x1="18" y1="20" x2="18" y2="10"></line> <line x1="12" y1="20" x2="12" y2="4"></line> <line x1="6" y1="20" x2="6" y2="14"></line> </svg> Reports </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers"> <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon> <polyline points="2 17 12 22 22 17"></polyline> <polyline points="2 12 12 17 22 12"></polyline> </svg> Integrations </a> </li> </ul> <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted"> <span>Saved reports</span> <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg> </a> </h6> <ul class="nav flex-column mb-2"> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Current month </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Last quarter </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Social engagement </a> </li> <li class="nav-item"> <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Year-end sale </a> </li> </ul> </div> </nav> </html>
将导航栏和侧边栏分别插入
dashboard.html
文件和list.html
文件中相应位置(即复用组件代码,删除原先重复代码):<!-- 引入组件导航栏 --> <div th:replace="~{common/common::topbar}"></div> <!-- 引入组件侧边栏,并且传递参数给组件 --> <!-- 分别为dashboard和list,传参是为了让侧边栏组件判别出当前是在首页或者员工管理页,进而高亮侧边栏相应栏 --> <div th:replace="~{common/common::sidebar(active='main')}"></div> <div th:replace="~{common/common::sidebar(active='employees')}"></div>
-
删除
list.html
页面原本数据,并接收从EmployeeController
传送过来的员工数据,进而展示,并且增加了两个按钮显示(暂不可用):<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <h2>Section title</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>sex</th> <th>department</th> <th>birth</th> <th>操作</th> </tr> </thead> <!-- 这里使用了Thymeleaf提供的工具类,方便调用数据 --> <tbody> <tr th:each="emp:${emps}"> <td th:text="${emp.getId()}"></td> <td th:text="${emp.getLastName()}"></td> <td th:text="${emp.getEmail()}"></td> <!-- 由于存储的数据只有0和1,这里要进行判断 --> <td th:text="${emp.getSex()==0?'女':'男'}"></td> <td th:text="${emp.department.getDepartmentName()}"></td> <!-- 获取的时间要使用工具类进行格式化输出 --> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td> <td> <button class="btn btn-sm btn-primary">修改</button> <button class="btn btn-sm btn-danger">删除</button> </td> </tr> </tbody> </table> </div> </main>
-
增加员工操作:
EmployeeController.java
新增Get和Post获取跳转:@Autowired DepartmentDao departmentDao; // 接收Get请求,并展示添加员工表单页面 @GetMapping("/add_employee") public String toAddEmployeePage(Model model){ // 查询所有部门 Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("depts",departments); return "employee/add_employee"; } // 接收Post请求,添加新员工之后跳转回员工页面 @PostMapping("/add_employee") public String addEmployee(Employee employee){ employeeDao.addEmployee(employee); return "redirect:/employees"; }
list.html
文件中在main
部分的最开始部分增加链接:<h2><a class="btn btn-sm btn-success" th:href="@{/add_employee}">添加员工</a></h2>
employee
目录下新建add_employee.html
文件,内容完全复制list.html
,修改中间main
部分显示表单:<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <!-- 表单提交动作,且提交方法设置为Post --> <form th:action="@{/add_employee}" method="post"> <!-- 姓名文本框 --> <div class="form-group"> <label>LastName</label> <input type="text" class="form-control" name="lastName" placeholder="LastName"> </div> <!-- 邮箱文本框 --> <div class="form-group"> <label>Email</label> <input type="Email" class="form-control" name="email" placeholder="Email"> </div> <!-- 性别单选框 --> <div class="radio"> <label><input type="radio" name="sex" value="1">男</label> </div> <div class="radio"> <label><input type="radio" name="sex" value="0">女</label> </div> <!-- 部门下拉框 --> <div class="form-group"> <label>department</label> <!-- 部门value为id,name提交的必须是id --> <select class="form-control" th:name="department.id"> <option th:each="dept:${depts}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option> </select> </div> <!-- 出生日期 --> <div class="form-group"> <label>Birth</label> <input type="date" class="form-control" name="birth" placeholder="Birth"> </div> <button type="submit" class="btn btn-primary">添加</button> </form> </main>
最后由于表单中date格式提交的数据是yyyy-MM-dd格式,而Spring中默认Date格式为dd/MM/yyyy,所以需要在
application.properties
文件中修改默认配置:# 修改默认Date格式 spring.mvc.format.date=yyyy-MM-dd
-
修改员工操作:
EmployeeController.java
新增修改Get和Post获取跳转:// 接收Get请求,并展示修改员工表单页面,修改时要接收员工Id @GetMapping("/update_employee/{id}") public String toUpdateEmployeePage(@PathVariable("id")Integer id,Model model){ // 根据Id查出员工数据 Employee employee = employeeDao.getEmployeeById(id); model.addAttribute("emp",employee); // 查询所有部门 Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("depts",departments); return "employee/update_employee"; } // 接收Post请求,修改新员工之后跳转回员工页面 @PostMapping("/update_employee") public String updateEmployee(Employee employee){ employeeDao.addEmployee(employee); return "redirect:/employees"; }
list.html
修改“修改”按钮为链接:<!-- 修改成链接格式,并且传参过去 --> <a class="btn btn-sm btn-primary" th:href="@{/update_employee/}+${emp.getId()}">修改</a>
employee
目录下新建update_employee.html
文件,内容完全复制add_employee.html
,修改中间main
部分表单:<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <!-- 表单提交动作 --> <form th:action="@{/add_employee}" method="post"> <!-- 增加一个隐藏域用于传id,这样才能修改原员工 --> <input type="hidden" name="id" th:value="${emp.getId()}"> <!-- 姓名文本框 --> <div class="form-group"> <label>LastName</label> <input th:value="${emp.getLastName()}" type="text" class="form-control" name="lastName" placeholder="LastName"> </div> <!-- 邮箱文本框 --> <div class="form-group"> <label>Email</label> <input th:value="${emp.getEmail()}" type="Email" class="form-control" name="email" placeholder="Email"> </div> <!-- 性别单选框 --> <div class="radio"> <label><input th:checked="${emp.getSex()==1}" type="radio" name="sex" value="1">男</label> </div> <div class="radio"> <label><input th:checked="${emp.getSex()==0}" type="radio" name="sex" value="0">女</label> </div> <!-- 部门下拉框 --> <div class="form-group"> <label>department</label> <!-- 部门value为id,name提交的必须是id --> <select class="form-control" th:name="department.id"> <option th:selected="${emp.getDepartment().getId()==dept.getId()}" th:each="dept:${depts}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option> </select> </div> <!-- 出生日期 --> <div class="form-group"> <label>Birth</label> <!-- 由于日期读取出来格式不对,所以需要修改 --> <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="date" class="form-control" name="birth" placeholder="Birth"> </div> <button type="submit" class="btn btn-primary">修改</button> </form> </main>
-
删除员工操作:
EmployeeController.java
新增删除跳转:// 接收Get请求,删除员工,要接收员工Id @GetMapping("/delete_employee/{id}") public String deleteEmployee(@PathVariable("id")Integer id,Model model){ employeeDao.deleteEmployee(id); return "redirect:/employees"; }
list.html
文件修改删除按钮为链接:<a class="btn btn-sm btn-danger" th:href="@{/delete_employee/}+${emp.getId()}">删除</a>
5.404页面
只需要在
templates
文件夹下新建error
文件夹并将404.html
文件放入其中即可自动配置。
6.注销
LoginController.java
文件中增加跳转:
// 注销
@RequestMapping("/user/logout")
public String logout(HttpSession session){
// 使会话失效
session.invalidate();
return "redirect:/index.html";
}
common.html
文件中修改导航栏的Sign out
链接:
<a class="nav-link" th:href="@{/user/logout}">Sign out</a>
此处为后续添加内容,为该员工管理系统增加数据库连接及相关增删改查操作!
7.为员工管理系统增加数据库连接及操作
由于在原员工管理系统直接修改产生部分Bug,于是本人新建项目,依然使用原项目前端页面,具体步骤如下:
-
新建项目,导入依赖:
- Developer Tools->Lombok,但可能需要修改版本,本人修改为1.18.12;
- Web->Spring Web;
- Template Engine->Thymeleaf;
- SQL->JDBC API;
- SQL->Spring Data JDBC;
- SQL->MySQL Driver;
-
新建
application.yml
配置文件,代码为:spring: datasource: username: root password: password # serverTimezone=UTC是时区, url: jdbc:mysql://localhost:3306/springboot_mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false mvc: format: date: yyyy-MM-dd # 整合mybatis,分别为实体类位置和mapper实现层位置 mybatis: type-aliases-package: com.kun.demowebmybatis.pojo mapper-locations: classpath:mybatis/mapper/*.xml
-
新增Mapper包,并新建
DepartmentMapper.java
、EmployeeMapper.java
两个接口文件,代码分别为:DepartmentMapper.java
:@Mapper @Repository public interface DepartmentMapper { List<Department> getAllDepartments(); Department getDepartmentById(Integer id); }
EmployeeMapper.java
:@Mapper @Repository public interface EmployeeMapper { List<Employee> getAllEmployees(); Employee getEmployeeById(Integer id); void addEmployee(Employee employee); void updateEmployee(Employee employee); void deleteEmployee(Integer id); }
-
导入原pojo包,并修改
Employee.java
实体类:@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer id; private String lastName; private String email; private Integer sex; private Integer departmentId; private Date birth; // 数据库中未存储,获取其它属性出来后临时获取 private String departmentName; }
-
新建controller包,新建
EmployeeController.java
文件:@Controller public class EmployeeController { @Autowired DepartmentMapper departmentMapper; @Autowired EmployeeMapper employeeMapper; @GetMapping("/employees") public String getAllEmployees(Model model){ List<Employee> employees = employeeMapper.getAllEmployees(); // 重新设置Employee Department department = null; Employee employee = null; for (int i = 0; i < employees.size(); i++) { department = departmentMapper.getDepartmentById(employees.get(i).getDepartmentId()); employee = employees.get(i); employee.setDepartmentName(department.getDepartmentName()); employees.set(i,employee); } model.addAttribute("emps",employees); return "employee/list"; } // 接收Get请求,并展示添加员工表单页面 @GetMapping("/add_employee") public String toAddEmployeePage(Model model){ // 查询所有部门 List<Department> departments = departmentMapper.getAllDepartments(); model.addAttribute("depts",departments); return "employee/add_employee"; } // 接收Post请求,添加新员工之后跳转回员工页面 @PostMapping("/add_employee") public String addEmployee(Employee employee){ employeeMapper.addEmployee(employee); return "redirect:/employees"; } // 接收Get请求,并展示修改员工表单页面,修改时要接收员工Id @GetMapping("/update_employee/{id}") public String toUpdateEmployeePage(@PathVariable("id")Integer id, Model model){ // 根据Id查出员工数据 Employee employee = employeeMapper.getEmployeeById(id); model.addAttribute("emp",employee); // 查询所有部门 List<Department> departments = departmentMapper.getAllDepartments(); model.addAttribute("depts",departments); return "employee/update_employee"; } // 接收Post请求,修改新员工之后跳转回员工页面 @PostMapping("/update_employee") public String updateEmployee(Employee employee){ employeeMapper.updateEmployee(employee); return "redirect:/employees"; } // 接收Get请求,删除员工,要接收员工Id @GetMapping("/delete_employee/{id}") public String deleteEmployee(@PathVariable("id")Integer id){ employeeMapper.deleteEmployee(id); return "redirect:/employees"; } }
-
导入原templates目录,并修改employee包下三个文件main内容:
list.html
:注意emp.getDepartmentName(),此处即上述员工实体类新增属性的使用<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <h2><a class="btn btn-sm btn-success" th:href="@{/add_employee}">添加员工</a></h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>sex</th> <th>department</th> <th>birth</th> <th>操作</th> </tr> </thead> <!-- 这里使用了Thymeleaf提供的工具类,方便调用数据 --> <tbody> <tr th:each="emp:${emps}"> <td th:text="${emp.getId()}"></td> <td th:text="${emp.getLastName()}"></td> <td th:text="${emp.getEmail()}"></td> <!-- 由于存储的数据只有0和1,这里要进行判断 --> <td th:text="${emp.getSex()==0?'女':'男'}"></td> <td th:text="${emp.getDepartmentName()}"></td> <!-- 获取的时间要使用工具类进行格式化输出 --> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td> <td> <!-- 修改成链接格式,并且传参过去 --> <a class="btn btn-sm btn-primary" th:href="@{/update_employee/}+${emp.getId()}">修改</a> <a class="btn btn-sm btn-danger" th:href="@{/delete_employee/}+${emp.getId()}">删除</a> </td> </tr> </tbody> </table> </div> </main>
add_employee.html
:注意部门name改为departmentId<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <!-- 表单提交动作 --> <form th:action="@{/add_employee}" method="post"> <!-- 姓名文本框 --> <div class="form-group"> <label>LastName</label> <input type="text" class="form-control" name="lastName" placeholder="LastName"> </div> <!-- 邮箱文本框 --> <div class="form-group"> <label>Email</label> <input type="Email" class="form-control" name="email" placeholder="Email"> </div> <!-- 性别单选框 --> <div class="radio"> <label><input type="radio" name="sex" value="1">男</label> </div> <div class="radio"> <label><input type="radio" name="sex" value="0">女</label> </div> <!-- 部门下拉框 --> <div class="form-group"> <label>department</label> <!-- 部门value为id,name提交的必须是id --> <select class="form-control" th:name="departmentId"> <option th:each="dept:${depts}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option> </select> </div> <!-- 出生日期 --> <div class="form-group"> <label>Birth</label> <input type="date" class="form-control" name="birth" placeholder="Birth"> </div> <button type="submit" class="btn btn-primary">添加</button> </form> </main>
update_employee.html
:注意提交表单跳转/update_employee,部门name改为departmentId<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <!-- 表单提交动作 --> <form th:action="@{/update_employee}" method="post"> <!--增加一个隐藏域用于传id,这样才能修改原员工--> <input type="hidden" name="id" th:value="${emp.getId()}"> <!-- 姓名文本框 --> <div class="form-group"> <label>LastName</label> <input th:value="${emp.getLastName()}" type="text" class="form-control" name="lastName" placeholder="LastName"> </div> <!-- 邮箱文本框 --> <div class="form-group"> <label>Email</label> <input th:value="${emp.getEmail()}" type="Email" class="form-control" name="email" placeholder="Email"> </div> <!-- 性别单选框 --> <div class="radio"> <label><input th:checked="${emp.getSex()==1}" type="radio" name="sex" value="1">男</label> </div> <div class="radio"> <label><input th:checked="${emp.getSex()==0}" type="radio" name="sex" value="0">女</label> </div> <!-- 部门下拉框 --> <div class="form-group"> <label>department</label> <!-- 部门value为id,name提交的必须是id --> <select class="form-control" th:name="departmentId"> <option th:selected="${emp.getDepartmentId()==dept.getId()}" th:each="dept:${depts}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option> </select> </div> <!-- 出生日期 --> <div class="form-group"> <label>Birth</label> <!-- 由于日期读取出来格式不对,所以需要修改 --> <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="date" class="form-control" name="birth" placeholder="Birth"> </div> <button type="submit" class="btn btn-primary">修改</button> </form> </main>
注:请注意其中一些细节的修改;
-
resources目录下增加mybatis.mapper包结构,并新建对应的mapper.xml文件:
DepartmentMapper.xml
:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 注意此处需要设置连接位置 --> <mapper namespace="com.kun.demowebmybatis.mapper.DepartmentMapper"> <select id="getAllDepartments" resultType="Department"> select * from department </select> <select id="getDepartmentById" resultType="Department"> select * from department where id = #{id} </select> </mapper>
EmployeeMapper.xml
:此处注意数据库键名要对应,且参数名字也须一一对应,以及实现接口方法与id一一对应<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 注意此处需要设置连接位置 --> <mapper namespace="com.kun.demowebmybatis.mapper.EmployeeMapper"> <select id="getAllEmployees" resultType="Employee"> select * from employees </select> <select id="getEmployeeById" resultType="Employee"> select * from employees where id = #{id} </select> <insert id="addEmployee" parameterType="Employee"> insert into employees (id,lastName,email,sex,departmentId,birth) values (#{id},#{lastName},#{email},#{sex},#{departmentId},#{birth}) </insert> <update id="updateEmployee" parameterType="Employee"> update employees set lastName = #{lastName},email = #{email},sex = #{sex},departmentId = #{departmentId},birth = #{birth} where id = #{id} </update> <delete id="deleteEmployee" parameterType="Integer"> delete from employees where id = #{id} </delete> </mapper>