SpringBoot + vue 管理系统

news2024/11/15 17:31:40

SpringBoot + vue 管理系统


文章目录

      • SpringBoot + vue 管理系统
        • 1、成品效果展示
        • 2、项目准备
        • 3、项目开发
          • 3.1、部门管理
            • 3.1.1、前端核心代码
            • 3.1.2、后端代码实现
          • 3.2、员工管理
            • 3.2.1、前端核心代码
            • 3.2.2、后端代码实现
          • 3.3、班级管理
            • 3.3.1、前端核心代码
            • 3.3.2、后端代码实现
          • 3.4、学生管理
            • 3.4.1、前端核心代码
            • 3.4.2、后端代码实现
          • 3.5、数据统计
            • 3.5.1、前端核心代码
            • 3.5.2、后端代码实现
          • 3.6、登录功能
            • 3.6.1、前端核心代码
            • 3.6.2、后端代码实现

1、成品效果展示

SpringBoot + vue 管理系统

2、项目准备

环境准备

image-20240714203052876

步骤:

  1. 准备数据库表
  2. 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
  3. 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类
  4. 准备对应的Mapper、Service(接口、实现类)、Controller基础结构
-- 部门管理
create table dept(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(10) not null unique comment '部门名称',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '修改时间'
) comment '部门表';

insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),
	(2,'教研部',now(),now()),
	(3,'咨询部',now(),now()), 
	(4,'就业部',now(),now()),
	(5,'人事部',now(),now());

-- 员工管理(带约束)
create table emp (
  id int unsigned primary key auto_increment comment 'ID',
  username varchar(20) not null unique comment '用户名',
  password varchar(32) default '123456' comment '密码',
  name varchar(10) not null comment '姓名',
  gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
  image varchar(300) comment '图像',
  job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
  entrydate date comment '入职时间',
  dept_id int unsigned comment '部门ID',
  create_time datetime not null comment '创建时间',
  update_time datetime not null comment '修改时间'
) comment '员工表';

INSERT INTO emp
	(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
	(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
	(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
	(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
	(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
	(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
	(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
	(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
	(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
	(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
	(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
	(11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
	(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
	(13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
	(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
	(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
	(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2007-01-01',2,now(),now()),
	(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());

CREATE TABLE clazz (
  id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID,主键',
  name varchar(30) NOT NULL UNIQUE COMMENT '班级名称',
  room varchar(20) DEFAULT NULL COMMENT '班级教室',
  begin_date date NOT NULL COMMENT '开课时间',
  end_date date NOT NULL COMMENT '结课时间',
  master_id int unsigned NOT NULL COMMENT '班主任ID, 关联员工表ID',
  create_time datetime NOT NULL COMMENT '创建时间',
  update_time datetime NOT NULL COMMENT '修改时间'
) COMMENT '班级表';

INSERT INTO clazz VALUES (1,'黄埔班一期','212','2023-04-30','2023-06-29',10,'2023-06-01 17:08:23','2023-06-01 17:39:58'),
	(2,'黄埔班二期','211','2023-06-25','2023-12-31',1,'2023-06-01 17:34:16','2023-06-01 17:43:43'),
	(3,'黄埔班三期','210','2023-07-10','2024-01-20',3,'2023-06-01 17:45:12','2023-06-01 17:45:12'),
	(4,'JavaEE就业165期','108','2023-06-15','2023-12-25',6,'2023-06-01 17:45:40','2023-06-01 17:45:40'),
	(5,'JavaEE就业166期','105','2023-07-20','2024-02-20',20,'2023-06-01 17:46:10','2023-06-01 17:46:10'),
	(6,'黄埔四期','209','2023-08-01','2024-02-15',7,'2023-06-01 17:51:21','2023-06-01 17:51:21');

CREATE TABLE student (
  id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID,主键',
  name varchar(10) NOT NULL COMMENT '姓名',
  no char(10) NOT NULL UNIQUE COMMENT '学号',
  gender tinyint unsigned NOT NULL COMMENT '性别, 1: 男, 2: 女',
  phone varchar(11) NOT NULL UNIQUE COMMENT '手机号',
  degree tinyint unsigned DEFAULT NULL COMMENT '最高学历, 1:初中, 2:高中, 3:大专, 4:本科, 5:硕士, 6:博士',
  violation_count tinyint unsigned NOT NULL DEFAULT '0' COMMENT '违纪次数',
  violation_score tinyint unsigned NOT NULL DEFAULT '0' COMMENT '违纪扣分',
  clazz_id int unsigned NOT NULL COMMENT '班级ID, 关联班级表ID',
  create_time datetime NOT NULL COMMENT '创建时间',
  update_time datetime NOT NULL COMMENT '修改时间'
) COMMENT '学生表';

INSERT INTO student VALUES (1,'Tom','2023001001',1,'18909091212',4,0,0,1,'2023-06-01 18:28:58','2023-06-01 18:28:58'),
	(2,'Cat','2023001002',2,'18909092345',3,0,0,1,'2023-06-01 18:34:57','2023-06-01 18:34:57'),
	(3,'Lily','2023001003',2,'13309230912',4,2,5,1,'2023-06-01 18:35:23','2023-06-01 19:37:42'),
	(4,'Jerry','2023001004',1,'15309232323',4,1,2,1,'2023-06-01 18:35:48','2023-06-01 19:37:35'),
	(6,'Nacos','2023002001',1,'18809091212',1,3,10,2,'2023-06-01 18:57:32','2023-06-01 19:37:29');

生成的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tlias</groupId>
    <artifactId>web-tlias</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>web-tlias</name>
    <description>web-tlias</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        pagehelper分页查询插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>

<!--        阿里云oss依赖-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.2</version>
        </dependency>
<!--        jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.tlias.WebTliasApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

创建项目工程目录结构

image-20240714203628782

配置文件application.yml

server:
  port: 8080

mybatis:
  mapper-locations: classpath:mappers/*xml
  type-aliases-package: com.tlias.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tlias
    username: root
    password: 123456
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB

pagehelper:
  reasonable: true

aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    accessKeyId: you_acesskey
    accessKeySecret: you_accessKeySecret
    bucketName: tlias-lwj

设置统一返回数据

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    private int code;
    private String msg;
    private Object data;

    public static Result success() {
        return new Result(1, "success", null);
    }

    public static Result success(Object data) {
        return new Result(1, "success", data);
    }

    public static  Result error(String msg) {
        return new Result(0,msg,null);
    }

}

构建一个vue项目

image-20240714203803145

3、项目开发

功能模块分为六部分:班级管理、学员管理、部门管理、员工管理、员工信息统计、学员信息统计

3.1、部门管理

开发的部门管理功能包含:

  1. 查询部门
  2. 删除部门
  3. 新增部门
  4. 更新部门
3.1.1、前端核心代码
<template>
  <div style="margin-top: 20px; margin: 50px; margin-right: 100px">
    <!-- 按钮 -->

    <el-row>
      <el-button
        style="float: right"
        type="primary"
        @click="dialogFormVisible = true; dept={}"
        >+ 新增部门</el-button
      >
    </el-row>
    <br>
    <!-- 数据表格 -->
    <template>
      <el-table
        highlight-current-row
        ref="multipleTable"
        :data="tableData"
        tooltip-effect="dark"
        style="width: 100%"
        border
      >
        <el-table-column type="index" width="100" label="序号" header-align="center" align="center"> </el-table-column>
        <el-table-column prop="name" label="部门名称" header-align="center" align="center"></el-table-column>
        <el-table-column label="最后操作时间" header-align="center" align="center">
          <template slot-scope="scope">
              {{scope.row.updateTime ? scope.row.updateTime.replace('T',' '):''}}
          </template>
        </el-table-column>

        <el-table-column label="操作" width="420" header-align="center" align="center">
          <template slot-scope="scope">
            <el-button
              size="mini"
              type="primary"
              plain
              @click="selectById(scope.row.id)"
              >编辑</el-button
            >

            <el-button
              size="mini"
              type="danger"
              plain
              @click="deleteById(scope.row.id)"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </template>

    <!-- 新建对话框 -->

    <el-dialog title="保存部门" :visible.sync="dialogFormVisible" >
      <el-form :model="dept" :rules="rules" ref="dept">
        <el-form-item label="部门名称" :label-width="formLabelWidth" prop="name">
          <el-input v-model="dept.name"  placeholder="请输入部门名称" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>

      <div slot="footer" class="dialog-footer">
        <el-button @click="cancel('dept')">取 消</el-button>
        <el-button type="primary" @click="save('dept')">确 定</el-button>
      </div>
    </el-dialog>

  </div>
</template>

<script>
import { findAll, add, update, deleteById, selectById } from "@/api/dept.js";

export default {
  data() {
    return {
      formLabelWidth: "120px",
      dialogFormVisible: false, //控制对话框是否可见
      dept: {
        name: "",
      },
      tableData: [],
      rules: {
          name: [
            { required: true, message: '请输入部门名称', trigger: 'blur' },
            { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
          ]
      }    
    };
  },

  methods: {

    //删除部门
    deleteById(id) {
      this.$confirm("确认删除?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
          deleteById(id).then((result) => {
          	if(result.data.code == 1){
	          	this.$message({
	              message: "恭喜你,删除成功",
	              type: "success",
	            });
	        }else{
	        	this.$message.error(result.data.msg);
	        }
            //重新查询数据
            this.init();
          });
        }).catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

    //根据ID查询部门 -- 回显
    selectById(id) {
      this.dialogFormVisible = true;
      selectById(id).then((result) => {
        this.dept = result.data.data;
      });
    },

    //保存方法
    save(formName) {
       this.$refs[formName].validate((valid) => {
          if(valid){
            let operator ;
            if (this.dept.id) {
              operator = update(this.dept); // 修改
            }else{
              operator = add(this.dept); //添加 
            }

            operator.then((result) => {
              if (result.data.code == 1) {
                //修改成功
                this.$message.success("恭喜你,保存成功");
                //重新查询数据
                this.init();
                // 关闭新建窗口
                this.dialogFormVisible = false;
                // 清空模型数据
                this.dept = {};
              } else {
                this.$message.error(result.data.msg);
              }
            });
          }
       })
    },

    //初始化 - 查询全部
    init() {
      findAll().then((result) => {
        console.log(result);
        if (result.data.code == 1) {
          this.tableData = result.data.data;
        }
      });
    },

    cancel(formName){
      this.dialogFormVisible = false;
      this.$refs[formName].resetFields();
    }
  },
  mounted() {
    //当页面加载完成后自动执行。
    this.init();
  },
};
</script>
3.1.2、后端代码实现

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

controller层

@RestController
@RequestMapping("depts")
@Slf4j
public class DeptController {

    @Autowired
    private DeptService service;

    /**
     * 查询部门
     * @return
     */
    @GetMapping
    public Result getDept(){
        log.info("查询部门数据");
        List<Dept> list = service.getDeptList();
        return Result.success(list);
    }

    /**
     * 根据id删除部门
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")

    public Result deleteDeptById(@PathVariable("id") Integer id){
        log.info("删除部门id {}",id);
        service.deleteDeptById(id);
        return Result.success();
    }

    /**
     * 新增部门
     * @param dept
     * @return
     */
    @PostMapping
    public Result save(@RequestBody Dept dept){
        log.info("新增部门 {}",dept);
        service.save(dept);
        return Result.success();
    }

    /**
     * 根据id查询部门
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result selectDeptById(@PathVariable("id") Integer id){
        log.info("根据id查询部门{}",id);
        Dept dept = service.selectDeptById(id);
        return Result.success(dept);
    }

    /**
     * 修改部门
     * @param dept
     * @return
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
        log.info("修改部门信息{}",dept);
        service.update(dept);
        return Result.success();
    }
}

service层

service接口

public interface DeptService {
    /**
     * 查询部门
     * @return
     */
    List<Dept> getDeptList();

    /**
     * 删除部门
     * @param id
     */
    void deleteDeptById(Integer id);

    /**
     * 新增部门
     * @param dept
     */
    void save(Dept dept);

    /**
     * 根据id查询部门
     * @param id
     * @return
     */
    Dept selectDeptById(Integer id);

    /**
     * 修改部门信息
     * @param dept
     */
    void update(Dept dept);
}

serviceImpl

@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;

    /**
     * 查询部门
     *
     * @return
     */
    @Override
    public List<Dept> getDeptList() {
        List<Dept> list =  deptMapper.getDeptList();
        return list;
    }

    /**
     * 删除部门
     *
     * @param id
     */
    @Override
    public void deleteDeptById(Integer id) {
        Integer result = empMapper.selectEmpByDeptId(id);
        if (result < 1 ){
            throw new RuntimeException("不能删除部门,部门下面存在员工");
        }
        deptMapper.deleteDeptById(id);
    }

    /**
     * 新增部门
     *
     * @param dept
     */
    @Override
    public void save(Dept dept) {
        //添加修改时间
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());

        deptMapper.save(dept);
    }

    /**
     * 根据id查询部门
     *
     * @param id
     * @return
     */
    @Override
    public Dept selectDeptById(Integer id) {
        Dept dept =  deptMapper.selectDeptById(id);
        return dept;
    }

    /**
     * 修改部门信息
     *
     * @param dept
     */
    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
}

mapper层

@Mapper
public interface DeptMapper {
    /**
     * 查询部门
     * @return
     */
    @Select("select * from dept")
    List<Dept> getDeptList();

    /**
     * 删除部门
     * @param id
     */
    @Delete("delete from dept where id = #{id}")
    void deleteDeptById(Integer id);

    /**
     * 新增部门
     * @param dept
     */
    @Insert("insert into dept values (null,#{name},#{createTime},#{updateTime})")
    void save(Dept dept);

    /**
     * 根据id查询部门
     * @param id
     * @return
     */
    @Select("select * from dept where id = #{id}")
    Dept selectDeptById(Integer id);

    /**
     * 修改部门信息
     * @param dept
     */
    @Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
    void update(Dept dept);
}
3.2、员工管理

我们可以把员工管理功能分为:

  1. 分页查询
  2. 带条件的分页查询
  3. 删除员工
  4. 新增员工
  5. 修改员工
3.2.1、前端核心代码
<template>
  <div class="app-container">

    <!--搜索表单-->
    <el-form :inline="true" :model="searchEmp" class="demo-form-inline">
      <el-form-item label="姓名">
        <el-input
          v-model="searchEmp.name"
          placeholder="请输入员工姓名"
        ></el-input>
      </el-form-item>
      <el-form-item label="性别">
        <el-select v-model="searchEmp.gender" placeholder="请选择">
          <el-option label="男" value="1"></el-option>
          <el-option label="女" value="2"></el-option>
        </el-select>
      </el-form-item>

      <el-form-item label="入职时间">
        <el-date-picker
          v-model="entrydate"
          clearable
          value-format="yyyy-MM-dd"
          type="daterange"
          placeholder="选择日期"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          style="width: 400px; margin-left: 20px"
        ></el-date-picker>
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="onSubmit">查询</el-button>
         <el-button type="info" @click="clear">清空</el-button>
      </el-form-item>
    </el-form>



    <!--按钮-->
    <el-row>
      <el-button type="danger" size="medium" @click="deleteByIds">- 批量删除</el-button>
      <el-button type="primary" size="medium" @click="dialogVisible = true; emp = { image: ''};" >+ 新增员工</el-button>
    </el-row>

    <!--添加数据对话框表单-->
    <el-dialog ref="form" title="编辑员工" :visible.sync="dialogVisible" width="30%">
      <el-form :model="emp" :rules="rules" ref="emp" label-width="80px" size="mini">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="emp.username"></el-input>
        </el-form-item>
        <el-form-item label="员工姓名"  prop="name">
          <el-input v-model="emp.name"></el-input>
        </el-form-item>

        <el-form-item label="性别"  prop="gender">
          <el-select v-model="emp.gender" placeholder="请选择" style="width:100%" >
             <el-option
              v-for="item in genderList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>

        <el-form-item label="头像">
          <el-upload
            class="avatar-uploader"
            action="/api/upload"
            :headers="token"
            name="image"
            :show-file-list="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
          >
            <img v-if="emp.image" :src="emp.image" class="avatar" />
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
          </el-upload>
        </el-form-item>

        <el-form-item label="职位">
          <el-select v-model="emp.job" placeholder="请选择" value-key="emp.job" style="width:100%">
            <el-option
              v-for="item in jobList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>

        <el-form-item label="入职日期">
          <el-date-picker
            v-model="emp.entrydate"
            clearable
            type="date"
            value-format="yyyy-MM-dd" 
            placeholder="选择日期"
            size="small"
            style="width:100%"
          ></el-date-picker>
        </el-form-item>

        <el-form-item label="归属部门">
          <el-select v-model="emp.deptId" placeholder="请选择" style="width:100%">
            <!-- 
            <el-option label="学工部" value="1"></el-option>
            <el-option label="教研部" value="2"></el-option>
             -->
            <el-option
              v-for="item in deptList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="save('emp')">提交</el-button>
          <el-button @click="cancel('emp')">取消</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>

    <br>

    <!--表格-->
    <template>
      <el-table :data="tableData" style="width: 100%" border @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"  align="center"></el-table-column>
        <el-table-column  prop="name"  label="姓名"  align="center"></el-table-column>
        <el-table-column prop="image" label="头像" align="center">
          <template slot-scope="{ row }">
            <el-image style="width: auto; height: 40px; border: none; cursor: pointer" :src="row.image"></el-image>
          </template>
        </el-table-column>

        <el-table-column align="center" label="性别">
          <template slot-scope="scope">
            <span style="margin-right: 10px">
            {{scope.row.gender == "1" ? "男" : "女"}}</span>
          </template>
        </el-table-column>

        <el-table-column align="center" label="职位">
          <template slot-scope="scope">
            <span style="margin-right: 10px" v-if="scope.row.job == 1">班主任</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 2">讲师</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 3">学工主管</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 4">教研主管</span>
          </template>
        </el-table-column>

        <el-table-column align="center" label="日职日期">
          <template slot-scope="scope">
            {{ scope.row.entrydate }}
          </template>
        </el-table-column>
        <el-table-column align="center" label="最后操作时间">
          <template slot-scope="scope">
            {{scope.row.updateTime ? scope.row.updateTime.replace('T',' '):''}}
          </template>
        </el-table-column>

        <el-table-column align="center" label="操作">
          <template slot-scope="scope">
            <el-button type="primary" size="small" @click="update(scope.row.id)">编辑</el-button>
            <el-button type="danger" size="small" @click="deleteById(scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </template>

    <br>

    <!--分页工具条-->
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :background="background"
      :current-page="currentPage"
      :page-sizes="[5, 10, 15, 20]"
      :page-size="5"
      layout="total, sizes, prev, pager, next, jumper"
      :total="totalCount">
    </el-pagination>
  </div>
</template>

<script>
import { page, add, update, deleteById, selectById } from "@/api/emp.js";
import { findAll } from "@/api/dept.js";
import { getToken } from '@/utils/auth';

export default {
  data() {
    return {
      background: true,
      // 每页显示的条数
      pageSize: 5,
      // 总记录数
      totalCount: 100,
      // 当前页码
      currentPage: 1,
      // 添加数据对话框是否展示的标记
      dialogVisible: false,
      // 品牌模型数据
      searchEmp: {
        name: "",
        gender: "",
      },
      emp: {
        username: "",
        name: "",
        gender: "",
        image: "",
        job: "",
        entrydate: "",
        deptId: ""
      },
      deptList: [],
      genderList: [{id: 1,name: "男"},{id: 2,name: "女"}],
      jobList: [{id: 1,name: "班主任"},{id: 2,name: "讲师"},{id: 3, name: "学工主管"},{id: 4,name: "教研主管"}],
      beginTime: "",
      endTime: "",
      entrydate: "",
      // 被选中的id数组
      selectedIds: [],
      // 复选框选中数据集合
      multipleSelection: [],
      // 表格数据
      tableData: [],
      token: {token: getToken()},
      rules: {
          username: [
            {required: true, message: '请输入用户名', trigger: 'blur' },
            {min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
          ],
          name: [
            {required: true, message: '请输入姓名', trigger: 'blur' },
            {min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
          ],
          gender: [
            {required: true, message: '请选择性别', trigger: 'change' }
          ]
        }
    };
  },
  mounted() {
    this.page(); //当页面加载完成后,发送异步请求,获取数据
    findAll().then((result) => {
      this.deptList = result.data.data;
    });
  },
  
  methods: {
    // 查询分页数据
    page() {
      page(
        this.searchEmp.name,
        this.searchEmp.gender,
        this.beginTime,
        this.endTime,
        this.currentPage,
        this.pageSize
      ).then((res) => {
        this.totalCount = res.data.data.total;
        this.tableData = res.data.data.rows;
      });
    },

    // 复选框选中后执行的方法
    handleSelectionChange(val) {
      this.multipleSelection = val;
    },

    // 查询方法
    onSubmit() {
      this.currentPage = 1;
      this.page();
    },

    clear(){
      this.searchEmp = {name: "", gender: ""};
      this.beginTime = "",
      this.endTime = "";
      this.entrydate = "";
      this.page();
    },
    
    // 添加数据
    save(formName) {
      //校验表单
      this.$refs[formName].validate((valid) => {
          if (valid) {
            let operator;
            if (this.emp.id) {//修改
              operator = update(this.emp);
            } else { //新增
              operator = add(this.emp);
            }
            operator.then((resp) => {
              if (resp.data.code == 1) {
                this.dialogVisible = false;
                this.page();
                this.$message({ message: "恭喜你,保存成功", type: "success" });
                this.emp = { image: "" };
              } else {
                this.$message.error(resp.data.msg);
              }
            });
          }
      });
    },
    update(id) {
      //1. 打开窗口
      this.dialogVisible = true;
      //2. 发送请求

      selectById(id).then((result) => {
        if (result.data.code == 1) {
          this.emp = result.data.data;
          this.emp;
        }
      });
    },

    
    //分页
    handleSizeChange(val) {
      // 重新设置每页显示的条数
      this.pageSize = val;
      this.page();
    },

    handleCurrentChange(val) {
      // 重新设置当前页码
      this.currentPage = val;
      this.page();
    },


    //删除员工信息
    deleteById(id){
      this.$confirm("此操作将删除该数据, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
          //2. 发送AJAX请求
          deleteById(id).then((resp) => {
            if (resp.data.code == 1) {
              //删除成功
              this.$message.success("恭喜你,删除成功");
              this.page();
            } else {
              this.$message.error(resp.data.msg);
            }
          });
      }).catch(() => {
          //用户点击取消按钮
          this.$message.info("已取消删除");
        });
    },


    // 批量删除员工信息
    deleteByIds() {
      // 弹出确认提示框
      this.$confirm("此操作将删除该数据, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
          //用户点击确认按钮
          //1. 创建id数组, 从 this.multipleSelection 获取即可
          for (let i = 0; i < this.multipleSelection.length; i++) {
            this.selectedIds[i] = this.multipleSelection[i].id;
          }

          //2. 发送AJAX请求
          deleteById(this.selectedIds).then((resp) => {
            if (resp.data.code == "1") {
              //删除成功
              this.$message.success("恭喜你,删除成功");
              this.page();
            } else {
              this.$message.error(resp.data.msg);
            }
          });
      }).catch(() => {
          //用户点击取消按钮
          this.$message.info("已取消删除");
        });
    },

    cancel(formName){
      this.dialogVisible = false;
      this.$refs[formName].resetFields();
    },

    //文件上传相关
    handleAvatarSuccess(res, file) {
      this.emp.image = res.data;
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === "image/jpeg";
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isJPG) {
        this.$message.error("上传头像图片只能是 JPG 格式!");
      }
      if (!isLt2M) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isJPG && isLt2M;
    },
  },


  watch: {
    entrydate(val) {
      if (val && val.length >= 2) {
        this.beginTime = val[0];
        this.endTime = val[1];
      } else {
        this.beginTime = "";
        this.endTime = "";
      }
    },
  },
};
</script>
<style>
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 100px;
  height: 100px;
  line-height: 100px;
  text-align: center;
}
.avatar {
  width: 100px;
  height: 100px;
  display: block;
}
</style>
3.2.2、后端代码实现

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;
    private String image;
    private Short job;
    private LocalDate entrydate;
    private Integer deptId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

controller

@RestController
@RequestMapping("/emps")
@Slf4j
public class EmpController {

    @Autowired
    private EmpService service;

    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @param name
     * @param gender
     * @param begin
     * @param end
     * @return
     */
    @GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Short gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
        log.info("员工分页查询 page = {},pageSize = {}", page, pageSize);
        PageBean pageBean = service.page(page,pageSize,name,gender,begin,end);
        return Result.success(pageBean);
    }

    /**
     * 删除
     * @param ids
     * @return
     */
    @DeleteMapping("/{ids}")
    public Result deleteByIds(@PathVariable("ids") List<Integer> ids){
        log.info("删除 ids = {}", ids);
        service.deleteByIds(ids);
        return Result.success();
    }

    /**
     * 新增员工
     * @param emp
     * @return
     */
    @PostMapping
    public Result save(@RequestBody Emp emp){
        log.info("新增员工 {}", emp);
        service.save(emp);
        return Result.success();
    }

    /**
     * 根据id查询员工
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result selectById(@PathVariable Integer id){
        log.info("根据id查询员工");
        Emp emp = service.selectById(id);
        return Result.success(emp);
    }

    @PutMapping
    public Result update(@RequestBody Emp emp){
        log.info("修改员工{}",emp);
        service.update(emp);
        return Result.success();
    }

    @GetMapping("/list")
    public Result selectAll(){
        log.info("查询全部员工");
        List<Emp> list = service.selectAll();
        return Result.success(list);
    }
}

service层

public interface EmpService {
    /**
     * 员工分页查询
     *
     * @param page
     * @param pageSize
     * @param name
     * @param gender
     * @param begin
     * @param end
     * @return
     */
    PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end);

    /**
     * 删除
     * @param ids
     */
    void deleteByIds(List<Integer> ids);

    /**
     * 新增员工
     * @param emp
     */
    void save(Emp emp);

    /**
     * 根据id查询员工
     * @param id
     * @return
     */
    Emp selectById(Integer id);

    /**
     * 修改员工
     * @param emp
     */
    void update(Emp emp);

    /**
     * 查询全部员工
     * @return
     */
    List<Emp> selectAll();

    /**
     * 员工登录
     * @param emp
     * @return
     */
    Emp login(Emp emp);
}
@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;


    @Override
    public PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
        //设置分页参数
        PageHelper.startPage(page,pageSize);
        //进行查询
        List<Emp> list = empMapper.list(name,gender,begin,end);

        //获取分页结果
        Page<Emp> empPage = (Page<Emp>) list;

        return new PageBean(empPage.getTotal(),empPage.getResult());
    }

    /**
     * 删除
     *
     * @param ids
     */
    @Override
    public void deleteByIds(List<Integer> ids) {
        empMapper.deleteByIds(ids);
    }

    /**
     * 新增员工
     *
     * @param emp
     */
    @Override
    public void save(Emp emp) {
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.save(emp);
    }

    /**
     * 根据id查询员工
     *
     * @param id
     * @return
     */
    @Override
    public Emp selectById(Integer id) {
        Emp emp = empMapper.selectById(id);
        return emp;
    }

    /**
     * 修改员工
     *
     * @param emp
     */
    @Override
    public void update(Emp emp) {
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.update(emp);
    }

    /**
     * 查询全部员工
     *
     * @return
     */
    @Override
    public List<Emp> selectAll() {
        List<Emp> list = empMapper.selectAll();
        return list;
    }

    /**
     * 员工登录
     *
     * @param emp
     * @return
     */
    @Override
    public Emp login(Emp emp) {
        return empMapper.getEmpByUsernameAndPassword(emp);
    }
}

mapper层

@Mapper
public interface EmpMapper {
    /**
     * 查询部门下是否有员工
     * @param id
     * @return
     */
    @Select("select  count(*) from emp where dept_id = #{id}")
    Integer selectEmpByDeptId(Integer id);
    /**
     * 查询
     */
//    @Select("select * from emp")
    List<Emp> list(@Param("name") String name,@Param("gender") Short gender,
                   @Param("begin") LocalDate begin,@Param("end") LocalDate end);

    /**
     * 删除
     * @param ids
     */
    void deleteByIds(@Param("ids") List<Integer> ids);

    /**
     * 新增员工
     * @param emp
     */
    @Insert("insert into emp values (null,#{username},#{password},#{name},#{gender}" +
            ",#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    void save(Emp emp);

    /**
     * 根据id查询员工
     * @param id
     * @return
     */
    @Select("select * from emp where id = #{id}")
    Emp selectById(Integer id);

    /**
     * 修改员工
     * @param emp
     */
    @Update("update emp set username = #{username},password = #{password}," +
            "name = #{name},gender = #{gender},image = #{image},job = #{job}," +
            "entrydate = #{entrydate},dept_id = #{deptId},create_time = #{createTime}," +
            "emp.update_time = #{updateTime} where id = #{id}")
    void update(Emp emp);

    /**
     * 查询全部员工
     * @return
     */
    @Select("select * from emp")
    List<Emp> selectAll();

    /**
     * 员工性别统计
     * @return
     */
    List<PieChartData> getEmpGenderData();


    /**
     * 员工职位统计
     * @return
     */

    @Select("select (case job\n" +
            "            when 1 then '班主任'" +
            "            when 2 then '讲师'" +
            "            when 3 then '学工主管'" +
            "            when 4 then '教研主管'" +
            "            when 4 then '咨询师'" +
            "            else '无' end) as job," +
            "       count(*) as jobcount" +
            " from emp" +
            " group by job")
   List<Map<String,Object>> getEmpJobData();

    /**
     * 员工登录
     * @param emp
     * @return
     */
    @Select("select * from emp where username = #{username} and  password = #{password}")
    Emp getEmpByUsernameAndPassword(Emp emp);
}

mapper.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.tlias.mapper.EmpMapper">


    <delete id="deleteByIds">
        delete from emp
        where id in
        <foreach collection="ids" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </delete>


    <select id="list" resultType="com.tlias.entity.Emp">
        select * from emp
        <where>
            <if test="name != null and name != ''">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>
    <select id="getEmpGenderData" resultType="com.tlias.entity.PieChartData">
        select if(gender = 1,'男性员工','女性员工') as 'name',count(*) as 'value' from emp group by gender;
    </select>

</mapper>

因为需要涉及到oss文件上传所以我们还需要整合

@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class aliyunOssProperties {

    private String endpoint;

    private String accessKeyId;

    private String accessKeySecret;

    private String bucketName;
}
@Component
public class aliyunUtils {
    @Autowired
    private aliyunOssProperties properties;

    public String upload(MultipartFile multipartFile) throws IOException {

        String endpoint = properties.getEndpoint();
        String accessKeyId = properties.getAccessKeyId();
        String accessKeySecret = properties.getAccessKeySecret();
        String bucketName = properties.getBucketName();


        //生成上传名称
        String originalFilename = multipartFile.getOriginalFilename();
        String objectName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);

        try {
            InputStream inputStream = multipartFile.getInputStream();
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);

            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "//" +objectName;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
3.3、班级管理

开发的班级管理功能包含:

  1. 查询班级
  2. 删除班级
  3. 新增班级
  4. 更新班级
3.3.1、前端核心代码
<template>
  <div style="margin-top: 20px; margin: 50px; margin-right: 100px">
    <!-- 按钮 -->
    <el-row>
      <el-button style="float: right" type="primary" @click="dialogFormVisible = true; dept = {}">+ 新增班级</el-button>
    </el-row>
    <br>
    <!-- 数据表格 -->
    <template>
      <el-table highlight-current-row ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%"
        border>
        <el-table-column type="index" width="100" label="序号" header-align="center" align="center"> </el-table-column>
        <el-table-column prop="name" label="班级名称" header-align="center" align="center"></el-table-column>
        <el-table-column prop="room" label="上课教室" header-align="center" align="center"></el-table-column>

        <el-table-column label="开课时间" header-align="center" align="center">
          <template slot-scope="scope">
            {{ scope.row.beginDate ? scope.row.beginDate.replace('T', ' ') : '' }}
          </template>
        </el-table-column>

        <el-table-column label="结课时间" header-align="center" align="center">
          <template slot-scope="scope">
            {{ scope.row.endDate ? scope.row.endDate.replace('T', ' ') : '' }}
          </template>
        </el-table-column>

        <el-table-column label="创建时间" header-align="center" align="center">
          <template slot-scope="scope">
            {{ scope.row.createTime ? scope.row.createTime.replace('T', ' ') : '' }}
          </template>
        </el-table-column>
        <el-table-column label="最后操作时间" header-align="center" align="center">
          <template slot-scope="scope">
            {{ scope.row.updateTime ? scope.row.updateTime.replace('T', ' ') : '' }}
          </template>
        </el-table-column>

        <!-- <el-table-column prop="masterId" label="教师id" header-align="center" align="center"></el-table-column> -->


        <el-table-column label="操作" width="420" header-a lign="center" align="center">
          <template slot-scope="scope">
            <el-button size="mini" type="primary" plain @click="selectById(scope.row.id)">编辑</el-button>

            <el-button size="mini" type="danger" plain @click="deleteById(scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </template>

    <!-- 新建对话框 -->

    <el-dialog title="保存班级" :visible.sync="dialogFormVisible">
      <el-form :model="clazz" :rules="rules" ref="clazz">
        <el-form-item label="班级名称"  prop="name">
          <el-input v-model="clazz.name" placeholder="请输入班级名称" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="教室" prop="room">
          <el-input v-model="clazz.room" placeholder="请输入教室" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="开始日期" prop="beginDate">
          <el-date-picker v-model="clazz.beginDate" type="date" placeholder="选择开始日期"></el-date-picker>
        </el-form-item>
        <el-form-item label="结束日期" prop="endDate">
          <el-date-picker v-model="clazz.endDate" type="date" placeholder="选择结束日期"></el-date-picker>
        </el-form-item>
        <!-- <el-form-item label="班主任ID" prop="masterId">
          <el-input v-model="clazz.masterId" placeholder="请输入班主任ID" autocomplete="off"></el-input>
        </el-form-item> -->

      </el-form>

      <div slot="footer" class="dialog-footer">
        <el-button @click="cancel('clazz')">取 消</el-button>
        <el-button type="primary" @click="save('clazz')">确 定</el-button>
      </div>
    </el-dialog>

  </div>
</template>

<script>
import { findAll, add, update, deleteById, selectById } from "@/api/clazz.js";

export default {
  data() {
    return {
      formLabelWidth: "120px",
      dialogFormVisible: false, //控制对话框是否可见
      clazz: {
        name: "",
        room: "",
        beginDate: "",
        endDate: "",
        masterId: ""
      },
      tableData: [],
      rules: {
        name: [
          { required: true, message: '请输入部门名称', trigger: 'blur' },
          { min: 2, max: 20, message: '长度在 210 个字符', trigger: 'blur' }
        ],
        room: [
          { required: true, message: '请输入教室', trigger: 'blur' }
        ],
        beginDate: [
          { required: true, message: '请选择开始日期', trigger: 'change' }
        ],
        endDate: [
          { required: true, message: '请选择结束日期', trigger: 'change' }
        ],
        // masterId: [
        //   { required: true, message: '请输入班主任ID', trigger: 'blur' }
        // ]
      }
    };
  },

  methods: {

    //删除部门
    deleteById(id) {
      this.$confirm("确认删除?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        deleteById(id).then((result) => {
          if (result.data.code == 1) {
            this.$message({
              message: "恭喜你,删除成功",
              type: "success",
            });
          } else {
            this.$message.error(result.data.msg);
          }
          //重新查询数据
          this.init();
        });
      }).catch(() => {
        this.$message({
          type: "info",
          message: "已取消删除",
        });
      });
    },

    //根据ID查询部门 -- 回显
    selectById(id) {
      this.dialogFormVisible = true;
      selectById(id).then((result) => {
        this.clazz = result.data.data;
      });
    },

    //保存方法
    save(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          let operator;
          if (this.clazz.id) {
            operator = update(this.clazz); // 修改
          } else {
            operator = add(this.clazz); //添加 
          }

          operator.then((result) => {
            if (result.data.code == 1) {
              //修改成功
              this.$message.success("恭喜你,保存成功");
              //重新查询数据
              this.init();
              // 关闭新建窗口
              this.dialogFormVisible = false;
              // 清空模型数据
              this.clazz = {};
            } else {
              this.$message.error(result.data.msg);
            }
          });
        }
      })
    },

    //初始化 - 查询全部
    init() {
      findAll().then((result) => {
        console.log(result);
        if (result.data.code == 1) {
          this.tableData = result.data.data;
        }
      });
    },

    cancel(formName) {
      this.dialogFormVisible = false;
      this.$refs[formName].resetFields();
    }
  },
  mounted() {
    //当页面加载完成后自动执行。
    this.init();
  },
};
</script>
<style></style>

3.3.2、后端代码实现

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Clazz {
    private Integer id;
    private String name;
    private String room;
    private LocalDate beginDate;
    private LocalDate endDate;
    private Integer masterId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

controller层

@RestController
@RequestMapping("/clazzs")
@Slf4j
public class ClazzController {

    @Autowired
    private ClazzService clazzService;

    /**
     * 查询所有
     * @return
     */
    @GetMapping
    public Result findAll(){
        log.info("findAll...");
        List<Clazz> list = clazzService.findAll();
        return Result.success(list);
    }

    /**
     * 新增班级
     * @param clazz
     * @return
     */
    @PostMapping
    public Result save(@RequestBody Clazz clazz){
        log.info("save{}",clazz);
        clazzService.save(clazz);
        return Result.success();
    }

    /**
     * 根据id删除班级
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    public Result deleteById(@PathVariable Long id){
        log.info("deleteById...{}",id);
        clazzService.deleteById(id);
        return Result.success();
    }

    /**
     * 根据id查询班级
     */
    @GetMapping("/{id}")
    public Result findById(@PathVariable Long id){
        log.info("findById...{}",id);
        Clazz clazz = clazzService.findById(id);
        return Result.success(clazz);
    }

    /**
     * 更新班级
     * @param clazz
     * @return
     */
    @PutMapping
    public Result update(@RequestBody Clazz clazz){
        log.info("update...{}",clazz);
        clazzService.update(clazz);
        return Result.success();
    }
}

service

public interface ClazzService {
    /**
     * 查询所有班级
     * @return
     */
    List<Clazz> findAll();

    /**
     * 新增班级
     * @param clazz
     */
    void save(Clazz clazz);

    /**
     * 根据id删除班级
     * @param id
     */
    void deleteById(Long id);

    /**
     * 根据id查询班级
     * @param id
     * @return
     */
    Clazz findById(Long id);

    /**
     * 更新班级
     * @param clazz
     */
    void update(Clazz clazz);
}
@Service
public class ClazzServiceImpl implements ClazzService {

    @Autowired
    private ClazzMapper clazzMapper;

    /**
     * 查询所有班级
     *
     * @return
     */
    @Override
    public List<Clazz> findAll() {
        List<Clazz> clazzList = clazzMapper.findAll();
        return clazzList;
    }

    /**
     * 新增班级
     *
     * @param clazz
     */
    @Override
    public void save(Clazz clazz) {
        clazz.setCreateTime(LocalDateTime.now());
        clazz.setUpdateTime(LocalDateTime.now());
        clazzMapper.save(clazz);
    }

    /**
     * 根据id删除班级
     *
     * @param id
     */
    @Override
    public void deleteById(Long id) {
        clazzMapper.deleteById(id);
    }

    /**
     * 根据id查询班级
     *
     * @param id
     * @return
     */
    @Override
    public Clazz findById(Long id) {
        Clazz clazz = clazzMapper.findById(id);
        return clazz;
    }

    /**
     * 更新班级
     *
     * @param clazz
     */
    @Override
    public void update(Clazz clazz) {
        clazz.setUpdateTime(LocalDateTime.now());
        clazzMapper.update(clazz);
    }
}

mapper层

@Mapper
public interface ClazzMapper {
    /**
     * 查询所有班级
     * @return
     */
    @Select("select * from clazz")
    List<Clazz> findAll();

    /**
     * 新增班级
     * @param clazz
     */
    @Insert("insert into clazz values (null,#{name},#{room},#{beginDate},#{endDate},#{masterId}," +
            "#{createTime},#{updateTime})")
    void save(Clazz clazz);

    /**
     * 根据id删除班级
     * @param id
     */
    @Delete("delete from clazz where id = #{id}")
    void deleteById(Long id);

    /**
     * 根据id查询班级
     * @param id
     * @return
     */
    @Select("select * from clazz where id = #{id};")
    Clazz findById(Long id);

    /**
     * 更新班级
     * @param clazz
     */
    @Update("update clazz set name = #{name},room = #{room},begin_date = #{beginDate}," +
            "end_date = #{endDate},master_id = #{masterId} where id = #{id}")
    void update(Clazz clazz);
}
3.4、学生管理

我们可以把员工管理功能分为:

  1. 分页查询
  2. 带条件的分页查询
  3. 删除员工
  4. 新增员工
  5. 修改员工
3.4.1、前端核心代码
<template>
  <div class="app-container">

    <!--搜索表单-->
    <el-form :inline="true" :model="searchStudent" class="demo-form-inline">
      <el-form-item label="姓名">
        <el-input
          v-model="searchStudent.name"
          placeholder="请输入学生姓名"
        ></el-input>
      </el-form-item>
      <el-form-item label="性别">
        <el-select v-model="searchStudent.gender" placeholder="请选择">
          <el-option label="男" value="1"></el-option>
          <el-option label="女" value="2"></el-option>
        </el-select>
      </el-form-item>


      <el-form-item>
        <el-button type="primary" @click="onSubmit">查询</el-button>
         <el-button type="info" @click="clear">清空</el-button>
      </el-form-item>
    </el-form>



    <!--按钮-->
    <el-row>
      <el-button type="danger" size="medium" @click="deleteByIds">- 批量删除</el-button>
      <el-button type="primary" size="medium" @click="dialogVisible = true; student = { image: ''};" >+ 新增学生</el-button>
    </el-row>

    <!--添加数据对话框表单-->
    <el-dialog ref="form" title="编辑学生" :visible.sync="dialogVisible" width="30%">
      <el-form :model="student" :rules="rules" ref="student" label-width="80px" size="mini">
        <el-form-item label="姓名" prop="name">
          <el-input v-model="student.name"></el-input>
        </el-form-item>
        <el-form-item label="学号"  prop="no">
          <el-input v-model="student.no"></el-input>
        </el-form-item>

        <el-form-item label="性别"  prop="gender">
          <el-select v-model="student.gender" placeholder="请选择" style="width:100%" >
             <el-option
              v-for="item in genderList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>

        <el-form-item label="手机"  prop="phone">
          <el-input v-model="student.phone"></el-input>
        </el-form-item>

        <el-form-item label="班级"  prop="phone">
          <el-input v-model="student.clazzId"></el-input>
        </el-form-item>

        <!-- <el-form-item label="头像">
          <el-upload
            class="avatar-uploader"
            action="/api/upload"
            :headers="token"
            name="image"
            :show-file-list="false"
            :on-success="handleAvatarSuccess"
            :before-upload="beforeAvatarUpload"
          >
            <img v-if="emp.image" :src="emp.image" class="avatar" />
            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
          </el-upload>
        </el-form-item> -->

        <!-- <el-form-item label="职位">
          <el-select v-model="emp.job" placeholder="请选择" value-key="emp.job" style="width:100%">
            <el-option
              v-for="item in jobList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item> -->
<!-- 
        <el-form-item label="入职日期">
          <el-date-picker
            v-model="emp.entrydate"
            clearable
            type="date"
            value-format="yyyy-MM-dd" 
            placeholder="选择日期"
            size="small"
            style="width:100%"
          ></el-date-picker>
        </el-form-item> -->

        <!-- <el-form-item label="班级">
          <el-select v-model="student.clazzId" placeholder="请选择" style="width:100%">
            <el-option
              v-for="item in clazzList"
              :key="item.value"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item> -->

        <el-form-item>
          <el-button type="primary" @click="save('student')">提交</el-button>
          <el-button @click="cancel('student')">取消</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>

    <br>

    <!--表格-->
    <template>
      <el-table :data="tableData" style="width: 100%" border @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"  align="center"></el-table-column>
        <el-table-column  prop="name"  label="姓名"  align="center"></el-table-column>
        <!-- <el-table-column prop="image" label="头像" align="center">
          <template slot-scope="{ row }">
            <el-image style="width: auto; height: 40px; border: none; cursor: pointer" :src="row.image"></el-image>
          </template>
        </el-table-column> -->
        <el-table-column  prop="no"  label="学号"  align="center"></el-table-column>
        <el-table-column  prop="phone"  label="手机"  align="center"></el-table-column>
        <el-table-column align="center" label="性别">
          <template slot-scope="scope">
            <span style="margin-right: 10px">
            {{scope.row.gender == "1" ? "男" : "女"}}</span>
          </template>
        </el-table-column>

        <!-- <el-table-column  prop="degree"  label="学年"  align="center"></el-table-column> -->
     <!-- <el-table-column  prop="violationCount"  label="违纪总数"  align="center"></el-table-column> -->
        <!-- <el-table-column  prop="violationScore"  label="违纪分数"  align="center"></el-table-column> -->
        <el-table-column  prop="clazzId"  label="班级"  align="center"></el-table-column>

        <!-- <el-table-column align="center" label="职位">
          <template slot-scope="scope">
            <span style="margin-right: 10px" v-if="scope.row.job == 1">班主任</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 2">讲师</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 3">学工主管</span>
            <span style="margin-right: 10px" v-if="scope.row.job == 4">教研主管</span>
          </template>
        </el-table-column> -->

        <el-table-column align="center" label="入学日期">
          <template slot-scope="scope">
            {{ scope.row.createTime ? scope.row.createTime.replace('T', ' ') : '' }}
          </template>
        </el-table-column>
        <!-- <el-table-column align="center" label="最后操作时间">
          <template slot-scope="scope">
            {{scope.row.updateTime ? scope.row.updateTime.replace('T',' '):''}}
          </template>
        </el-table-column> -->

        <el-table-column align="center" label="操作">
          <template slot-scope="scope">
            <el-button type="primary" size="small" @click="update(scope.row.id)">编辑</el-button>
            <el-button type="danger" size="small" @click="deleteById(scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </template>

    <br>

    <!--分页工具条-->
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :background="background"
      :current-page="currentPage"
      :page-sizes="[5, 10, 15, 20]"
      :page-size="5"
      layout="total, sizes, prev, pager, next, jumper"
      :total="totalCount">
    </el-pagination>
  </div>
</template>

<script>
import { page, add, update, deleteById, selectById , findAll} from "@/api/student.js";
// import { findAll } from "@/api/dept.js";
import { getToken } from '@/utils/auth';

export default {
  data() {
    return {
      background: true,
      // 每页显示的条数
      pageSize: 5,
      // 总记录数
      totalCount: 100,
      // 当前页码
      currentPage: 1,
      // 添加数据对话框是否展示的标记
      dialogVisible: false,
      // 模型数据
      searchStudent: {
        name: "",
        gender: ""
      },
      student: {
        name: "",
        no: "",
        gender: "",
        phone: "",
        degree: "",
        violationCount: "",
        violationScore: "",
        clazzId: ""
      },
      clazzList: [],
      genderList: [{id: 1,name: "男"},{id: 2,name: "女"}],

      selectedIds: [],
      // 复选框选中数据集合
      multipleSelection: [],
      // 表格数据
      tableData: [],
      token: {token: getToken()},
      rules: {
          name: [
            {required: true, message: '请输入姓名', trigger: 'blur' },
            {min: 2, max: 10, message: '长度在 210 个字符', trigger: 'blur' }
          ],
          gender: [
            {required: true, message: '请选择性别', trigger: 'change' }
          ]
        }
    };
  },

  mounted() {
    this.page(); //当页面加载完成后,发送异步请求,获取数据
    findAll().then((result) => {
      this.deptList = result.data.data;
    });
  },
  
  methods: {
    // 查询分页数据
    page() {
      page(
        this.searchStudent.name,
        this.searchStudent.gender,
        // this.beginTime,
        // this.endTime,
        this.currentPage,
        this.pageSize
      ).then((res) => {
        this.totalCount = res.data.data.total;
        this.tableData = res.data.data.rows;
      });
    },

    // 复选框选中后执行的方法
    handleSelectionChange(val) {
      this.multipleSelection = val;
    },

    // 查询方法
    onSubmit() {
      this.currentPage = 1;
      this.page();
    },

    clear(){
      this.searchStudent = {name: "", gender: ""};
      this.page();
    },
    
    // 添加数据
    save(formName) {
      //校验表单
      this.$refs[formName].validate((valid) => {
          if (valid) {
            let operator;
            if (this.student.id) {//修改
              operator = update(this.student);
            } else { //新增
              operator = add(this.student);
            }
            operator.then((resp) => {
              if (resp.data.code == 1) {
                this.dialogVisible = false;
                this.page();
                this.$message({ message: "恭喜你,保存成功", type: "success" });
                this.student = { image: "" };
              } else {
                this.$message.error(resp.data.msg);
              }
            });
          }
      });
    },


    update(id) {
      //1. 打开窗口
      this.dialogVisible = true;
      //2. 发送请求

      selectById(id).then((result) => {
        if (result.data.code == 1) {
          this.student = result.data.data;
          this.student;
        }
      });
    },

    
    //分页
    handleSizeChange(val) {
      // 重新设置每页显示的条数
      this.pageSize = val;
      this.page();
    },

    handleCurrentChange(val) {
      // 重新设置当前页码
      this.currentPage = val;
      this.page();
    },


    //删除员工信息
    deleteById(id){
      this.$confirm("此操作将删除该数据, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
          //2. 发送AJAX请求
          deleteById(id).then((resp) => {
            if (resp.data.code == 1) {
              //删除成功
              this.$message.success("恭喜你,删除成功");
              this.page();
            } else {
              this.$message.error(resp.data.msg);
            }
          });
      }).catch(() => {
          //用户点击取消按钮
          this.$message.info("已取消删除");
        });
    },


    // 批量删除员工信息
    deleteByIds() {
      // 弹出确认提示框
      this.$confirm("此操作将删除该数据, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
          //用户点击确认按钮
          //1. 创建id数组, 从 this.multipleSelection 获取即可
          for (let i = 0; i < this.multipleSelection.length; i++) {
            this.selectedIds[i] = this.multipleSelection[i].id;
          }

          //2. 发送AJAX请求
          deleteById(this.selectedIds).then((resp) => {
            if (resp.data.code == "1") {
              //删除成功
              this.$message.success("恭喜你,删除成功");
              this.page();
            } else {
              this.$message.error(resp.data.msg);
            }
          });
      }).catch(() => {
          //用户点击取消按钮
          this.$message.info("已取消删除");
        });
    },

    cancel(formName){
      this.dialogVisible = false;
      this.$refs[formName].resetFields();
    },

    //文件上传相关
    handleAvatarSuccess(res, file) {
      this.student.image = res.data;
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === "image/jpeg";
      const isLt2M = file.size / 1024 / 1024 < 2;
      if (!isJPG) {
        this.$message.error("上传头像图片只能是 JPG 格式!");
      }
      if (!isLt2M) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isJPG && isLt2M;
    },
  },


  watch: {
    entrydate(val) {
      if (val && val.length >= 2) {
        this.beginTime = val[0];
        this.endTime = val[1];
      } else {
        this.beginTime = "";
        this.endTime = "";
      }
    },
  },
};
</script>
<style>
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 100px;
  height: 100px;
  line-height: 100px;
  text-align: center;
}
.avatar {
  width: 100px;
  height: 100px;
  display: block;
}
</style>
3.4.2、后端代码实现

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private String no;
    private Short gender;
    private String phone;
    private Short degree;
    private Short violationCount;
    private Short violationScore;
    private Short clazzId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentData {
    private List categoryList;
    private List dataList;
}

controller

package com.tlias.controller;

import com.github.pagehelper.Page;
import com.tlias.entity.PageBean;
import com.tlias.entity.Student;
import com.tlias.result.Result;
import com.tlias.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/students")
@Slf4j
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping
    public Result page(String name, Short gender,
                       @RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize){
        log.info("员工分页查询 page={},pageSize={}", page, pageSize);
        PageBean pageBean = studentService.page(page,pageSize,name,gender);
        return Result.success(pageBean);
    }

    /**
     * 查询全部
     * @return
     */
    @GetMapping("/list")
    public Result findAll(){
        log.info("findAll...");
        List<Student> studentList = studentService.findAll();
        return Result.success(studentList);
    }

    /**
     * 新增学生
     * @param student
     * @return
     */
    @PostMapping()
    public Result save(@RequestBody Student student){
        log.info("save student...{}",student);
        studentService.save(student);
        return Result.success();
    }

    /**
     * 根据id查询学生
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result findById(@PathVariable Long id){
        log.info("findById...{}",id);
        Student student = studentService.findById(id);
        return Result.success(student);
    }

    /**
     * 修改学生
     * @param student
     * @return
     */
    @PutMapping
    public Result update(@RequestBody Student student){
        log.info("update student...{}",student);
        studentService.update(student);
        return Result.success();
    }

    /**
     * 删除
     * @param ids
     * @return
     */
    @DeleteMapping("/{ids}")
    public Result deleteById(@PathVariable List<Integer> ids){
        log.info("deleteById...{}",ids);
        studentService.deleteById(ids);
        return Result.success();
    }

}

service

public interface StudentService {
    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @param name
     * @param gender
     * @return
     */
    PageBean page(Integer page, Integer pageSize, String name, Short gender);

    /**
     * 查询全部
     * @return
     */
    List<Student> findAll();

    /**
     * 新增学生
     * @param student
     */
    void save(Student student);

    /**
     * 根据id查询学生
     * @param id
     * @return
     */
    Student findById(Long id);

    /**
     * 修改学生
     * @param student
     */
    void update(Student student);

    /**
     * 根据id删除
     * @param ids
     */
    void deleteById(List<Integer> ids);
}

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    /**
     * 分页查询
     *
     * @param page
     * @param pageSize
     * @param name
     * @param gender
     * @return
     */
    @Override
    public PageBean page(Integer page, Integer pageSize, String name, Short gender) {
        PageHelper.startPage(page,pageSize);
        List<Student> list = studentMapper.page(name,gender);
        Page<Student> studentPage = (Page<Student>) list;
        return new PageBean(studentPage.getTotal(),studentPage.getResult());
    }

    /**
     * 查询全部
     *
     * @return
     */
    @Override
    public List<Student> findAll() {
        List<Student> studentList = studentMapper.findAll();
        return studentList;
    }

    /**
     * 新增学生
     *
     * @param student
     */
    @Override
    public void save(Student student) {
        student.setCreateTime(LocalDateTime.now());
        student.setUpdateTime(LocalDateTime.now());
        studentMapper.save(student);
    }

    /**
     * 根据id查询学生
     *
     * @param id
     * @return
     */
    @Override
    public Student findById(Long id) {
        return studentMapper.findById(id);
    }

    /**
     * 修改学生
     *
     * @param student
     */
    @Override
    public void update(Student student) {
        student.setUpdateTime(LocalDateTime.now());
        studentMapper.update(student);
    }

    /**
     * 根据id删除
     *
     * @param ids
     */
    @Override
    public void deleteById(List<Integer> ids) {
        studentMapper.deleteById(ids);
    }

}

mapper

@Mapper
public interface StudentMapper {
    @Select("SELECT clazz.`name` as 'class',COUNT(student.clazz_id) as 'classcount' " +
            "FROM clazz LEFT JOIN student ON student.clazz_id = clazz.id GROUP BY clazz.`name` ;")
    List<Map<String, Object>> getStudentData();

    /**
     * 分页查询
     * @param name
     * @param gender
     * @return
     */
    List<Student> page(@Param("name") String name,@Param("gender") Short gender);

    /**
     * 查询全部
     * @return
     */
    @Select("select * from student")
    List<Student> findAll();

    /**
     * 新增学生
     * @param student
     */
    @Insert("insert into student(name, no, gender, phone,clazz_id, create_time, update_time) " +
            "value(#{name},#{no},#{gender},#{phone},#{clazzId},#{createTime},#{updateTime})")
    void save(Student student);

    /**
     * 根据id查询学生
     * @param id
     * @return
     */
    @Select("select * from student where id = #{id};")
    Student findById(Long id);

    /**
     * 修改学生
     * @param student
     */
    @Update("update student set name = #{name},no = #{no},gender = #{gender}," +
            "phone = #{phone},clazz_id = #{clazzId} where id = #{id}")
    void update(Student student);

    /**
     * 根据id删除
     * @param ids
     */
    void deleteById(@Param("ids") List<Integer> ids);
}
<?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.tlias.mapper.StudentMapper">

    <delete id="deleteById">
        delete from student where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
    <select id="page" resultType="com.tlias.entity.Student">
        select * from student
        <where>
            <if test="name != null and name != ''">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender != #{gender}
            </if>
        </where>
    </select>
</mapper>
3.5、数据统计
3.5.1、前端核心代码
<template>
  <div class="chart-container">

    <div  style="width: 50%; ">
      <h1>员工性别统计</h1> <br>
      <div id="myChart1"  style="width: 100%; height: 500px"></div>
    </div>

    <div  style="width: 50%; ">
      <h1>员工职位统计</h1> <br>
      <div id="myChart2"  style="width: 100%; height: 500px"></div>
    </div>

  </div>
</template>

<script>
import * as echarts from 'echarts'
import { getGenderData, getJobData } from "@/api/report.js";

export default {
  data() {
    return {
      genderCountList: [],
      jobCategoryList:[],
      jobDataList:[]
    }
  },
  mounted() { 
    this.getGenderData(); //获取员工性别统计数据
    this.getJobData(); //获取员工职位统计数据
  },

  methods: {
    //获取员工性别统计数据
    getGenderData(){
      getGenderData().then((result) => {
          if(result.data.code == 1){
             this.genderCountList = result.data.data;
             
             this.initChart1();
          }
      });
    },

    //获取员工职位统计数据
    getJobData(){
      getJobData().then((result) => {
          if(result.data.code == 1){
             this.jobCategoryList = result.data.data.categoryList;
             this.jobDataList = result.data.data.dataList;

             this.initChart2();
          }
      });
    },

    initChart1() {
      // 获取图表容器, 创建图表实例
      let myChart = echarts.init(document.querySelector('#myChart1'));
       
      let option = {
        tooltip: {
          trigger: 'item'
        },
        legend: {
          top: '5%',
          left: 'center'
        },
        series: [{
            name: '员工性别统计',
            type: 'pie',
            radius: ['40%', '70%'],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#fff',
              borderWidth: 2
            },
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 40,
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: this.genderCountList
          }
        ]
      };

      // 绘制图表
      myChart.setOption(option);
    },


    initChart2() {
      // 获取图表容器, 创建图表实例
      let myChart = echarts.init(document.querySelector('#myChart2'));
       
      let option = {
        xAxis: {
          type: 'category',
          data: this.jobCategoryList
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            data: this.jobDataList,
            type: 'bar'
          }
        ]
      };
      
      // 绘制图表
      myChart.setOption(option);
    }
  }
};
</script>

