从0到1构建可视化大屏-员工管理

举报
文艺倾年 发表于 2022/08/06 17:34:18 2022/08/06
【摘要】 前言:blush:你好,我是小航,一个正在变秃、变强的文艺倾年。:bell:本专栏【仗剑天涯】每晚11:30-12:30更新,欢迎大家多多关注 需求分析(3min)序号类型名称描述1基本功能部门管理l 管理员可以维护部门信息 l 部门可以只有一级层次结构2基本功能员工信息l 管理员录入员工信息(工号、姓名、性别、出生日期、籍贯、学历、毕业院校、专业等) l 管理员指定员工所属的部门3基...

前言

:blush:你好,我是小航,一个正在变秃、变强的文艺倾年。
:bell:本专栏【仗剑天涯】每晚11:30-12:30更新,欢迎大家多多关注

需求分析(3min)

序号 类型 名称 描述
1 基本功能 部门管理 l 管理员可以维护部门信息 l 部门可以只有一级层次结构
2 基本功能 员工信息 l 管理员录入员工信息(工号、姓名、性别、出生日期、籍贯、学历、毕业院校、专业等) l 管理员指定员工所属的部门
3 基本功能 查询 l 管理员可以查询部门下所有员工 l 管理员可根据员工工号、姓名等查询员工详细信息
4 扩展功能 简历管理 l 每个员工有一份Word格式的简历,可以存入系统中,并可供下载
5 扩展功能 员工照片 l 每个员工有一张照片,可存入系统中

根据需求,项目主要分两个模块:部门管理(树形展示)、员工管理(录入信息,指定部门)

技术选型(1min)

SpringBoot、MyBatis-plus、Vue、Webpack、DataV、Echarts、OSS

项目环境:jdk8、Vue-cli-2.0、DataV-2.7.3、Echarts-4.6.0、Webpack-4.0、Npm-6.13、Node-v12.16

数据库设计(10min)

数据库名:tyut_employee

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

SpringBoot初始化(10min)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
先暂且装两个依赖:
在这里插入图片描述
在这里插入图片描述
降一降SpringBoot版本,搞搞相关的配置:

在这里插入图片描述
依赖:

	<dependencies>
        <!-- commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--        HttpUtils-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.5</version>
        </dependency>
        <!-- Mybaits-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok -->
        <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>
    </dependencies>

application.yml

server:
  port: 9999

spring:
  application:
    name: tyut_employee

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tyut_employee?useSSL=false&serverTimezone=UTC
    username: root
    password: root

引入独家配方:
在这里插入图片描述

生成基础代码(5min)

这里我们使用人人代码生成器,小航魔改代码生成器还在搞ing
在这里插入图片描述
别忘了修改数据源:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
排除错后:
在这里插入图片描述

前端大屏初始化(15min)

组件库:
介绍 | DataV (jiaminghi.com)
Element-UI

git clone https://gitee.com/MTrun/big-screen-vue-datav.git
cd big-screen-vue-datav 
yarn install 
yarn run serve

初始化完成效果:
在这里插入图片描述
我们接下来使用Vscode打开
安装需要的依赖:

yarn add element-ui
yarn add axios

在这里插入图片描述
在main.js里面注册插件:
这里就按需引入了,一引百引叭
在这里插入图片描述
封装Axios(这里封装风格本人不太喜欢,后期会改,这里先应付代码生成器)

httpRequest.js(慢慢再改叭,先直接复制叭)

import axios from 'axios'
import qs from 'qs'
import merge from 'lodash/merge'


const http = axios.create({
  	timeout: 20000,
    baseURL: 'http://localhost:9999',
})


/**
 * 请求地址处理
 * @param {*} actionName action方法名称
 */
http.adornUrl = (actionName) => {
  // 非生产环境 && 开启代理, 接口前缀统一使用[/proxyApi/]前缀做代理拦截!
  return actionName
}

/**
 * get请求参数处理
 * @param {*} params 参数对象
 * @param {*} openDefultParams 是否开启默认参数?
 */
http.adornParams = (params = {}, openDefultParams = true) => {
  var defaults = {
    't': new Date().getTime()
  }
  return openDefultParams ? merge(defaults, params) : params
}

/**
 * post请求数据处理
 * @param {*} data 数据对象
 * @param {*} openDefultdata 是否开启默认数据?
 * @param {*} contentType 数据格式
 *  json: 'application/json; charset=utf-8'
 *  form: 'application/x-www-form-urlencoded; charset=utf-8'
 */
