从0到1构建可视化大屏-员工管理
前言
: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
- 点赞
- 收藏
- 关注作者
评论(0)