项目开发阶段-员工管理
后台登录退出功能开发
1、展示登录页面
展示完整login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>瑞吉外卖管理端</title>
<link rel="shortcut icon" href="../../favicon.ico">
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css">
<link rel="stylesheet" href="../../styles/login.css">
<link rel="stylesheet" href="../../styles/icon/iconfont.css" />
<style>
.body{
min-width: 1366px;
}
</style>
</head>
<body>
<div class="login" id="login-app">
<div class="login-box">
<img src="../../images/login/login-l.png" alt="">
<div class="login-form">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" >
<div class="login-form-title">
<img src="../../images/login/logo.png" style="width:139px;height:42px;" alt="" />
</div>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" maxlength="20"
prefix-icon="iconfont icon-user" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="iconfont icon-lock" maxlength="20"
@keyup.enter.native="handleLogin" />
</el-form-item>
<el-form-item style="width:100%;">
<el-button :loading="loading" class="login-btn" size="medium" type="primary" style="width:100%;"
@click.native.prevent="handleLogin">
<span v-if="!loading">登录</span>
<span v-else>登录中...</span>
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/request.js"></script>
<script src="../../js/validate.js"></script>
<script src="../../api/login.js"></script>
<script>
new Vue({
el: '#login-app',
data() {
return {
loginForm:{
username: 'admin',
password: '123456'
},
loading: false
}
},
computed: {
loginRules() {
const validateUsername = (rule, value, callback) => {
if (value.length < 1 ) {
callback(new Error('请输入用户名'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码必须在6位以上'))
} else {
callback()
}
}
return {
'username': [{ 'validator': validateUsername, 'trigger': 'blur' }],
'password': [{ 'validator': validatePassword, 'trigger': 'blur' }]
}
}
},
created() {
},
methods: {
async handleLogin() {
this.$refs.loginForm.validate(async (valid) => {
if (valid) {
this.loading = true
let res = await loginApi(this.loginForm)
if (String(res.code) === '1') { //1表示登录成功
localStorage.setItem('userInfo',JSON.stringify(res.data))
window.location.href= '/backend/index.html'
} else {
this.$message.error(res.msg)
this.loading = false
}
}
})
}
}
})
</script>
</body>
</html>
2、构建实体类Employee
/**
* 员工信息表
*/
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber; //身份证号
private Integer status; //状态码
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
3、创建三层架构
在目录下创建controller、service、mapper三个文件夹。
4、创建mapper层
在mapper文件夹下创建EmployeeMapper类,继承Basemapper接口。
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
5、创建service层
EmployeeService继承IService
public interface EmployeeService extends IService<Employee> {
}
EmployeeServiceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService {
}
6、创建controller实体类层
其中,controller中包含两个功能:登录账号、退出账号
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/***
* 员工登录功能
*/
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
//1.将页面提交的密码password进行md5加密
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
//2.根据页面提交的用户名username查询数据库,从数据库中查询employee表中的数据,根据getUsername字段,查询username为指定数据的值
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//构建查询条件,Employee::getUsername为实体类的字段名,employee.getUsername()为前端传入的用户名
queryWrapper.eq(Employee::getUsername,employee.getUsername());
//查询,如果在字段中查找到,则将其赋值给 emp 变量,否则返回null
Employee emp = employeeService.getOne(queryWrapper);
/**
Employee::getUsername
1.实例化一个Employee对象
Employee employee = new Employee()
2.调用对象employee的get方法,这里调用的是getUsername
employee.getUsername();
**/
//3.如果没有查询到则返回失败结果
if (emp==null){
return R.error("登录失败,请检查用户名是否存在!");
}
//4.密码比对,不一致则返回登录失败结果
if (!emp.getPassword().equals(password)){
return R.error("登录失败,请检查密码是否正确!");
}
//5.查看员工状态,1为正常,0为禁用
if (emp.getStatus()==0){
return R.error("登陆失败,当前账号状态不可用!");
}
//6.登陆成功,将员工id存入session
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
/***
* 员工退出功能
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
//清理session中保存的当前登录的员工id
request.getSession().removeAttribute("employee");
return R.success("已退出账号!");
}
}
7、创建通用返回实体类
创建common文件夹,在文件夹中创建实体类R
/**
* 通用返回结果,服务端响应的数据最终都会封装成此对象
* @param <T>
*/
@Data
public class R<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
8、展示后台主页面
展示完整index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>瑞吉外卖管理端</title>
<link rel="shortcut icon" href="favicon.ico">
<!-- 引入样式 -->
<link rel="stylesheet" href="plugins/element-ui/index.css" />
<link rel="stylesheet" href="styles/common.css" />
<link rel="stylesheet" href="styles/index.css" />
<link rel="stylesheet" href="styles/icon/iconfont.css" />
<style>
.body{
min-width: 1366px;
}
.app-main{
height: calc(100% - 64px);
}
.app-main .divTmp{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="app" id="app">
<div class="app-wrapper openSidebar clearfix">
<!-- sidebar -->
<div class="sidebar-container">
<div class="logo">
<!-- <img src="images/logo.png" width="122.5" alt="" /> -->
<img src="images/login/login-logo.png" alt="" style="width: 117px; height: 32px" />
</div>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="defAct"
:unique-opened="false"
:collapse-transition="false"
background-color="#343744"
text-color="#bfcbd9"
active-text-color="#f4f4f5"
>
<div v-for="item in menuList" :key="item.id">
<el-submenu :index="item.id" v-if="item.children && item.children.length>0">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span>{{item.name}}</span>
</template>
<el-menu-item
v-for="sub in item.children"
:index="sub.id"
:key="sub.id"
@click="menuHandle(sub,false)"
>
<i :class="iconfont" :class="sub.icon"></i>
<span slot="title">{{sub.name}}</span>
</el-menu-item
>
</el-submenu>
<el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">
<i class="iconfont" :class="item.icon"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</div>
</el-menu>
</el-scrollbar>
</div>
<div class="main-container">
<!-- <navbar /> -->
<div class="navbar">
<div class="head-lable">
<span v-if="goBackFlag" class="goBack" @click="goBack()"
><img src="images/icons/btn_back@2x.png" alt="" /> 返回</span
>
<span>{{headTitle}}</span>
</div>
<div class="right-menu">
<div class="avatar-wrapper">{{ userInfo.name }}</div>
<!-- <div class="logout" @click="logout">退出</div> -->
<img src="images/icons/btn_close@2x.png" class="outLogin" alt="退出" @click="logout" />
</div>
</div>
<div class="app-main" v-loading="loading">
<div class="divTmp" v-show="loading"></div>
<iframe
id="cIframe"
class="c_iframe"
name="cIframe"
:src="iframeUrl"
width="100%"
height="auto"
frameborder="0"
v-show="!loading"
></iframe>
</div>
</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="plugins/axios/axios.min.js"></script>
<script src="js/request.js"></script>
<script src="./api/login.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
defAct: '2',
menuActived: '2',
userInfo: {},
menuList: [
// {
// id: '1',
// name: '门店管理',
// children: [
{
id: '2',
name: '员工管理',
url: 'page/member/list.html',
icon: 'icon-member'
},
{
id: '3',
name: '分类管理',
url: 'page/category/list.html',
icon: 'icon-category'
},
{
id: '4',
name: '菜品管理',
url: 'page/food/list.html',
icon: 'icon-food'
},
{
id: '5',
name: '套餐管理',
url: 'page/combo/list.html',
icon: 'icon-combo'
},
{
id: '6',
name: '订单明细',
url: 'page/order/list.html',
icon: 'icon-order'
}
// ],
// },
],
iframeUrl: 'page/member/list.html',
headTitle: '员工管理',
goBackFlag: false,
loading: true,
timer: null
}
},
computed: {},
created() {
const userInfo = window.localStorage.getItem('userInfo')
if (userInfo) {
this.userInfo = JSON.parse(userInfo)
}
this.closeLoading()
},
beforeDestroy() {
this.timer = null
clearTimeout(this.timer)
},
mounted() {
window.menuHandle = this.menuHandle
},
methods: {
logout() {
logoutApi().then((res)=>{
if(res.code === 1){
localStorage.removeItem('userInfo')
window.location.href = '/backend/page/login/login.html'
}
})
},
goBack() {
// window.location.href = 'javascript:history.go(-1)'
const menu = this.menuList.find(item=>item.id===this.menuActived)
// this.goBackFlag = false
// this.headTitle = menu.name
this.menuHandle(menu,false)
},
menuHandle(item, goBackFlag) {
this.loading = true
this.menuActived = item.id
this.iframeUrl = item.url
this.headTitle = item.name
this.goBackFlag = goBackFlag
this.closeLoading()
},
closeLoading(){
this.timer = null
this.timer = setTimeout(()=>{
this.loading = false
},1000)
}
}
})
</script>
</body>
</html>
9、展示登录退出js
展示登录退出功能请求的javascript文件
function loginApi(data) {
return $axios({
'url': '/employee/login',
'method': 'post',
data
})
}
function logoutApi(){
return $axios({
'url': '/employee/logout',
'method': 'post',
})
}
10、过滤器拦截页面
创建filter文件夹,在文件夹中创建登录拦截器LoginCheckFilter
/***
* 检查用户是否已经完成登录
*/
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取本次请求的URL
String requestURL = request.getRequestURI();
log.info("拦截到请求:{}",request);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//2.判断本次请求是否需要处理
boolean check = check(urls, requestURL);
//3.如果不需要处理,则直接放行
if (check){
log.info("本次请求{}不需要处理:",requestURL);
filterChain.doFilter(request,response);
return ;
}
//4.判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,用户id为{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5.如果未登录则返回未登录结果处
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return ;
}
/***
* 路径匹配,检查本次是否需要放行
*/
public boolean check(String[] urls,String requestURL){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url,requestURL);
if (match){
return true;
}
}
return false;
}
}
创建完拦截器后,在"启动类"上添加注解@ServletComponentScan进行扫包
新增员工功能开发
1、展示添加页面
展示add.html完整页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
</head>
<body>
<div class="addBrand-container" id="member-add-app">
<div class="container">
<el-form
ref="ruleForm"
:model="ruleForm"
:rules="rules"
:inline="false"
label-width="180px"
class="demo-ruleForm"
>
<el-form-item label="账号:" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入账号" maxlength="20"/>
</el-form-item>
<el-form-item
label="员工姓名:"
prop="name"
>
<el-input
v-model="ruleForm.name"
placeholder="请输入员工姓名"
maxlength="20"
/>
</el-form-item>
<el-form-item
label="手机号:"
prop="phone"
>
<el-input
v-model="ruleForm.phone"
placeholder="请输入手机号"
maxlength="20"
/>
</el-form-item>
<el-form-item
label="性别:"
prop="sex"
>
<el-radio-group v-model="ruleForm.sex">
<el-radio label="男"></el-radio>
<el-radio label="女"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="身份证号:"
prop="idNumber"
>
<el-input
v-model="ruleForm.idNumber"
placeholder="请输入身份证号"
maxlength="20"
/>
</el-form-item>
<div class="subBox address">
<el-form-item>
<el-button @click="goBack()">
取消
</el-button>
<el-button
type="primary"
@click="submitForm('ruleForm', false)"
>
保存
</el-button>
<el-button
v-if="actionType == 'add'"
type="primary"
class="continue"
@click="submitForm('ruleForm', true)"
>
保存并继续添加
</el-button>
</el-form-item>
</div>
</el-form>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/request.js"></script>
<script src="../../api/member.js"></script>
<script src="../../js/validate.js"></script>
<script src="../../js/index.js"></script>
<script>
new Vue({
el: '#member-add-app',
data() {
return {
id: '',
actionType : '',
ruleForm : {
'name': '',
'phone': '',
'sex': '男',
'idNumber': '',
username: ''
}
}
},
computed: {
rules () {
return {
//账号
username: [
{
required: true, 'validator': checkUserName, trigger: 'blur'
}
],
//姓名
name: [{ required: true, 'validator': checkName, 'trigger': 'blur' }],
'phone': [{ 'required': true, 'validator': checkPhone, 'trigger': 'blur' }],
'idNumber': [{ 'required': true, 'validator': validID, 'trigger': 'blur' }]
}
}
},
created() {
this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
this.init()
}
},
mounted() {
},
methods: {
async init () {
queryEmployeeById(this.id).then(res => {
console.log(res)
if (String(res.code) === '1') {
console.log(res.data)
this.ruleForm = res.data
this.ruleForm.sex = res.data.sex === '0' ? '女' : '男'
// this.ruleForm.password = ''
} else {
this.$message.error(res.msg || '操作失败')
}
})
},
submitForm (formName, st) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.actionType === 'add') {
const params = {
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}
addEmployee(params).then(res => {
if (res.code === 1) {
this.$message.success('员工添加成功!')
if (!st) {
this.goBack()
} else {
this.ruleForm = {
username: '',
'name': '',
'phone': '',
// 'password': '',
// 'rePassword': '',/
'sex': '男',
'idNumber': ''
}
}
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else {
const params = {
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}
editEmployee(params).then(res => {
if (res.code === 1) {
this.$message.success('员工信息修改成功!')
this.goBack()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
}
} else {
console.log('error submit!!')
return false
}
})
},
goBack(){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/list.html',
name: '员工管理'
},false)
}
}
})
</script>
</body>
</html>
member.js
function getMemberList (params) {
return $axios({
url: '/employee/page',
method: 'get',
params
})
}
// 修改---启用禁用接口
function enableOrDisableEmployee (params) {
return $axios({
url: '/employee',
method: 'put',
data: { ...params }
})
}
// 新增---添加员工
function addEmployee (params) {
return $axios({
url: '/employee',
method: 'post',
data: { ...params }
})
}
// 修改---添加员工
function editEmployee (params) {
return $axios({
url: '/employee',
method: 'put',
data: { ...params }
})
}
// 修改页面反查详情接口
function queryEmployeeById (id) {
return $axios({
url: `/employee/${id}`,
method: 'get'
})
}
2、创建异常类
在common文件夹中创建全局异常类GlobalExceptionHandler
/***
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class,Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/***
* 异常处理方法
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if (ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = "账号" + split[2] + "已存在";
return R.error(msg);
}
return R.error("添加失败,出现了未知的异常");
}
}
3、添加controller方法
在employee中添加新增员工方法
/***
* 新增员工功能
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始化密码123456
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获取当前用户登录的id
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success("新增员工成功");
}
员工信息分页查询
1、创建配置类拦截器
/**
* 配置MP的分页插件
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
2、添加controller类方法
在employeecontroller中添加分页功能
/****
*员工信息分页查询
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
//构造分页构造器
Page pageinfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件
//模糊查询 like(是否使用该条件,字段,值)
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByD esc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageinfo,queryWrapper);
return R.success(pageinfo);
}
3、展示员工信息页面
展示完整list.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
<style>
#member-app .notAdmin::after{
border: 0 !important;
}
</style>
</head>
<body>
<div class="dashboard-container" id="member-app">
<div class="container">
<div class="tableBar">
<el-input
v-model="input"
placeholder="请输入员工姓名"
style="width: 250px"
clearable
@keyup.enter.native="handleQuery"
>
<i
slot="prefix"
class="el-input__icon el-icon-search"
style="cursor: pointer"
@click="handleQuery"
></i>
</el-input>
<el-button
type="primary"
@click="addMemberHandle('add')"
>
+ 添加员工
</el-button>
</div>
<el-table
:data="tableData"
stripe
class="tableBox"
>
<el-table-column
prop="name"
label="员工姓名"
></el-table-column>
<el-table-column
prop="username"
label="账号"
></el-table-column>
<el-table-column
prop="phone"
label="手机号"
></el-table-column>
<el-table-column label="账号状态">
<template slot-scope="scope">
{{ String(scope.row.status) === '0' ? '已禁用' : '正常' }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="160"
align="center"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)"
:class="{notAdmin:user !== 'admin'}"
>
编辑
</el-button>
<el-button
type="text"
size="small"
class="delBut non"
@click="statusHandle(scope.row)"
v-if="user === 'admin'"
>
{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pageList"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="counts"
:current-page.sync="page"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/request.js"></script>
<script src="../../api/member.js"></script>
<script>
new Vue({
el: '#member-app',
data() {
return {
input: '',
counts: 0,
page: 1,
pageSize: 10,
tableData : [],
id : '',
status : '',
}
},
computed: {},
created() {
this.init()
this.user = JSON.parse(localStorage.getItem('userInfo')).username
},
mounted() {
},
methods: {
async init () {
const params = {
page: this.page,
pageSize: this.pageSize,
name: this.input ? this.input : undefined
}
await getMemberList(params).then(res => {
if (String(res.code) === '1') {
this.tableData = res.data.records || []
this.counts = res.data.total
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
},
handleQuery() {
this.page = 1;
this.init();
},
// 添加
addMemberHandle (st) {
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
} else {
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html?id='+st,
name: '修改员工'
},true)
}
},
//状态修改
statusHandle (row) {
this.id = row.id
this.status = row.status
this.$confirm('确认调整该账号的状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {
console.log('enableOrDisableEmployee',res)
if (String(res.code) === '1') {
this.$message.success('账号状态更改成功!')
this.handleQuery()
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
handleSizeChange (val) {
this.pageSize = val
this.init()
},
handleCurrentChange (val) {
this.page = val
this.init()
}
}
})
</script>
</body>
</html>
启用/禁用员工账号
1、前端展示页面
展示list.html部分页面,“员工信息分页查询”中包含完整员工信息页面
<el-table-column
label="操作"
width="160"
align="center"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)"
:class="{notAdmin:user !== 'admin'}"
>
编辑
</el-button>
<el-button
type="text"
size="small"
class="delBut non"
@click="statusHandle(scope.row)"
v-if="user === 'admin'"
>
{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
展示list.html页面中的vue代码,在钩子函数中,获取员工姓名
created() {
const userInfo = window.localStorage.getItem('userInfo')
if (userInfo) {
this.userInfo = JSON.parse(userInfo)
}
this.closeLoading()
},
展示list.html页面中的vue代码,在方法中,修改员工状态。当要修改状态时,将状态修改为当前相反的状态。
//状态修改
statusHandle (row) {
this.id = row.id
this.status = row.status
this.$confirm('确认调整该账号的状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {
console.log('enableOrDisableEmployee',res)
if (String(res.code) === '1') {
this.$message.success('账号状态更改成功!')
this.handleQuery()
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
展示ajax代码
// 修改---启用禁用接口
function enableOrDisableEmployee (params) {
return $axios({
url: '/employee',
method: 'put',
data: { ...params }
})
}
2、添加controller类方法
/**
* 根据id修改员工信息
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
Long employeeId = (Long)request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(employeeId);
employeeService.updateById(employee);
return R.success("员工信息修改成功");
}
3、创建对象映射器
在使用json将前端数据传递到后端页面的时候,整型值的精度会丢失,所以需要将值转换成字符串类型。所以需要将数值进行序列化与反序列化。
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
编辑员工信息功能开发
获取当前页面中的url,在url中得到主页转递过来的id值,并将id作为参数,请求后端的数据
1.前端页面展示
展示前端页面list.html和add.html
list.html
<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)"
:class="{notAdmin:user !== 'admin'}"
>
编辑
</el-button>
// 添加
addMemberHandle (st) {
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
} else {
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html?id='+st,
name: '修改员工'
},true)
}
},
add.html
created() {
this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
this.init()
}
},
2.添加controller方法
/**
* 根据id查询员工信息
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询用户信息");
Employee employee = employeeService.getById(id);
if (employee!=null){
return R.success(employee);
}
return R.error("员工信息查询失败");
}
插入更新公共字段自动填充
mybatis plus 公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码
1.添加注解
在实体类Employee类的属性上,加入@TableField注解,指定自动填充的策略
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
2.实现接口类
按照框架要求编写元数据对象处理器MyMetaObjecthandler公共类,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]....");
log.info(metaObject.toString());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]....");
log.info(metaObject.toString());
}
}
优化employeecontroller类,将save方法代码注释
/*
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获取当前用户登录的id
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
*/
//此功能放到公共类中
/*
Long employeeId = (Long)request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(employeeId);
*/
在实现类中补全代码
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]....");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",new Long(1));
metaObject.setValue("updateUser",new Long(1));
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]....");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
3.线程
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
- LoginCheckFilter的doFilter方法
- EmployeeController的update
- MyMetaObjectHandler的updateFill方法
- 可以在以上三个方法中,添加测试
long id = Thread.currentThread().getId();
log.info("线程{}",id);
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用Thread变量时,ThreadLocal为每个使用该变量线程提供的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。ThreadLocal为每个线程提供一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
我们可以在LoginCheckFilter方法中获取当前登录用户id,并调用ThreadLocald的set方法来设置当前线程的线程局部变量的值(用户id),然后再MyMetaObjectHandler的updateFill方法中调用ThreadLocal
实现步骤:
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
ps:ThreadLocal只能存储一个数值,即一个线程一个local
4.创建封装工具类
在公共类中新建BaseContext工具类
/**
* 基于Thread封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setThreadLocal(Long id){
threadLocal.set(id);
}
public static Long getCuttentId(){
return threadLocal.get();
}
}
5.修改登录检查过滤器
在LoginCkeckFilter里添加代码
//4.判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,用户id为{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setThreadLocal(empId);
filterChain.doFilter(request,response);
return;
}
6.修改自定义元数据对象处理器
在MyMetaObjecthandler类自动填充字段的插入更新字段功能中,修改其中的代码
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCuttentId());
metaObject.setValue("updateUser",BaseContext.getCuttentId());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCuttentId());