企业CRM系统 SpringBoot+MyBatis Plus+MySQL

目录

1、前言

2、项目效果

2.1、登录功能

2.2、页面国际化功能

2.3、员工添加功能

 2.4、编辑员工功能

2.5、删除员工功能

2.6、分页查询功能

 2.7、条件分页查询功能

 3、功能分析

3.1、登录功能

3.2、页面国际化功能

3.3、员工添加和修改

3.4、员工删除

3.5、员工分页+条件查询



1、前言

该项目是企业CRM(客户关系管理(Customer Relationship Management,简称CRM),是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售营销和服务上的交互,从而提升其管理方式,向客户提供创新式的个性化的客户交互和服务的过程。其最终目标是吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场。)作为初级开发工程师的入门项目。有简单的增、删、改、查;有分页查询、批量删除、条件查询等一系列功能... ...文章附上源码,可自取进行二次开发或重构。下面分析主要的思路!


2、项目效果

2.1、登录功能

  • 基于session方式的密码登录,使用spring mvc拦截器技术,对用户身份进行认证和资源路径的拦截,否则提示" 您还没有权限访问, 请先登录!"。

2.2、页面国际化功能

  •  在开发软件程序时,想要让不同用户看到不同国家的不同语言的效果,需要对页面进行国际化(internationalization)处理,在resources目录下创建i18n目录创建Resource Bundle配置文件,配置区域信息解析器(LocaleResolver)。

2.3、员工添加功能

  1. 发送GET请求跳转添加页面
  2. 通过POST方式将员工对象保存到数据库

 2.4、编辑员工功能

  1. 重构页面和回显数据
  2. 通过PUT方法修改员工信息

2.5、删除员工功能

  1.  删除按钮发送jquery异步请求
  2. 通过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">&copy; 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">&laquo;</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">&raquo;</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

有更多业务需求欢迎在评论区留言,我们一起相互学习~~~ 

最后别忘了你的点赞,关注是我们学习最大的动力~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Husp0707

你的小小点赞、关注是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值