Part1 第一部分(本页)
1 后台用户登入、退出、请求拦截
1.1用户登入
1.1.1 开始前逻辑梳理
- 前端点击登入后,发送请求到了后端,注意这个路径的命名规范:从视图解析器向下走,到对应的资源路径,改为资源.do(优点就是在拦截器配置拦截路径的时候很方便)
- controller都是对前端请做出的一个响应,返回的东西基本上都封装成对象返回回去(使用RetObj类和前端进行交换,信息交换,例如添加员工失败,就可以返回这个类的对象,将提示信息传到前台去弹窗显示),这个对象放到公共的包里去。
- 前端的数据直接封装成对象给controller,注意前端提交的名字和后端实体类的属性名称要一致!注意添加(@RequestBody)给参数Employee employee
- 然后 一般会想获取登入成功员工的信息,存到session中,注意方法
- 通过点击登入按钮后 F12 中url,method(post),数据形式(json)就可以编写后端controller
1.1.2 前端代码理解
- vue 主要代码
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
}
}
})
}
- 注意loginApi()和logoutApi()是单独在一个脚本文件中,主要功能就是进行ajax处理!
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',
})
}
1.1.3 后端代码
- 前端用json字符串格式向后台传请求参数,那么后台需要采用==@RequestBody==来处理请求的json格式数据,将json数据转换为java对象,否则springmvc就不能解析导致传空参的结果
- 如果加上@ResponseBody注解,就不会走视图解析器,不会返回页面,目前返回的json数据。如果不加,就走视图解析器,返回页面
@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);
}
}
1.2 用户退出
-
找到前端对应的样式的位置
-
找到对应vue代码部分
分析:这里removeItem是删除前端存储的内容;前端显示管理员是xxx,利用了local storageconst userInfo = window.localStorage.getItem('userInfo') if (userInfo) { this.userInfo = JSON.parse(userInfo) }
按F12去看,注意,userInfo是数据模型中添加的,登入成功后就storyge了,之后在index.html中又有进行调用,显示出管理员是谁(退出按钮的旁边)
- 找到logoutApi中对应的代码
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',
})
}
- 修改完上面的url,找到methode之后,现在可以写后端的controller了
(也可以用F12看,因为点击按钮后会发送一个请求,就可以看到这个请求的method,url…)
@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'
}
})
},
1.3 拦截请求
只拦截数据的请求,对于页面和静态资源,想看就看,应该放行。
- 代码实现:
先配置
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);
}
}
2 员工管理
2.1 index.html页面前端分析
- 登入完成后,进入backend/index.html之后,对该页面进行开发
- 注意,index.html仅仅只是左边的一小个页面(上图黑色的部分),点击左边的菜单导航栏,对应跳转到url。前端并且设置了默认url,一进入就可以直接跳转到对应的页面,可以修改成baidu,会在右侧显示出百度的官网,右侧的各个页面在另外的目录下:/page/…
- 点击index.html中的菜单,转到对应的html,注意menuHandle()方法
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()
},
2.2 添加员工
2.2.1 开始前思路整理
- 下面这个页面是点击“添加员工”按钮之后显示出来的,自己去看前端的逻辑,点击事件,跳转。
前端设置了一些不能未空的校验,以下就是需要输入的字段。
- 注意:员工的名字唯一,如果新建添加员工前端页面重复添加同样名字的表单,抛出异常的最底层是从数据库开始抛出的,这个问题要在下面得到解决。
- 基本流程
可以打开F12调试,输入对应的信息,查看url,method(post),提交数据的方式(json),这些东西就可以用来写后端的controller了!而不需要花很多时间研究前端的提交流程(也该理解)
2.2.2 前端跳转逻辑
- 新增员工按钮自己去看,也是一个点击事件发生跳转。下面这个是输入好员工信息后发生的点击事件跳转,现在去看对应的方法:submitForm()
- submitForm() 先进行校验…注意
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)
})
- addEmployee,点进去对应ajax请求
// 新增---添加员工
function addEmployee (params) {
return $axios({
url: '/employee',
method: 'post',
data: {
...params }
})
}
2.2.3 后端代码编写
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("成功添加员工!");
}
}
2.2.4 解决用户名等字段重复,导致数据库添加错误问题
- 全局异常处理中@ControllerAdvice(…)底层是代理,代理controller,将controller中的方法作为切面切到手。
- 使用到了Spring切面,将某些controller,出现某些异常的时候,作为切面切出来处理
- 主要思路还是使用RetObj返回类中设置信息!
- 报错如下,思路是写一个类(全局捕获比较好,减少代码重复),专门处理异常。像这个重复提交的异常,我们通过RetObj,把重复出来的字段数据截取出来,传给前端做提示。如果是其他类错误:提示信息就写:未知错误!
### 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("未知错误!");
}
}
}
2.2.4 总结
- 主要理解数据流转
2.3 员工信息分页查询
2.3.1 实现思路分析
第四点注意,Controller或者其他页面到底是如何实现 封装为json的形式再传送前端的?毕竟返回的是returnRetObj;
个人认为就是加了注解:@ResponseBody,在controller中复合注解中包含了
@RestController,所以在写其他类,如果涉及到返回数据给前端,并以json的形式,注意加上@ReponseBody
- 类似的思路,先去查看F12,页面提交的url和method,以及前端提交的参数:page,pageSize,这样就能够写后端代码了
2.3.2 前端代码分析
- 页面一打开,会自动提交查询请求,主要是通过vue的钩子函数created()(生命周期函数)实现
- 前端可能提交的三个数据:page(页面,默认传过来是1),pageSize(每页条数,默认传过来是10),name(再根据名字模糊查询,这个也是前端一个输入框那边输入的,用于查询的附加条件,旁边有个小按钮(放大镜),点击就执行查询)
- 模糊查询相关前端逻辑如下,注意clearable属性,点击之后执行handleQuery,清除默认提示。vue的监听组件:
@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类型数据了。具体的说: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;
...
- 进一步查看或修改getMemberList对应的url、method
function getMemberList (params) {
return $axios({
url: '/employee/backend/page/member/add.do/page',
method: 'get',
params
})
}
- 至于如何展示页面的,主要使用到了vue中的element-ui组件,定了包括每页默认条数等信息。还有一些细节比如后端返回的json数据本来对于账户状态只有1和0,但是在页面上显示了正常、禁用,这就是因为前端进行了处理。
<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>
2.3.3 后端使用MybatisPlus的分页插件进行分页
-
前端可能提交的三个数据: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;
}
}
- 后端controller,插入错误的话应该会进行全局异常捕获。
@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);
}
2.4 禁用、启用员工账号
2.4.1 实现思路分析
- 分析需求
普通用户的操作栏目:只有编辑功能,没有启用和禁用功能
- 开发思路,其实主要操作status就行了,禁用就把相关的id设置为0,所以前端给后端的参数就两个:id status
- 编辑的时候 也可以复用这个update方法!(思路就是controller接收什么消息无所谓,直接使用==@RequestBody封装到Employee对象中去==,这就做到了来什么数据都能封装到里面去,实现了复用),另外
根据 ID 选择修改boolean updateById(T entity);
这个如果entity是null的字段就不修改,只修改非null的字段(充分体现了是update)
- 出现的问题:禁用的时候根据id update语句中id多了两个零。去查看当时分页查询的数据(分页查询的结果就是现在操作的行),发现当时提交过来的id也是正常的。问题是出在js的精度上。
解决方案:
2.4.2 前端代码分析
- 实现普通用户在操作栏目 只有 “编辑” 功能,而管理员能够 “启用"、“禁用” 其他员工账号。前端通过localstorage拿到当前登入的用户名,之后
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)
})
})
},
2.4.3 后端代码分析
- 一开是单独写了禁用的后端代码,后来与edit 下一节的内容进行了整合,就是说禁用这里也算是edit,也是把一些字段(status)封装到Employee对象中,之后进行update.
- 注意的是,update或者updateById() 都是只更新不为null 的字段,比如这里的Employee 对象只有status属性是0或者1,其他均为null,但是更新过去的时候,其他字段不会变的!,只 update xxxx set(非null字段)
@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("成功禁止使用权限");
}
2.5 编辑员工信息
2.5.1 思路分析
- 步骤分析:关键在第五步回显,页面回显当前需要修改用户的信息(提交请求,根据id查出信息,显示到上面),在此基础上修改
2.5.2 前端分析
- 先跳到添加页面,并带着id,进行回显,模式是修改
<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)
}
},
- 由于修改也是跳转到新增页面进行操作,当vue对象创建完成之后,自动调用构造函数created()
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>
2.5.3 后端代码编写
- 点击编辑按钮后,后端查询信息进行回显
@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("未查询到该员工对应的信息");
}
- 回显信息到前端,用户进行修改后,点击保存按钮,这里的保存按钮注意是复用的!!! 在新增用户保存的时候,使用的是PostMapping(url),在编辑用户保存的时候,对应的是PutMapping(url),注意这个url可以一样,可以通过Put还是Post去区分不同的controller方法。当然也可以不一样,因为前端使用actionType 判断是否是add,还是edit,会分别调用不同的方法。
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("修改失败!");
}
}
3 分类管理业务开发(见part2)第二篇文章
http://t.csdn.cn/QO1iA