<style>
  h1 {
    text-align: center;
  }

  .chart-container {
    display: flex;
  }
  .chart-container > div {
    flex: 1;
  }
</style>
3.5.2、后端代码实现

实体类

@Data
public class PieChartData {
    private String name;
    private String value;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentData {
    private List categoryList;
    private List dataList;
}

controller

@RestController
@RequestMapping("/report")
@Slf4j
public class ReportController {

    @Autowired
    private ReportService service;

    @GetMapping("/empGenderData")
    public Result getEmpGenderData(){
        log.info("员工性别信息信息");
        List<PieChartData> list = service.getEmpGenderData();
        return Result.success(list);
    }

    @GetMapping("/empJobData")
    public Result getEmpJobData(){
        log.info("员工职位信息");
        JobData jobData  = service.getEmpJobData();
        return Result.success(jobData);
    }

    @GetMapping("/studentData")
    public Result getStudentData(){
        log.info("学员人数统计");
        StudentData studentData = service.getStudentData();
        return Result.success(studentData);
    }
}

service层

public interface ReportService {
    /**
     * 员工性别统计
     * @return
     */
    List<PieChartData> getEmpGenderData();

    /**
     * 员工职位信息统计
     * @return
     */
    JobData getEmpJobData();

    /**
     * 学员统计
     * @return
     */
    StudentData getStudentData();
}
@Service
public class ReportServiceImpl implements ReportService {

