目录
1、前言
该项目是企业CRM(客户关系管理(Customer Relationship Management,简称CRM),是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理方式,向客户提供创新式的个性化的客户交互和服务的过程。其最终目标是吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场。)作为初级开发工程师的入门项目。有简单的增、删、改、查;有分页查询、批量删除、条件查询等一系列功能... ...文章附上源码,可自取进行二次开发或重构。下面分析主要的思路!
2、项目效果
2.1、登录功能
- 基于session方式的密码登录,使用spring mvc拦截器技术,对用户身份进行认证和资源路径的拦截,否则提示" 您还没有权限访问, 请先登录!"。
2.2、页面国际化功能
- 在开发软件程序时,想要让不同用户看到不同国家的不同语言的效果,需要对页面进行国际化(internationalization)处理,在resources目录下创建i18n目录创建Resource Bundle配置文件,配置区域信息解析器(LocaleResolver)。
2.3、员工添加功能
- 发送GET请求跳转添加页面
- 通过POST方式将员工对象保存到数据库
2.4、编辑员工功能
- 重构页面和回显数据
- 通过PUT方法修改员工信息
2.5、删除员工功能
- 删除按钮发送jquery异步请求
- 通过DELETE方式删除一条记录,也可以批量删除多条记录
2.6、分页查询功能
- GET方式查询员工数据。
2.7、条件分页查询功能
3、功能分析
3.1、登录功能
关于登录在这篇文章不再详细解释。我将放到新的文章作为一个专题专门介绍。
3.2、页面国际化功能
步骤:
1、编写国际化配置文件,抽取页面需要国际化的消息。
application.yaml
配置文件
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///springboot_web_demo?charactEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
servlet:
multipart:
enabled: true
max-file-size: 2000MB
max-request-size: 2000MB
resources:
# 静态资源配置
static-locations: classpath:/templates/, classpath:/static/
thymeleaf:
cache: false # 关闭模板引擎缓存
messages:
basename: i18n.login # 配置国际化基础名(包名.基础名)
mvc:
date-format: yyyy-MM-dd # 配置日期转换类型
server:
port: 8080 # 服务器地址
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启mp日志
2、springboot自动装配管理国际化资源文件的组件。
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
3、获取页面国际化的值。
templates/login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta 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="">
<link rel="icon" th:href="@{/favicon.ico}">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/admin/user/login}" method="post">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!-- 登录错误提示信息 -->
<p style="color: #ff0000" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
</form>
</body>
</html>
- 页面国际化时中文乱码问题。采用全局配置方法
4、国际化Locale(区域信息对象)
原理
@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 localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
5、根据请求头带来的区域信息获取Locale进行国际化
common/MyLocaleResolver 区域信息国际化解析器
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)){
String[] split = l.split("_"); // ["en", "US"] ['zh', 'CN']
locale = new Locale(split[0], split[1]);
}
return locale;
}
common/MyMvcConfig spring的配置类
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//将组件注册到容器
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
//添加内部拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截: 静态资源 *.css *.js img
//springboot配置静态资源映射
registry.addInterceptor(new LoginHandlerIntercepter())
.addPathPatterns("/**")
.excludePathPatterns("/", "/login.html", "/admin/user/login", "/asserts/**","/webjars/**");
}
};
return adapter;
}
//国际化区域信息解析器
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
3.3、员工添加和修改
添加数据通过点击 保存 按钮,将前端提交的参数封装成Employee对象传递到后端,调用MP的insert方法保存到数据库就可以了。
list页面发送 th:href 请求,将输入框的内容提交到后端,数据以json格式返回。
注意:添加数据时json数据的id是不需要手动提交。
修改数据主要通过携带员工的id回显属性参数值向后端传递,调用MPupdateById(employee)方法,员工添加和修改是二合一的页面,点击 修改 返回员工列表页面。
核心代码:
<!-- 添加员工页面表单 -->
<!--
需要区分是员工修改还是员工添加?
判断emp对象是否为空。如果是空,则是员工添加否则是员工修改
-->
<h2>
<button class="btn btn-danger deleteBatchBtn" id="delBatch">批量删除</button>
<a class="btn btn-success" th:href="@{/emp}">员工添加</a>
<button class="btn btn-light">导出</button>
</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th><input type="checkbox" value="" id="th_checkAll"></th>
<th>序号</th>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>birth</th>
<th>department</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<form id="delSelected" action="#" method="post">
<input type="hidden" name="_method" value="DELETE">
<tr th:each="emp:${pb.records}">
<td><input type="checkbox" class="td_checkAll" name="eid" th:value="${emp.id}"></td>
<td th:text="${empStat.count}"></td>
<td th:text="${emp.id}"></td>
<td> [[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
<td th:text="${emp.deptId}"></td>
<td>
<a type="button" class="btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<!-- 删除单个员工 th:attr="action=@{/subscribe} 改变属性的值 -->
<button class="btn-sm btn-danger deleteBtn" th:attr="del_uri=@{/emp/}+${emp.id}">删除</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
controller层:控制层提供和前端发起请求的接口
//跳转到添加页面
@GetMapping("/emp")
public String toSavePage(Model model){
//查询所有部门列表
List<Department> departments = departmentService.findAll();
model.addAttribute("depts", departments);
return "emp/employ_add";
}
//添加员工
@PostMapping("/emp")
public String save(Employee employee){
System.out.println("--------添加员工---------"+employee);
//保存员工信息
employeeService.save(employee);
//返回员工列表页面1、重定向redirect 2、转发forward
return "redirect:/emps";
}
//来到修改页面并回显员工信息
@GetMapping("/emp/{id}")
public String toEditPage(@PathVariable("id") Integer id, Model model) {
//根据id查询员工信息
Employee employee = employeeService.findById(id);
model.addAttribute("emp", employee);
//查询所有部门列表
List<Department> departments = departmentService.findAll();
model.addAttribute("depts", departments);
//返回修改页面和添加页面是同一个页面
return "emp/employ_add";
}
//修改员工: 根据id修改员工
@PutMapping("/emp")
public String updateEmployee(Employee employee){
System.out.println("---------修改员工---------"+employee);
employeeService.updateEmployee(employee);
//返回员工查询列表页面
return "redirect:/emps";
}
service实现类:执行业务逻辑
//添加员工
@Override
public void save(Employee employee) {
employeeMapper.insert(employee);
}
//根据id查询员工信息
@Override
public Employee findById(Integer id) {
//从缓存中获取数据
Employee employee = cache.get(id);
if (employee == null){
//从数据库查询
Employee quaryEmployee = employeeMapper.selectById(id);
//放到缓存中
cache.put(id, quaryEmployee);
return quaryEmployee;
}
return cache.get(id);
}
//根据员工id修改员工
@Override
public void updateEmployee(Employee employee) {
employeeMapper.updateById(employee);
}
3.4、员工删除
核心代码:
1)单条记录删除:
<!-- 删除单个员工 th:attr="action=@{/subscribe} 改变属性的值 -->
<button class="btn-sm btn-danger deleteBtn" th:attr="del_uri=@{/emp/}+${emp.id}">删除</button>
<!-- 删除单个员工 -->
<form id="deleteEmpForm" action="#" method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
<form th:action="@{/emp}" method="post">
<!-- 发送PUT请求修改员工数据:
1、spring mvc中配置 HiddenHttpMethodFilter(spring boot自动装配)
2、页面创建一个POST表单
3、创建一个input项, name="_method", 值就是指定的请求方式。
-->
<input type="hidden" name="_method" th:if="${emp!=null}" value="PUT" >
<input type="hidden" name="id" th:if="${emp != null}" th:value="${emp.id}">
<div class="mb-3">
<label>LastName</label>
<input type="text" class="form-control" name="lastName" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
</div>
<div class="mb-3">
<label>email</label>
<input type="text" class="form-control" name="email" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
</div>
<div class="form-group">
<label>gender</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender == '1'}">
<label>男</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender == '0'}">
<label>女</label>
</div>
</div>
<div class="mb-3">
<label>birth</label>
<input type="date" class="form-control" name="birth" placeholder="1999-01-01" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd')}">
</div>
<div class="mb-3">
<label>department</label>
<select class="form-select" name="deptId">
<!-- 提交部门id -->
<option th:selected="${emp!=null}?${dept.id == emp.deptId}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'保存'">保存</button>
</form>
<!-- 删除单个员工 -->
<script>
$(".deleteBtn").click(function () {
if (confirm("确定要删除么? ")) {
$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
return false;
}
});
</script>
controller层:获取路径变量id
//删除单个员工
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
employeeService.deleteEmployee(id);
return "redirect:/emps";
}
service实现类:根据员工id调用MP方法deleteById()
//删除单个员工
@Override
public void deleteEmployee(Integer id) {
employeeMapper.deleteById(id);
}
2) 批量删除:
<button class="btn btn-danger deleteBatchBtn" id="delBatch">批量删除</button>
<th><input type="checkbox" value="" id="th_checkAll"></th>
<form id="delSelected" action="#" method="post">
<input type="hidden" name="_method" value="DELETE">
<tr th:each="emp:${pb.records}">
<td><input type="checkbox" class="td_checkAll" name="eid" th:value="${emp.id}"></td>
<td th:text="${empStat.count}"></td>
<td th:text="${emp.id}"></td>
<td> [[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
<td th:text="${emp.deptId}"></td>
<td>
<a type="button" class="btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<!-- 删除单个员工 th:attr="action=@{/subscribe} 改变属性的值 -->
<button class="btn-sm btn-danger deleteBtn" th:attr="del_uri=@{/emp/}+${emp.id}">删除</button>
</td>
</tr>
</form>
<!-- 删除多个员工 -->
<script>
$(".deleteBatchBtn").click(function () {
// 全选功能
$("#th_checkAll").click(function () {
$(".td_checkAll").attr("checked", this.checked)
});
// 选中的复选框
$("#delBatch").click(function () {
if (confirm("您确定要删除所选中的员工数据么?")){
$("#delSelected").attr("action", $(this).attr("del_uri")).submit()
return false;
}
});
});
</script>
controller层:获取多个员工的id集合
//批量删除员工
@DeleteMapping("/emps")
public String deleteBatchEmployee(HttpServletRequest request){
//接收员工数据
String[] eids = request.getParameterValues("eid");
System.out.println("-----eids-------->"+eids);
employeeService.deleteBatchEmployee(eids);
return "redirect:/emps";
}
service实现类:根据集合ids[] 调用mp删除方法
//批量删除员工
@Override
public void deleteBatchEmployee(String[] eids) {
if (eids != null && eids.length>0){
for (String eid : eids) {
employeeMapper.deleteById(eid);
}
}
}
3.5、员工分页+条件查询
前端
1、页面加载后发送th:href="@{/emps}"请求,携带当前页码currentPage和每页显示记录数pageSize参数。
2、将数据渲染到模型上。
后端
1、controller层:
- 接收参数:PageBean对象的当前页码currentPage和每页显示记录数pageSize
- 调用service。employeeService.findAll返回PageBean对象
- 将PageBean对象转为json格式
2、service层:
- 接收参数:PageBean对象的当前页码currentPage和每页显示记录数pageSize
- 计算起始索引,调用mapper的mp方法:selectCount、selectPage
- 封装PageBean对象
核心代码:
controller:
//查询所有员工+分页查询
@GetMapping("/emps")
public String findAll(Model model, PageBean<Employee> pageBean, Employee employee) {
//默认设置在第1页,每页显示5条数据
if (pageBean.getCurrentPage()==null || "".equals(pageBean.getCurrentPage())){
pageBean.setCurrentPage(1);
}
if (pageBean.getPageSize()==null || "".equals(pageBean.getPageSize())){
pageBean.setPageSize(7);
}
//查询所有的员工+分页对象
PageBean<Employee> pb = employeeService.findAll(pageBean, employee);
System.out.println("----------pb--------->"+pb);
List<Department> depts = departmentService.findAll();
//将员工对象放到请求域中
model.addAttribute("pb", pb);
model.addAttribute("depts", depts);
model.addAttribute("emp", employee);
//返回员工列表页面
return "emp/employ_list";
}
service实现类:
//查询所有员工列表+分页显示
@Override
public PageBean<Employee> findAll(PageBean<Employee> pageBean, Employee employee) {
//1. 接收 当前页码
Integer currentPage = pageBean.getCurrentPage();
//2. 接收 每页展示条数
Integer pageSize = pageBean.getPageSize();
//创建新的PageBean对象
PageBean<Employee> pb = new PageBean<Employee>();
pb.setCurrentPage(currentPage);
pb.setPageSize(pageSize);
//4. 按页查询员工数据
//条件构造器
LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>();
String lastName = employee.getLastName();
Integer deptId = employee.getDeptId();
lqw.like(Employee::getLastName, lastName).or().eq(Employee::getDeptId, deptId);
//计算开始索引
Integer start = (currentPage - 1) * pageSize;
System.out.println("---------start------->"+start);
//分页构造器
IPage<Employee> page = new Page<>(start, pageSize);
employeeMapper.selectPage(page, lqw);
Long _totalRecords = page.getTotal();
Integer totalRecords = _totalRecords.intValue();
pb.setTotalRecords(totalRecords);
Long _totalPage = page.getPages();
Integer totalPage = _totalPage.intValue();
pb.setTotalPage(totalPage);
List<Employee> records = page.getRecords();
pb.setRecords(records);
return pb;
}
分页对象PageBean:
@Data
public class PageBean<T> implements Serializable {
private Integer currentPage; //当前页 第1, 2, 3, ...., 16 页
private Integer pageSize; //每页显示的记录数 5
private Integer totalPage; //总页码数 16页
private List<T> records; //当前页记录数 每页5条记录
private Integer totalRecords; //总记录 80条记录
分页导航条:
<div>
<!-- 分页导航条
前端传递的参数:
1、当前页码:currentPage
2、每页显示的条数:pageSize
后端需要响应的参数:
1、当前页记录(集合):records
2、总记录数:totalRecords
-->
<nav aria-label="Page navigation example">
<ul class="pagination">
<li>
<span id="span_page"> 共 <span style="color: #ff0000">[[${pb.totalPage}]]</span> 页, 共<span style="color: #ff0000">[[${pb.totalRecords}]]</span> 条记录 </span>
</li>
<li class="page-item">
<a class="page-link" href="#"
th:href="@{/emps(currentPage=${pb.currentPage}-1, pageSize=7, lastName=${emp.lastName}, deptId=${emp.deptId})}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 分页工具条 ${#numbers.sequence(from,to)} -->
<th:block th:each="page:${#numbers.sequence(1, pb.totalPage)}">
<li class="page-item" th:class="${page == pb.currentPage}?'page-item active':'page-item'">
<a class="page-link" href="#" th:href="@{/emps(currentPage=${page}, pageSize=7, lastName=${emp.lastName}, deptId=${emp.deptId})}">
[[${page}]]
</a>
</li>
</th:block>
<li class="page-item">
<a class="page-link" href="#"
th:href="@{/emps(currentPage=${pb.currentPage}+1, pageSize=7, lastName=${emp.lastName}, deptId=${emp.deptId})}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
<li>
<span> 当前第 <span style="color: #ff0000">[[${pb.currentPage}]]</span> 页 </span>
</li>
</ul>
</nav>
</div>
更多细节内容,可以自取下面链接:
https://gitee.com/hfnu_112/employee-crmsystem.git
有更多业务需求欢迎在评论区留言,我们一起相互学习~~~
最后别忘了你的点赞,关注是我们学习最大的动力~~~