http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => {
  var defaults = {
    't': new Date().getTime()
  }
  data = openDefultdata ? merge(defaults, data) : data
  return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
}

export default http

在这里插入图片描述
引入刚刚生成的代码:
在这里插入图片描述
这里加一点点美化叭,修改Element-ui主题色为透明:
tables.scss

/*表格样式*/
.el-table{
    background-color: rgba(255,255,255,0);
    color: #FFFFFF;
    border:none;
    &:before,
    *:before{
      height: 0px!important;
    }
    th,tr,td{
      background-color: rgba(255,255,255,0);
    }
    th>.cell{
      color: #24b2bc;
    }
    *{
      border:none;
    }
    .el-table__header{
      tr{
        background-color: rgba(255,255,255,0);
        th{
          border:none;
          color: #fff;
          background-color: rgba(255,255,255,0);
        }
        .is-leaf{
          border-bottom:none;
        }
      }
    }
    .el-table__body{
      tr{
        background-color: rgba(255,255,255,0);
        td{
          border:none;
          button{
            background-color:#245cbc;
            padding:5px;
            color:#fff;
          }
        }
      }
      .el-table__empty-text{
        color: white;
      }
    }
    //修改带操作的表格背景色
    .el-table__row{
      &:hover{
        td{
          background-color: rgba(255,255,255,0) !important;
        }
      }
    }
    //修改固定右边操作的表格背景悬浮色
    .hover-row{
      td{
        background-color: rgba(255,255,255,0) !important;
      }
    }

    
  }

  .el-pagination button:disabled{
    background-color: #0b0f1c;
  }
  .el-button--default{
    background-color:rgb(83, 194, 83);
    color:#fff;
    border-color:green;
  }
  .el-pager li{
    background:none;
    border:1px solid #409EFF;
    border-radius:2px;
    box-shadow: 0 0 5px #409EFF inset;
  }

// 时间选择器样式修改
.el-date-editor .el-range-input, .el-date-editor .el-range-separator{
  background:none;
}

// 弹框样式修改
  .el-dialog{
    background-color:#0b0f1c;
  }
  .el-dialog .el-dialog__body .el-form label{
    color:#fff;
  }
  .el-dialog .el-dialog__header .el-dialog__title{
    color:#fff;
  }
  .el-input__inner{
    background-color:rgba(255, 255, 255, 0);
    border-color:#409EFF;
    box-shadow: 0 0 5px #409EFF inset;
  }


  // 选择框修改
  .el-date-editor .el-range-input, .el-date-editor .el-range-separator{
    background:none;
  }
  .el-form-item{
    margin-left:10px;
  }
  .el-pagination{
    width:420px;
    position:fixed;
    right:0
  }

// 下标选择
  .el-tabs__item{
    color:#a5b0b6;
  }

在这里插入图片描述

前端布局(35min)

先设计四个模块:首页、部门管理、员工管理、系统设置
在这里插入图片描述
配置路由:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
  path: '/',
  name: 'index',
  component: () => import('../views/index.vue')
},
{
  path: '/admin',
  name: 'admin',
  component: () => import('../views/employee/admin.vue')
},{
  path: '/dept',
  name: 'dept',
  component: () => import('../views/employee/dept.vue')
},{
  path: '/user',
  name: 'user',
  component: () => import('../views/employee/user.vue')
},{
  path: '/',
  name: 'userdept',
  component: () => import('../views/employee/userdept.vue')
},

]
const router = new VueRouter({
  mode: 'hash',
  routes: routes
})

export default router

访问效果:(如果遇到权限问题,请去掉isAuth()后端未整合shiro)
在这里插入图片描述
这样的页面我们是不喜欢的,所以我们新建一个layout布局

在这里插入图片描述
重新配置一下路由:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: "/",
    name: "layout",
    component: () => import('../layout/index.vue'),
    children: [
      {
        path: '/index',
        name: 'index',
        component: () => import('../views/index.vue')
      },
      {
        path: '/admin',
        name: 'admin',
        component: () => import('../views/employee/admin.vue')
      },
      {
        path: '/dept',
        name: 'dept',
        component: () => import('../views/employee/dept.vue')
      },
      {
        path: '/user',
        name: 'user',
        component: () => import('../views/employee/user.vue')
      },
      {
        path: '/userdept',
        name: 'userdept',
        component: () => import('../views/employee/userdept.vue')
      },
    ]
  },
]
const router = new VueRouter({
  mode: 'hash',
  routes: routes
})