    @Autowired
    private EmpMapper empMapper;

    @Autowired
    private StudentMapper studentMapper;

    /**
     * 员工性别统计
     *
     * @return
     */
    @Override
    public List<PieChartData> getEmpGenderData() {
        List<PieChartData> list = empMapper.getEmpGenderData();
        return list;
    }

    /**
     * 员工职位信息统计
     *
     * @return
     */
    @Override
    public JobData getEmpJobData() {
        List<Map<String,Object>> mapList = empMapper.getEmpJobData();
        System.out.println(mapList);
        if (!CollectionUtils.isEmpty(mapList)) {
            List<Object> categoryList = mapList.stream().map(map -> {
                return map.get("job");
            }).collect(Collectors.toList());

            List<Object> dataList = mapList.stream().map(map -> {
                return map.get("jobcount");
            }).collect(Collectors.toList());

            JobData jobData = new JobData();
            jobData.setCategoryList(categoryList);
            jobData.setDataList(dataList);
            System.out.println(jobData);
            return jobData;
        }

        return null;
    }

    /**
     * 学员统计
     *
     * @return
     */
    @Override
    public StudentData getStudentData() {
        List<Map<String,Object>> mapList = studentMapper.getStudentData();
        System.out.println(mapList);

        if (!CollectionUtils.isEmpty(mapList)){
            List<Object> categoryList = mapList.stream().map(map -> {
                return map.get("class");
            }).collect(Collectors.toList());

            List<Object> dataList = mapList.stream().map(map -> {
                return map.get("classcount");
            }).collect(Collectors.toList());

            return new StudentData(categoryList,dataList);
        }
        return null;
    }
}
3.6、登录功能

我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登录,就直接访问到了后台。 这是不安全的,所以要做登录认证。 最终我们要实现的效果就是用户必须登录之后,才可以访问后台系统中的功能

JWT令牌最典型的应用场景就是登录认证:

  1. 在浏览器发起请求来执行登录操作,此时会访问登录的接口,如果登录成功之后,我们需要生成一个jwt令牌,将生成的 jwt令牌返回给前端。
  2. 前端拿到jwt令牌之后,会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端。
  3. 服务端统一拦截请求之后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否是有效。如果有效,就直接放行进行请求的处理。

在JWT登录认证的场景中我们发现,整个流程当中涉及到两步操作:

  1. 在登录成功之后,要生成令牌。
  2. 每一次请求当中,要接收令牌并对令牌进行校验。
3.6.1、前端核心代码
<template>
  <div class="login-container">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">

      <div class="title-container">
        <h3 class="title">Tlias智能学习辅助系统</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
        </span>
      </el-form-item>

      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>

    </el-form>
  </div>
</template>

<script>
import { validUsername } from '@/utils/validate'
import { login } from '@/api/user'
import { setToken } from '@/utils/auth'
export default {
  name: 'Login',
  data() {

    //用户名校验规则
    const validateUsername = (rule, value, callback) => {
      if (!validUsername(value)) {
        callback(new Error('请输入正确的用户名'))
      } else {
        callback()
      }
    }

    //用户名校验规则
    const validatePassword = (rule, value, callback) => {
      if (value.length < 6) {
        callback(new Error('密码长度至少为6位'))
      } else {
        callback()
      }
    }

    //数据模型
    return {
      loginForm: {
        username: 'jinyong',
        password: '123456'
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
      loading: false,
      passwordType: 'password',
      redirect: undefined
    }
  },

  methods: {
    //展示密码
    showPwd() {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    
    //登录方法
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          
          //调用登录后端接口
          login(this.loginForm).then((result) => {
            console.log(result)
            if (result.data.code == 1) {
              setToken(result.data.data);
              console.log('login success');
              this.$router.push('/');
            } else {
              this.$message.error(result.data.msg);
              this.loading = false
            }
          });
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
  }
}
</script>

<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg:#283443;
$light_gray:#fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>

<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
      font-family: '楷体';
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

3.6.2、后端代码实现

创建工具类,实现jwt令牌的发放和解析

public class JwtUtils {

    private static String signKey = "jwtHelloWorld";
    private static Long expire = 60 * 60 * 1000L;

    public static String generateToken(Map<String, Object> claims) {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return token;
    }

    public static Claims parseToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(token)
                .getBody();
        return claims;
    }
}

创建拦截器,进行拦截

/**
 * 自定义拦截器
 */
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1、获取请求url
        String url = request.getRequestURI().toString();

        //3、获取请求头中的token
        String token = request.getHeader("token");
        if (!StringUtils.hasLength(token)){ //不存在
            log.info("获取令牌为空");
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.getWriter().write(json);
            return false;
        }

        //4、解析token,失败则未登录
        try {
            Claims claims = JwtUtils.parseToken(token);
        } catch (Exception e) {
            log.info("解析令牌错误");
            Result notLogin = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(notLogin);
            response.getWriter().write(json);
            return false;
        }
        //5、放行
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

controller层

@RestController
@Slf4j
public class LoginController {

    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("员工登录{}",emp);
        Emp empLogin = empService.login(emp);
        if (empLogin != null){
            Map<String, Object> map = new HashMap<>();
            map.put("id", empLogin.getId());
            map.put("username", empLogin.getUsername());
            String token = JwtUtils.generateToken(map);
            return Result.success(token);
        }
        return Result.error("查无此用户");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1926323.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab 计算一个平面与一条直线的交点

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里使用一种很有趣的坐标:Plucker线坐标,它的定义如下所示: 这个坐标有个很有趣的性质,将直线 L L L与由其齐次坐标 V = (

STM32的定时器HAL库

目录 一&#xff0c;定时器的介绍 一&#xff0c;定时器的介绍 1. STM32F103C8T6微控制器内部集成了多种类型的定时器&#xff0c;这些定时器在嵌入式系统中扮演着重要角色&#xff0c;用于计时、延时、事件触发以及PWM波形生成、脉冲捕获等应用。 1.1 高级定时器&…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(六)-人工智能控制的自主无人机用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

从汇编层看64位程序运行——函数的调用和栈平衡

函数调用 不知道有没有人想过一个问题&#xff1a;A函数调用B函数&#xff0c;B函数是如何知道在调用结束后回到A函数中的&#xff1f; 比如下面的代码&#xff0c;main函数调用foo。当foo执行完毕&#xff0c;需要执行main函数的return 0语句。但是main和foo是割裂的&#x…

【排序 】

目录 1&#xff0c; 排序的概念及引用 1.1 排序的概念 1.2 常见的排序算法 2&#xff0c; 常见排序算法的实现 2.1 插入排序 2.1.1基本思想&#xff1a; 2.1.2 直接插入排序 2.1.3 希尔排序( 缩小增量排序 )(面试很少问) 2.2 选择排序 2.2.1基本思想&#xff1a; 2.…

Java巅峰之路---基础篇---综合练习(面向对象)

目录 文字版格斗游戏 基础版 souf输出语句 进阶版 键盘录入的说明 复杂对象数组练习 需求&#xff1a; 添加和遍历 删除和遍历 修改和遍历 文字版格斗游戏 基础版 格斗游戏&#xff0c;每个游戏角色的姓名&#xff0c;血量&#xff0c;都不相同&#xff0c;在选定人…

2024最新Cloudways主机使用教程(含最新Cloudways折扣码)

Cloudways是一家提供云托管服务的公司&#xff0c;可以帮助你轻松管理和运行你的网站。本教程是Cloudways主机注册和使用教程。Cloudways界面简洁&#xff0c;使用方便&#xff0c;不需要复杂的设置&#xff0c;就能快速搭建一个WordPress网站。它的主机功能包括高级缓存和Bree…

Linux命令更新-Vim 编辑器

简介 Vim 是 Linux 系统中常用的文本编辑器&#xff0c;功能强大、可扩展性强&#xff0c;支持多种编辑模式和操作命令&#xff0c;被广泛应用于程序开发、系统管理等领域。 1. Vim 命令模式 Vim 启动后默认进入命令模式&#xff0c;此时键盘输入的命令将用于控制编辑器本身&…

QT控件篇三

一、微调框 微调框&#xff08;QSpinBox&#xff09;是一个常用的Qt控件&#xff0c;允许用户通过增加或减少值来输入数字。分为两种, 整型-QSpinBox 浮点 QDoubleSpinBoxQSpinBox&#xff08;微调框&#xff09;的 setSingleStep 函数可以用来设置每次调整的步长&#xff08;…

Kafka基础入门-代码实操

Kafka是基于发布/订阅模式的消息队列&#xff0c;消息的生产和消费都需要指定主题&#xff0c;因此&#xff0c;我们想要实现消息的传递&#xff0c;第一步必选是创建一个主题&#xff08;Topic&#xff09;。下面我们看下在命令行和代码中都是如何创建主题和实现消息的传递的。…

TDesign组件库日常应用的一些注意事项

【前言】Element&#xff08;饿了么开源组件库&#xff09;在国内使用的普及率和覆盖率高于TDesign-vue&#xff08;腾讯开源组件库&#xff09;&#xff0c;这也导致日常开发遇到组件使用上的疑惑时&#xff0c;网上几乎搜索不到其文章解决方案&#xff0c;只能深挖官方文档或…

Python编程工具PyCharm和Jupyter Notebook的使用差异

在编写Python程序时需要用到相应的编程工具&#xff0c;PyCharm和Jupyter Notebook是最常用2款软件。 PyCharm是很强大的综合编程软件&#xff0c;代码提示、代码自动补全、语法检验、文本彩色显示等对于新手来说实在太方便了&#xff0c;但在做数据分析时发现不太方便&#xf…

UGUI优化篇(更新中)

UGUI优化篇 1. 基础概念2. 重要的类1. MaskableGraphic类继承了IMaskable类2. 两种遮罩的实现区别RectMask2DMask 3. 渲染部分知识深度测试深度测试的工作原理 渲染队列透明物体在渲染时怎么处理为什么透明效果会造成性能问题 1. 基础概念 所有UI都由网格绘制的如image由两个三…

成为CMake砖家(2): macOS创建CMake本地文档的app

大家好&#xff0c;我是白鱼。 使用 CMake 的小伙伴&#xff0c; 有的是在 Windows 上&#xff0c; 还有的是在 macOS 上。之前咱们讲了 windows 上查看 cmake 本地 html 文档的方式&#xff0c; 这篇讲讲 macOS 上查看 cmake 本地 html 文档的方法。 1. 问题描述 当使用 CMa…

数模·图论

matlab中图的表示 顶点集权值集的形式 s是源点&#xff0c;t是终点&#xff0c;w是对应的权值 调用graph(s,t,w)作为参数创建图 调用plot函数绘图plot(G,EdgeLabel,G.Edges.Weight,LineWidth,2) 设置x和y的坐标范围set(gca,XTick,[],YTick,[]) s[1 2 3]; t[4 1 2]; w[5 2 6]; …

程序包不存在【java: 程序包org.springframework.boot不存在】

1、问题提示&#xff1a;java: 程序包org.springframework.boot不存在 注意&#xff1a;已经下载好了程序包&#xff0c;就是提示不存在 2、解决办法

一个开源完全免费的无损视频或音频的剪切/裁剪/分割/截取和视频合并工具

大家好&#xff0c;今天给大家分享一款致力于成为顶尖跨平台FFmpeg图形用户界面应用的软件工具LosslessCut。 LosslessCut是一款致力于成为顶尖跨平台FFmpeg图形用户界面应用的软件工具&#xff0c;专为实现对视频、音频、字幕以及其他相关媒体资产的超高速无损编辑而精心打造。…

《后端程序猿 · EasyPOI 导入导出》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

蓝桥杯嵌入式第十五届模拟考试3解析

1 题目 2 程序 /* USER CODE BEGIN PTD */ char buf1[20],buf2[20],buf3[20],buf4[20],buf5[20],buf6[20],buf7[20],buf8[20],buf9[20]; struct keys {int step;int length;int state; }key[5]; int display; double v1,v2; int t; double v1l1.2,v1u2.2,v2l1.4,v2u3.0; dou…

深度学习复盘与论文复现D

文章目录 一、新环境搭建与适应1、easy_install和pip的安装使用2、关于安装包超时的解决方案3、brew安装包安装4、使用新环境运行以前项目5、解决win的pycharm修改内存后无法启动 二、Dataset 数据读取问题1、Lightning Torch 读取数据2、Pytorch的DataLoader数据读取机制3、Py…