Part1 第一部分(本页)
Part 第2部分链接
methods: { //方法:
async handleLogin() { //点击登入按钮的时候就会调用(去找前面的点击,对应的handleLogin()),async异步操作,关键词用在函数上
this.$refs.loginForm.validate(async (valid) => {//进行校验,主要校验表单是不是为空
if (valid) {//如果校验通过
this.loading = true //(显示登入中...)前面有span v-if标签对应着
let res = await loginApi(this.loginForm) //验证通过后,通过ajax发送请求,请求的路径点击进去修改!res是controller响应回来的结果;await关键子用于async函数当中(await可以得到异步的结果)
//loginForm 里面放的就是用户名密码。
if (String(res.code) === '1') {
比如说员工emp的实体,放在这个属性上,前端页面能转为json,保存到浏览器中
localStorage.setItem('userInfo',JSON.stringify(res.data))
window.location.href= '/backend/index.html'
} else {
//弹窗res的msg属性,之前这里一直报错F12:Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'type')
//原因是你自己设置的属性是message不是msg,这里把属性统一改成了msg
/*
vue语法:提示窗口,有点像alert
this.$message({
message:'保存成功了,我是message',
type: 'success'
})
*/
this.$message.error(res.msg)
this.loading = false
}
}
})
}
function loginApi(data) {
return $axios({
'url': '/employee/backend/page/login/login.do',
'method': 'post',
data
})
}
function logoutApi(){
return $axios({
'url': '/employee/backend/page/login/logout.do',
'method': 'post',
})
}
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工登录
* @param request
* @param employee
* @return
*/
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){
//是用json字符串格式向后台传请求参数,那么后台需要采用@RequestBody来处理请求的json格式数据,将json数据转换为java对象,否则springmvc就不能解析导致传空参的结果
//如果加上@ResponseBody注解,就不会走视图解析器,不会返回页面,目前返回的json数据。如果不加,就走视图解析器,返回页面
//1、将页面提交的密码password进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
//2、根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
//3、如果没有查询到则返回登录失败结果
if(emp == null){
return R.error("登录失败");
}
//4、密码比对,如果不一致则返回登录失败结果
if(!emp.getPassword().equals(password)){
return R.error("登录失败");
}
//5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if(emp.getStatus() == 0){
return R.error("账号已禁用");
}
//6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
}
找到对应vue代码部分
分析:这里removeItem是删除前端存储的内容;前端显示管理员是xxx,利用了local storage
const userInfo = window.localStorage.getItem('userInfo')
if (userInfo) {
this.userInfo = JSON.parse(userInfo)
}
按F12去看,注意,userInfo是数据模型中添加的,登入成功后就storyge了,之后在index.html中又有进行调用,显示出管理员是谁(退出按钮的旁边)
function loginApi(data) {
return $axios({
'url': '/employee/backend/page/login/login.do',
'method': 'post',
data
})
}
function logoutApi(){
return $axios({
'url': '/employee/backend/page/login/logout.do',
'method': 'post',
})
}
@PostMapping("/employee/backend/page/login/logout.do")
public RetObj<String> logout(HttpServletRequest request){
//清理Session中保存的当前登录员工的id
request.getSession().removeAttribute(Contants.SESSION_USERID);
//登入成功了,把对应的对象传给前端;这里的obj就是一个String,前端会显示一个弹窗:退出成功!
return RetObj.success("退出成功!");//retObj.data = obj;
}
RetObj.success("退出成功!")
controller返回这个对象之后,code=1,在下面进行了验证,验证完毕后删除浏览器的数据userInfo,并完成跳转!
注:一个找了很久的bug:res.code === 1,还有很多其他地方,前端是写成数字进行校验的,而你的RetObj类中的属性的字符串的“0”,“1”,所以一直无法通过,细节注意起来。
methods: {
logout() {
logoutApi().then((res)=>{
if(res.code === 1){
localStorage.removeItem('userInfo')
window.location.href = '/backend/page/login/login.html'
}
})
},
package cn.edu.uestc.ruijitakeout.common.config;
import cn.edu.uestc.ruijitakeout.backend.interceptor.LoginInterceptor;
import cn.edu.uestc.ruijitakeout.common.JacksonObjectMapper.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("拓展消息转换器成功加载");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//重写方法,添加拦截器方法
registry.addInterceptor(loginInterceptor())
//拦截哪些路径
.addPathPatterns("/**")
//不拦截路径
.excludePathPatterns("/employee/backend/page/login/login.do",
"/backend/**",
"/employee/backend/page/login/logout.do",
"/front/**",
"/error"
);
}
@Bean
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
}
拦截器:
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
log.info("当前路径:{}", uri);
/**
* HandlerMethod=>Controller中标注@RequestMapping的方法
* 需要配置静态资源不拦截时,添加这块逻辑 => 前后端分离项目
*
*/
// 是我们的conrtoller中的方法,如果不是的话,放行,给加载静态资源
/* if (!(handler instanceof HandlerMethod)) {
log.info("是静态资源或非controller中的方法,放行");
return true;
}*/
//通过session判断是否登入
if (request.getSession().getAttribute(Contants.SESSION_USERID) == null) {
//这里应该跳转到登入页面,,如何做?
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
log.info("用户未登入,通过输出流方式向客户端页面响应数据,打回登入页面");
response.getWriter().write(JSON.toJSONString(RetObj.error("NOTLOGIN")));//与前端request.js中的代码呼应
return false;
} else {
log.info("用户已经登入,id={}", request.getSession().getAttribute(Contants.SESSION_USERID));
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
menuHandle(item, goBackFlag) {
this.loading = true
this.menuActived = item.id
//这里的url就是前面数据部分里写的url,进行了跳转;
//iframeUrl在前面定义了,会跳到除了导航栏右边的页面,有iframe定义
this.iframeUrl = item.url
this.headTitle = item.name
this.goBackFlag = goBackFlag
this.closeLoading()
},
addEmployee(params).then(res => {..
这个请求ajax方法的理解! 下面代码中主要对响应回来的信息进行处理。submitForm (formName, st) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.actionType === 'add') {
const params = { //把表单数据拿过来封装成json对象,在下面的函数中传给后端
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}
//这个方法和前面的登入、退出类似,封装到了一个js文件中,调用ajax方法,url...与F12对应上了
//页面返回信息后执行回调函数then()里面的逻辑
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)
})
// 新增---添加员工
function addEmployee (params) {
return $axios({
url: '/employee',
method: 'post',
data: { ...params }
})
}
package cn.edu.uestc.ruijitakeout.backend.controller.workbench;
@Slf4j
@RestController
public class CRUDEmployeeController {
@Resource
EmployeeService employeeService;
@PostMapping("/employee/backend/page/member/add.do")
public RetObj addEmp(@RequestBody Employee employee, HttpServletRequest request){
log.info("成功进入到addEmp的controller");
//需要为employee设置一些前端没提交的字段数据create_time、update_time、create_user、update_user
//学会使用LocalDateTime
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置初始化密码
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//create_user,从session中拿
employee.setCreateUser((Long) request.getSession().getAttribute(Contants.SESSION_USERID));
employee.setUpdateUser((Long) request.getSession().getAttribute(Contants.SESSION_USERID));
boolean save = employeeService.save(employee);
return RetObj.success("成功添加员工!");
}
}
### Cause: java.sql.SQLIntegrityConstraintViolationException:
Duplicate entry 'admin2' for key 'employee.idx_username';
Duplicate entry 'admin2' for key 'employee.idx_username';
nested exception is java.sql.SQLIntegrityConstraintViolationException:
Duplicate entry 'admin2' for key 'employee.idx_username'] with root cause
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody //因为一会儿的写的方法,需要返回一个json数据,所以需要写这个注解(否则返回的就是那个对象!!)
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public RetObj<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage());
if (exception.getMessage().contains("Duplicate entry")){
String[] split = exception.getMessage().split(" ");
return RetObj.error(split[2] + "已存在!");
}else {
return RetObj.error("未知错误!");
}
}
}
第四点注意,Controller或者其他页面到底是如何实现 封装为json的形式再传送前端的?毕竟返回的是returnRetObj;
个人认为就是加了注解:@ResponseBody,在controller中复合注解中包含了
@RestController,所以在写其他类,如果涉及到返回数据给前端,并以json的形式,注意加上@ReponseBody
@keyup.enter.native
<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>
handlQquery()
handleQuery() {
this.page = 1;//默认值
this.init();
},
init()
包括点击分页条下面的数字,对应跳到第几页也是类似的结构
handleQuery() {
this.page = 1;//默认值
this.init();
},
分页查询,注意思考前端需要哪些数据,比如Page对象中的recode等数据,那后面写后端代码的时候返回值RetObj
就要注意传递的Page类型数据了。具体的说:Page pageInfo = new Page(page,pageSize);
后端在执行完条件查询:empService.page(pageInfo,lambdaQueryWrapper)
之后,直接将查询到的结果封装到pageInfo对象中了。后端解析这个对象转成的json,就能够拿到里面的recods等属性值!recodes就是查询到的每个员工的信息,前端代码赋值给了tableData。counts是下面“共xxx条数据显示的”
Page类
public class Page<T> implements IPage<T> {
private static final long serialVersionUID = 8545996863226528798L;
protected List<T> records;
protected long total;
protected long size;
protected long current;
protected List<OrderItem> orders;
protected boolean optimizeCountSql;
protected boolean searchCount;
protected boolean optimizeJoinOfCountSql;
protected String countId;
protected Long maxLimit;
...
function getMemberList (params) {
return $axios({
url: '/employee/backend/page/member/add.do/page',
method: 'get',
params
})
}
<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>
前端可能提交的三个数据:page(页面,默认传过来是1,前端可以选择查看第几页,这个消息也要传给后端),pageSize(每页条数,默认传过来是10),name(再根据名字模糊查询,这个也是前端一个输入框那边输入的,用于查询的附加条件)。着意味着后端需要写分页构造器和条件构造器。总的来说,对于后端其实很简单,只需要传递给前端一个Page的对象即可,封装为了json对象,records对应就是每个员工的信息,前端会进行循环展示。
配置MP的分页插件
/**
* MP分页插件的配置
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
@GetMapping("/employee/backend/page/member/page.do")
public RetObj<Page<Employee>> PaginationQuery(int page, int pageSize, String name){
log.info("前端数据:page={},pageSize={},name={}",page,pageSize,name);
Page<Employee> pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(name),Employee::getName,name)
.orderByDesc(Employee::getUpdateTime);
employeeService.page(pageInfo,lambdaQueryWrapper);
return RetObj.success(pageInfo);
}
根据 ID 选择修改boolean updateById(T entity);
这个如果entity是null的字段就不修改,只修改非null的字段(充分体现了是update)v-if="user === 'admin'"
判断是否显示按钮{{ scope.row.status == '1' ? '禁用' : '启用' }}
<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>
statusHandle(scope.row)
(scope.row)就是当条数据对应的json对象。之后enableOrDisableEmployee()方法(该方法封装到了js文件中)发送ajax请求。到这里,服务器肯定要写controller去接收处理这个请求。 //状态修改
statusHandle (row) {
this.id = row.id
this.status = row.status
//$confirm是elementUI提供的方法,用于弹出窗口的
this.$confirm('确认调整该账号的状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => { //confirmButtonText 这个对应的值 ‘确定点击后’,就会指向then后面这个回调函数
//enableOrDisableEmployee 发请求,三目运算符,当前是0,就传1给后端
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)
})
})
},
@PutMapping("/employee/backend/page/member/forbidden.do")
public RetObj<String> forbiddenUser(@RequestBody Employee employee, HttpServletRequest request){
log.info("成功进入controller方法,接收到:id={},status={}",employee.getId(),employee.getStatus());
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
//UPDATE employee SET status=?,update_time=?,update_user=? WHERE (id = ?)
lambdaUpdateWrapper.set(Employee::getStatus,employee.getStatus())
//.set(Employee::getUpdateTime,LocalDateTime.now())
//.set(Employee::getUpdateUser,request.getSession().getAttribute(Contants.SESSION_USERID))
.eq(Employee::getId,employee.getId());
boolean res = employeeService.update(lambdaUpdateWrapper);
log.info("是否成功:{}",res);
return RetObj.success("成功禁止使用权限");
}
<template slot-scope="scope">
<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)" /><!--把id传过去,按照id查询出默认信息进行回显-->
:class="{notAdmin:user !== 'admin'}"
>
// 添加
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)
}
},
created() { //钩子函数,vue对象创建后,该函数自动执行
this.id = requestUrlParam('id')
//三目运算符,如果id有值,this.id= true,则进入edit页面!
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {//如果id有值
this.init()
}
},
其中requestUrlParam方法
//获取url地址上面的参数
function requestUrlParam(argname){
var url = location.href//获取完整的请求url路径
var arrStr = url.substring(url.indexOf("?")+1).split("&")//解析字符串,动态取出id值,split是防止有多个参数
for(var i =0;i<arrStr.length;i++)//遍历数组
{
var loc = arrStr[i].indexOf(argname+"=")
if(loc!=-1){
//id=123456...把数字取出来
return arrStr[i].replace(argname+"=","").replace("?","")
}
}
return ""如果前面的return没执行,那就返回空,什么也不填充
}
methods: {
async init () {
queryEmployeeById(this.id).then(res => {//回调函数
console.log(res)
if (String(res.code) === '1') {
console.log(res.data)
this.ruleForm = res.data//回显,这里就要注意,res的data属性要赋一个Emp类型的值
this.ruleForm.sex = res.data.sex === '0' ? '女' : '男' //数据库那边是0,1
// this.ruleForm.password = ''
} else {
this.$message.error(res.msg || '操作失败')
}
})
},
v-if="actionType == 'add'"
type="primary"
class="continue"
@click="submitForm('ruleForm', true)"
>
保存并继续添加
</el-button>
@GetMapping("/employee/backend/page/member/getEmp.do/{id}")
public RetObj<Employee> queryEmpById(@PathVariable Long id){
Employee emp = employeeService.getById(id);
if (emp!=null){
return RetObj.success(emp);
}
return RetObj.error("未查询到该员工对应的信息");
}
submitForm (formName, st) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.actionType === 'add') {
const params = { //把表单数据拿过来封装成json对象,在下面的函数中传给后端
...this.ruleForm,
sex: this.ruleForm.sex === '女' ? '0' : '1'
}....
....
else{
....
}
这里采用不用的url进行了区分,注意,既然是修改信息,就要想到不仅仅是修改前端的那些基本个人信息,还需要设置updatetime,updateuser,应该思考全面!
@PutMapping("/employee/backend/page/member/edit.do")
public RetObj<String> updateById(@RequestBody Employee emp, HttpServletRequest request){
emp.setUpdateTime(LocalDateTime.now());
Long updaterId = (Long) request.getSession().getAttribute(Contants.SESSION_USERID);
emp.setUpdateUser(updaterId);
log.info("修改后的emp:{}",emp.toString());
boolean b = employeeService.updateById(emp);
if (b){
return RetObj.success("成功修改信息!");
}else {
return RetObj.error("修改失败!");
}
}
http://t.csdn.cn/QO1iA