export default router

效果展示:(是不是好看多了呢!)
在这里插入图片描述
为了分辨率统一,这里对css略作了修改

在这里插入图片描述

登录注册实现(30min)

前端实现:

新建一个Login.vue

登录注册页面算是基本功吧,(头像 + 标题 + 输入框 + 按钮)

<template>
  <div class="login-container">
 
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
      <!-- 头像区域 -->
      <div class="avatar-box">
        <img src="../assets/logo.png">
      </div>
 
      <div class="title-container">
        <h3 class="title">智慧员工管理系统</h3>
      </div>
 
      <el-form-item prop="username">
        <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">
        <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" />
      </el-form-item>

      <div>
        <el-button type="primary" style="width:100%;margin-bottom:20px;" @click.native.prevent="handleLogin">登录</el-button>
      </div> 
    </el-form>
  </div>
</template>
 
<script>
 
export default {
  name: 'Login',
  data() {
    // 校验用户名
    const validateUsername = (rule, value, callback) => {
      if (value.length == 0) {
        callback(new Error('用户名不能为空!'))
      } else {
        callback()
      }
    }
    // 校验密码
    const validatePassword = (rule, value, callback) => {
      if (value.length < 6) {
        callback(new Error('密码最少为6位字符!'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        username: '',
        password: ''
      },
      // 登录规则
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
      loading: false,
      passwordType: 'password'
    }
  },
  methods: {
    // 登录业务
    handleLogin() {
     
    }
  }
}
</script>
 
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;

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

.login-container {
  min-height: 100%;
  width: 100%;
  overflow: hidden;
  background: url(~@/assets/pageBg.png);
  background-size: 100% 100%;

  .el-input {
    display: inline-block;
    height: 47px;
    width: 100%;
 
    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;
      }
    }
  }
   // 头像
  .avatar-box {
    margin: 0 auto;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    border: 1px solid #409eff;
    box-shadow: 0 0 10px #409eff;
    position: relative;
    bottom: 20px;
    img {
      width: 100%;
      height: 100%;
      border-radius: 50%;
    }
  }
  // 登录表单
  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }
 
  .title-container {
    position: relative;
    .title {
      font-size: 30px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: 500;
    }
  }
}
</style>

配置路由:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [

  {
    path: '/login',
    name: 'login',
    component: () => import('../views/login1.vue')
  },
  {
    path: "/",
    name: "layout",
    component: () => import('../layout/index.vue'),
    children: [
      {
        path: '/index',
        name: 'index',
        component: () => import('../views/index.vue')
      },
      {
        path: '/admin',
        name: 'admin',
        component: () => import('../views/employee/admin.vue')
      },
      {
        path: '/dept',
        name: 'dept',
        component: () => import('../views/employee/dept.vue')
      },
      {
        path: '/user',
        name: 'user',
        component: () => import('../views/employee/user.vue')
      },
      {
        path: '/userdept',
        name: 'userdept',
        component: () => import('../views/employee/userdept.vue')
      },
    ]
  },
]
const router = new VueRouter({
  mode: 'hash',
  routes: routes
})

export default router

实现效果:
在这里插入图片描述
后端实现:

首先前后端需要对接,配置一下全局跨域:(具体含义-注释都给出了)

在这里插入图片描述

package com.tyut.employee.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 全局跨域
 */

@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 允许任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 允许任何头
        corsConfiguration.addAllowedMethod("*"); // 允许任何方法(post、get等)
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); // 对接口配置跨域设置
        return new CorsFilter(source);
    }
}

加入后端校验(JSR303):

1.导入依赖:

<!-- 属性效验-->
 <dependency>
     <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>2.0.1.Final</version>
 </dependency>

2.目标Bean标注注解(AdminEntity表)

@Data
@TableName("em_admin")
public class AdminEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 主键ID
	 */
	@TableId
	private Integer id;
	/**
	 * 用户名
	 */
	@NotEmpty
	private String username;
	/**
	 * 密码
	 */
	@Length(min = 6, message = "密码至少为6位")
	private String password;

}

3.新建LoginController

package com.tyut.employee.controller;

