承接Springboot+Vue前后端分离项目-管理系统-上
在上篇我们完成了登录页的前端布局以及后端接口
我们在这里完成管理页的全部任务
一.用户管理页面
用户管理布局
搜索栏
我们使用element标签来创建页面 组件 | Element
使用什么样式就直接在里面找就可以了,不需要死记
<template>
<div>
<!-- 搜索栏 -->
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<el-button type="primary" round>查询</el-button>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
为了美观,我们使用card样式将其套起来
但是此时卡片的边界与左栏上栏默认的贴在了一起,依旧不美观,我们可以直接修改一下卡片外边距,但是并不推荐这种做法,因为之后还有别的组件需要修改边距,我们这里修改整个区域的css
整个区域指的这里
我们打开F12查看此区域叫什么名字
我们在根组件App.vue中设置style,定义一个全局边距
<style>
.app-main{
/* 边距 */
padding: 10px;
}
</style>
再来修改一下框的宽度、右边距以及查询按钮的图案,这里仅修改这个地方的框大小就行了,没必要设置全局属性
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-card>
</div>
</template>
<script>
export default {
}
</script>
<style>
#search .el-input{
width: 200px;
margin-right: 10px;
}
</style>
十分甚至九分的美观啊(喜
若是想在这卡片末尾添加一个小按钮,我们可以使用Layout布局页面
这部分的完整代码如下
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-row>
<el-col :span="20">
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<!-- icon是图标,可以修改为自己的 -->
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<!-- align是对齐,right就是右对齐 -->
<el-col :span="4" align="right">
<el-button type="primary" icon="el-icon-plus" circle></el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
}
</script>
<style>
#search .el-input{
width: 200px;
margin-right: 10px;
}
</style>
结果栏
我们同样将结果列表放入card组件,就是为了美观,不放也行
可以依照上面那样在根组件App.vue中设置全局底边距,这样别的该种类组件也能用,如果只是想在本组件生效那就写在本类文件的style中
先查看该组件css名称
.el-card{
/* 底部边距 */
margin-bottom: 10px ;
}
我们来使用表格替换掉xxx
直接使用的话会报错,因为数据绑定不正常
我们先在script中创建空数据,因为data中返回的searchModel是对象类型,所以调用的时候要使用“对象.属性”的形式,而userList是数组,直接使用即可
<script>
export default {
data(){
return{
searchModel: {},
userList: []
}
}
};
</script>
修改我们之前的代码绑定的数据
添加/修改我们需要的字段,对应数据库字段即可
添加分页组件,在element中查找分页组件组件 | Element
<el-pagination
方法1
@size-change="handleSizeChange"
方法2
@current-change="handleCurrentChange"
参数1:当前页
:current-page="currentPage4"
参数2:展示数
:page-sizes="[100, 200, 300, 400]"
参数3:页数,不要写死,用变量代替
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
参数4:总记录数:这个是由后端传来的数据,不能写常量
:total="400">
</el-pagination>
这里面绑定了两个方法4个参数,所以不能直接使用,要先在script中定义出来,不然会报错
<script>
export default {
data(){
return{
total:0,
searchModel: {
pageNo:1,
pageSize:10
},
userList: []
}
},
methods:{
handleSizeChange(){
},
handleCurrentChange(){
}
}
};
</script>
更改一下分页展示的语言
最终效果
这部分的全部代码
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-row>
<el-col :span="20">
<el-input v-model="searchModel.username" placeholder="用户名"></el-input>
<el-input v-model="searchModel.phone" placeholder="电话"></el-input>
<!-- icon是图标,可以修改为自己的 -->
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<!-- align是对齐,right就是右对齐 -->
<el-col :span="4" align="right">
<el-button type="primary" icon="el-icon-plus" circle></el-button>
</el-col>
</el-row>
</el-card>
<!-- 结果栏 -->
<el-card>
<el-table :data="userList" stripe style="width: 100%">
<el-table-column type="index" label="#" width="180">
</el-table-column>
<el-table-column prop="id" label="ID" width="180">
</el-table-column>
<el-table-column prop="username" label="用户名" width="180">
</el-table-column>
<el-table-column prop="phone" label="电话" width="180">
</el-table-column>
<el-table-column prop="mail" label="电子邮箱">
</el-table-column>
<el-table-column label="操作" width="180">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="searchModel.pageNo"
:page-sizes="[5, 10, 15, 20]"
:page-size="searchModel.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data(){
return{
total:0,
searchModel: {
pageNo:1,
pageSize:10
},
userList: []
}
},
methods:{
handleSizeChange(){
},
handleCurrentChange(){
}
}
};
</script>
<style>
#search .el-input {
width: 200px;
margin-right: 10px;
}
</style>
2.用户管理接口
我们首先要实现分页查询,具体可以查看mybatis的官网插件主体 | MyBatis-Plus (baomidou.com)
创建分页插件拦截器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
Controller层
@GetMapping("/list")
// required表示查询时Params不必带上该参数
public Result<Map<String,Object>> getUserList(@RequestParam(value = "username",required = false) String username,
@RequestParam(value = "phone",required = false) String phone,
@RequestParam("pageNo") Long pageNo,
@RequestParam("pageSize") Long pageSize){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//判断长度是否大于零,其实判断你username是不是非空
wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);
Page<User> page = new Page<>(pageNo, pageSize);
userService.page(page,wrapper);
Map<String,Object> data = new HashMap<>();
data.put("total",page.getTotal());
data.put("rows",page.getRecords());
return Result.success(data);
}
测试
3.接口对接
我们若是想要对接,首先在vue中需要定义api,前面做登录接口时,模板已经帮我们定义好了登录接口部分,现在我们需要自己来实现
一个模块一个js文件,我们在api文件夹下创建userManage.js
import request from '@/utils/request'
export default {
// searchModel是前端传来的数据
getUserList(searchModel){
return request({
url: '/user/list',
method: 'get',
// post上传的是json格式,get上传的params
params:{
pageNo: searchModel.pageNo,
pageSize: searchModel.pageSize,
username: searchModel.username,
phone: searchModel.phone
}
});
},
// 有别的方法在这里写
}
在user.vue中引入
我们为查询按钮绑定一下事件
查看效果
此时我们看到并没有1页展示5条数据,是因为没有定义这两个方法,当变量发生改变时调用方法
handleSizeChange(pageSize){
this.searchModel.pageSize = pageSize;
this.getUserList();
},
handleCurrentChange(pageNo){
this.searchModel.pageNo = pageNo;
this.getUserList();
},
成功实现
4.密码加密
我们使用MD5算法进行密码加密,md5有不可逆转的特点
1.引入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
2.在启动类中配置
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
3.设计新增用户的接口
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping
//前端传来的user对象是一个json格式
public Result<?> addUser(@RequestBody User user){
user.setPassword(passwordEncoder.encode(user.getPassword()));
//这个方法已经进行了加盐处理,相同的初始数据生成的值不一样
userService.save(user);
return Result.success("新增用户成功");
}
}
4.设计登录用户的接口
我们先根据用户名查询判断,再来进行密码匹配
我们在Service层重新设计登录逻辑
@Override
public Map<String, Object> login(User user) {
//根据用户名与密码查询结果
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
// wrapper.eq(User::getPassword,user.getPassword()); 引入了MD5加密,就不能直接比对密码了
User loginUser = this.baseMapper.selectOne(wrapper);
//若结果不为空,并且密码和传入密码匹配则生成token,并将用户信息存入redis
if (loginUser!=null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())){
//暂时用UUID,终极方案应为jwt
String key = "user:" + UUID.randomUUID();
//存入redis
loginUser.setPassword(null); //密码不要存进去
//默认这是永久有效的,要设置timeout时间
redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);
//返回数据
Map<String,Object> data = new HashMap<>();
data.put("token",key);
return data;
}
return null;
}
查看效果(记得把数据库中的admin的密码改成刚才生成的md5值形式)
可以成功登录
5.用户修改
后端端口
@PutMapping
//前端传来的user对象是一个json格式
public Result<?> updateUser(@RequestBody User user){
user.setPassword(null);
userService.updateById(user);
return Result.success("修改用户成功");
}
//根据id查询用户信息,方便修改的时候在框中展现信息以方便对照修改
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id") Integer id){
User user = userService.getById(id);
return Result.success(user);
}
前端接口
我们先用插槽新增两个按钮
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button @click="openEditUI(scope.row.id)" type="primary" icon="el-icon-edit" circle size="mini"></el-button>
<el-button type="danger" icon="el-icon-delete" circle size="mini"></el-button>
</template>
</el-table-column>
api接口
addUser(user){
return request({
url: '/user',
method: 'post',
data: user
});
},
updatedUser(user){
return request({
url: '/user',
method: 'put',
data: user
})
},
//这个api是为了判断是新增还是修改
saveUser (user){
if(user.id == null && user.id == undefined){
//新增
return this.addUser(user);
}
//修改
return this.updatedUser(user);
},
getUserById(id){
return request({
url:`/user/${id}`,
method: 'get'
})
}
这里我们为了优化代码,也为了减少代码的繁琐我们的新增窗口与修改窗口使用的是同一个,但是我们要设置条件来区分展示
我们让密码栏不要展示出来,逻辑上密码不应该在这里修改,当新增时就展示,修改就隐藏
<el-form-item v-if="userForm.id == null || userForm.id == undefined"
label="登录密码"
prop="password"
:label-width="formLabelWidth"
>
我们在js代码中的方法中也设置条件,当id是null的时候是新增,当id不为null的时候说明当前有对象存在,是修改
methods: {
openEditUI(id){
if(id == null){
this.title = '新增用户';
this.dialogFormVisible = true;
}else{
this.title = '修改用户';
//根据用户查询id
userApi.getUserById(id).then(response =>{
this.userForm = response.data;
});
}
this.dialogFormVisible = true;
},
},
查看效果
5.用户删除
我们不进行物理删除,我们使用逻辑删除,就是数据库表字段中有一个delete字段,删除时是把0改为1
配置全局配置
在application.yml中
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted #全局逻辑删除的实体字段名
logic-delete-value: 1 #逻辑已删除 默认为1
logic-not-delete-value: 0 #逻辑未删除 默认为0
前端
deleteUserById(id){
return request({
url: `/user/${id}`,
method: 'delete'
})
}
deleteUser(user){
this.$confirm(`您确认删除用户${user.username}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userApi.deleteUserById(user.id).then(response => {
this.$message({
type: 'success',
message: response.message
});
this.getUserList();
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
我们删除用户a
发现delete操作数据库是update操作