import com.tyut.employee.entity.AdminEntity;
import com.tyut.employee.service.AdminService;
import com.tyut.employee.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * @author xh
 * @Date 2022/5/25
 */
@RestController
public class LoginController {
    @Autowired
    AdminService adminService;
    @PostMapping("/login")
    public R login(@Valid @RequestBody AdminEntity loginVo, BindingResult bindingResult) {
        // 判断是否验证成功
        if (bindingResult.hasErrors()) {
            return R.error(101, "账号、密码不能为空,且密码必须大于6位");
        }
        AdminEntity adminEntity = adminService.login(loginVo);
        return adminEntity != null ? R.ok().put("adminEntity", adminEntity) : R.error(102, "账号或密码错误");
    }
}

编写service、dao层:

@Override
    public AdminEntity login(AdminEntity loginVo) {
        AdminDao adminDao = this.baseMapper;
        // 1.根据用户名查询数据库是否存在账号
        @NotBlank String username = loginVo.getUsername();
        AdminEntity adminEntity = adminDao.selectOne(new QueryWrapper<AdminEntity>().eq("username", username));
        // 2.若存在核对密码
        // TODO 加盐解密
        if(adminEntity != null && adminEntity.getPassword().equals(loginVo.getPassword())) {
            return adminEntity;
        }
        return null;
    }

使用Apifox测试接口,并编写接口文档:(Postman、Swagger也可以,这次咱们用个新的)
在这里插入图片描述
新建一个快捷请求:
在这里插入图片描述
测试结果:
在这里插入图片描述
在这里插入图片描述
对接前端:

	// 登录业务
    handleLogin() {
      this.$http({
        url: this.$http.adornUrl('/login'),
        method: 'post',
        data: this.loginForm
      }).then(({ data }) => {
        if (data && data.code === 0) {
          console.log("登录成功")
        } else {
          console.log(data.msg)
        }
      })
    }

在这里插入图片描述
接下来我们对登录变得对用户友好一点:

前端增加一点点提示信息:

我们打开 Element-ui官网

在这里插入图片描述
这个不错,就这个辽。

	// 登录业务
    handleLogin() {
      this.$http({
        url: this.$http.adornUrl('/login'),
        method: 'post',
        data: this.loginForm
      }).then(({ data }) => {
        if (data && data.code === 200) {
          this.$message({
            message: '登录成功',
            type: 'success'
          });
          localStorage.setItem("login", "true")
          // 跳转页面
          this.$router.push('/')
        } else {
          this.$message.error(data.msg);
        }
      })
    }

在这里插入图片描述
我们给数据库增加一条记录:
在这里插入图片描述
正确输入密码后,成功跳转
在这里插入图片描述
如果用户已登录,就直接跳转到首页叭,这个小活就交给路由守卫叭:
注意我们前面在成功登录之后,给浏览器存储了一对key-value(login:true)

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [

  {
    path: '/login',
    name: 'login',
    component: () => import('../views/login.vue')
  },
  {
    path: "/",
    name: "layout",
    component: () => import('../layout/index.vue'),
    children: [
      {
        path: '/index',
        name: 'index',
        component: () => import('../views/index.vue')
      },
      {
        path: '/admin',
        name: 'admin',
        component: () => import('../views/employee/admin.vue')
      },
      {
        path: '/dept',
        name: 'dept',
        component: () => import('../views/employee/dept.vue')
      },
      {
        path: '/user',
        name: 'user',
        component: () => import('../views/employee/user.vue')
      },
      {
        path: '/userdept',
        name: 'userdept',
        component: () => import('../views/employee/userdept.vue')
      },
    ]
  },


]
const router = new VueRouter({
  mode: 'history', // 去掉url中的#
  routes: routes
})

// 注册一个全局前置守卫
router.beforeEach((to, from, next) => {
  // 登录及注册页面可以直接进入,而主页面需要分情况
  if (to.path == '/login') {
    if (localStorage.login === "true") {
      next('/');  // 用户已登录
    } else {
      next();
    }
  }
  else {
    if (from.path == "/login") // 从登录页面可以直接通过登录进入主页面
    {
      next();
    }
    else {
      // 从/进入,如果登录状态是true,则直接next进入主页面
      if (localStorage.login === "true") {
        next();
      }
      else { // 如果登录状态是false,那么跳转至登录页面,需要登录才能进入主页面
        next('/login');
      }
    }
  }
})
export default router
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。