码字不易,喜欢就点个关注❤,持续更新技术内容。
1 前后端联调
现在将后台管理页面(前端)和后台服务服务端进行集成,之前都是通过MockJS拦截前端请求随机生成数据来模拟服务器的数据响应的。当服务端接口开发完成后,前端就可以通过连接服务端程序指定的端口,然后向指定功能接口发送请求。
如当我们要进入后台管理页面中,需要通过导航守卫进行登录权限验证,如token认证,用户信息请求,路由跳转等,在讲解Vue技术那篇文章中已经做了更详细的描述。
如下登录过程,向服务端发送POST请求,此时请求的URL中没有IP地址和端口号,我们需要在.env.development配置文件中添加URL前缀。
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data: data
})
}
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://localhost:xxxx'
请求会服务器交给到以下控制器进行处理,然后返回包含token的结果集:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping("/login")
//前端一般传递的数据是json格式,必须使用对象接收,同时需要添加@RequestBody注解
public Result login(@RequestBody User user){
//生成token
String token = JwtUtils.generateToken(user.getUname());
//返回包含token的结果集
return Result.ok().data("token", token);
}
}
接下来我们进行登录操作的讲解:
首先我们先启动前端项目:
进入登录组件页面,然后点击登录,导航守卫进行拦截走一遍权限认证,如没有token进入登录页登录,输入后点击登录会发送一次请求,传递账号和密码到服务端生成token后放在响应数据response在action中存储token,最后才进入首页。(实际上进入首页前还发送一次获取用户信息的请求。)
如果没有token想直接进入非白名单页面(一般是除登录页面以外的),则需要进入登录页进行上面过程后再重定向到想去的页面。
因为此时后端未启动,不能与服务端端口建立连接,也就发送不了网络请求:
启动服务端,与服务端端口建立连接,发送网络请求(注意跨域问题,在文首有相关内容的链接):
如图成功获取token和用户信息:
2 组件更改和数据交互
初步进行前后端联调后,如果有的页面组件不符合我们的需求,我们要改成我们想要的样子,然后通过Axios完成数据异步加载。
修改组件页面。
异步加载列表数据,并渲染到页面组件上。
首先在路由模块中找出组件与路由的关联,对路由对应的组件进行更改。
{
path: '/example',
component: Layout,
redirect: '/example/table',
name: 'Example',
meta: { title: '用户管理', icon: 'el-icon-s-help' },
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/table/index'),
meta: { title: '用户列表', icon: 'table' }
},
{
path: 'tree',
name: 'Tree',
component: () => import('@/views/tree/index'),
meta: { title: '添加用户', icon: 'tree' }
}
]
},
所以要在在Table组件的进行更改,包括表单,表格,以及分页条,选择适当的组件后进行修改为符合自己需求的表单表格,注意需要在该表格用于展示用户的信息。
首先我们添加一个来包含整个组件页面(template中只能有一个根标签),然后在其中添加elementui的表格标签,在其中再添加表格列表,可以通过普通列定义其属性,方便进行渲染,还可以定义普通列的标签名以及调整适当的宽度。
注意如果响应数据打他中的性别值是0或1,则需要进行判断然后再渲染,在需要进行判断后渲染的普通列中加入一个插槽:
<template slot-scope="scope">
{{ scope.row.gender == 1 ? '男':'女' }}
</template>
表示判断每一条对象中的gender为"1"则显示为"男",否则为"女"。表格全部代码如下(注意还需要在数据模型返回绑定数据标签才会生效):
<el-table :data="tableData" border>
<el-table-column prop="id" label="序号" width="50"></el-table-column>
<el-table-column prop="classes" label="班级" width="120"></el-table-column>
<el-table-column prop="uname" label="姓名" width="120"></el-table-column>
<el-table-column prop="gender" label="性别" width="50">
<template slot-scope="scope">
{{ scope.row.gender == 1 ? '男':'女' }}
</template>
</el-table-column>
<el-table-column prop="birthday" label="生日"></el-table-column>
<el-table-column label="操作">
<el-button type="primary" size="mini">编辑</el-button>
<el-button type="primary" size="mini">删除</el-button>
</el-table-column>
</el-table>
剩下的表单和分页条可查看文档加入到Table组件中。
最后向服务端发送axios异步请求然后完成数据的渲染。
然后我们定义一个组件生命周期函数created(钩子),当组件被创建展示为页面前调用,在该方法中我们可以向后端发送axios请求数据。如在fetchData方法中封装了getList方法,该方法负责向后端指定接口发送请求:
前端发送的异步请求的方法,一般在api目录下定义。如在getList方法封装的request方法中定义请求路径url,以及请求方法GET,以及要向后端传递的参数。(该参数是后面分页请求要传递的页号)。这样后端就能接收到该请求并响应对应的结果集。
所以当我们想在单页面应用模板vue-admin-template中向后端发送请求时,可以在api目录下定义对应的方法,在request方法中定义对应的属性。
<script>
import { getList } from '@/api/table'
export default {
data() {
return {
tableData: [],
searchForm: {
"name":"",
gengder:""
},
value1:""
}
},
created: function() {
this.fetchData()
},
methods: {
fetchData() {
getList().then(response => {
this.tableData = response.data.data
})
},
}
}
</script>
如下图在前后端联调后,前端向后端发送请求后,后端返回响应数据data,通过赋值渲染到组件页面中:
上面只展现了各个组件的修改,简单地响应的数据,并未实现分页、编辑以及删除等功能。
2.1 分页功能
先分析,当我们点击分页的页号时,需要请求后端响应该页号的几条数据,如果是升序排序,就是向下读取几条数据作为一页;降序则是向上读取几条数据作为一页。然后返回响应数据然后进行赋值渲染。
<!-- 分页条 -->
<el-pagination background layout="total, prev, pager, next"
@size-change="handleSizeChange"
@current-change="changePage"
:page-size="pageSize"
v-if="isShow"
:total="total"></el-pagination>
可以看到第一次进入页面,当组件创建完毕时自动向后端发送请求,后端读取几条数据作为第一页返回,然后赋值渲染给组件页面;而当我们点击分页号时,会在分页组件中触发changePage方法(依然是调用getList方法向后端发送异步请求)。可以看到,该方法重新向后端发送请求,并传递分页的页号,后端接收到后根据该页号进行分页查询,最后封装到结果集中返回,然后前端接收后进行赋值和渲染。这就完成了数据的分页显示。
<script>
import { getList } from '@/api/table'
export default {
data() {
return {
tableData: [],
searchForm: {
"name":"",
gengder:"",
},
value1:"",
total: 1,
pageSize: 1,
isShow: false //默认当为响应数据时不显示分页条
}
},
created: function() {
this.fetchData()
},
methods: {
fetchData() {
getList().then(response => {
this.tableData = response.data.items.records
//总记录
this.total = response.data.items.total
//每页显示条数
this.pageSize = response.data.items.size
//当响应回数据时才显示分页
this.isShow = true
})
},
changePage(pageNum){
getList(pageNum).then((response => {
this.tableData = response.data.items.records
}))
},
}
}
</script>
如下后端后端的网络接口,观察理解以下,可以看到如果前端没有传递参数(也就是我们第一次打开页面时),后端默认响应第一页的数据,另外在查询条件中,通过id升序查询(如果用的是Sqlserver必须加该条件,否则报"@P0"附近有语法错误),最后封装到结果集中返回:
@GetMapping("/getAll")
public Result getAl(@RequestParam(defaultValue = "1") int pageNum){
//分页对象
Page<User> page = new Page<>(pageNum, 3);
//Sqlserver中需要加一个唯一字段排序,否则系统排序结果不唯一
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.orderByAsc("id");
Page<User> userPage = userMapper.selectPage(page, wrapper);
return Result.ok().data("items", userPage);
}
2.2 添加用户
在介绍编辑和删除之前,先对用户数据表进行添加用户。添加用户较简,只需在前端获取到用户数据就可以发送给后台进行插入。
在在用户表格中点击添加,触发doAdd方法。在doAdd方法中通过编程式路由跳转到添加页面。
<el-button type="primary" @click="doAdd">添加</el-button>
doAdd(){
//通过编程式路由跳转到添加页面
this.$router.push("/users/add");
},
在添加页面的表单中输入要添加的用户数据后确认添加,触发onSubmit方法,在方法中调用导入的网络请求方法add并传递表单数据。
onSubmit() {
add(this.form).then(res=>{
this.$message({
message: '添加成功',
type: 'success'
})
});
}
在add网络请求方法中向url发送post请求并发送填入的用户表单数据:
export function add(data) {
return request ({
url: '/user/add',
method: 'post',
data
})
}
后端服务器接收请求后调用相应控制器的接口进行处理:
@ApiOperation("添加用户")
@PostMapping("/add")
public Result addUser(@RequestBody User user) {
userMapper.insertUser(user);
return Result.ok();
}
在用户控制器中定义了一个接收post请求的接口,通过@RequestBody注解接收实体类对象,调用UserMapper用户映射接口中自定义的insertUser方法添加用户。因为如果直接调用MybatisPlus的insert方法,该方法会根据用户User类定义的属性自动生成插入的sql语句,这样包括id在内的所有属性都作为字段插入数据。但是在sqlsever中自增长的id字段不能显示插入并报错。所以在进行插入操作时,需要在UserMapper用户映射接口中定义Mybatis发送手写的sql插入语句。
另外,在自定义插入sql语句时,需要字段和只一一对应,否则会出现混插的情况。
@Insert("insert into t_user values(#{uname}, #{pwd}, #{birthday}, #{gender}), #{classes}")
//或者
@Insert("insert into t_user(uname, pwd, birthday, gender, classes)
values(#{uname}, #{pwd}, #{birthday}, #{gender}), #{classes}")
另另外,其实MybatisPlus对sqlsever数据库进行插入操作的问题可以通过@TableField(exist = false)注解来说明id属性不作为字段处理,但是后面的查询、更新和删除操作又需要用到id,所以不能去掉,只能委屈一下插入操作了。
2.3 编辑和删除功能
编辑用户与添加用户类似,不过还要在前台展示要编辑的用户数据,方便编辑;在后台,接收到传递来的用户对象时,需要提取用户id作为条件进行用户的删除。
首先进行较删除功能难一点的编辑功能。
2.3.1 编辑
在用户表格中点击编辑,调用handleEdit方法并传递当前行的对象:
<el-button type="primary" size="mini" @click="handleEdit(scope)">编辑</el-button>
在handleEdit方法中,将flag设置为true显示编辑浮窗,然后将传递的对象赋值给form表单对象进行展示,方便用户对比编辑。
handleEdit(scope) {
//首先打开浮窗
this.flag=true;
//1.将传递的对象数据赋值给要编辑的表单,这种方式直接影响用户表格数据的显示
this.form = scope.row
//2.或者是将对象里面属性展开,不用原来的对象赋值,避免影响用户表格数据的显示
// this.form = {...scope.row}
},
<!--定义浮窗的标签-->
<el-dialog title="编辑" width="50%" :visible.sync="flag"></el-dialog>
修改完表单数据后,点击浮窗的确定按钮调用doEdit方法,并关掉浮窗:
<el-button type="primary" @click="doEdit();flag=false">确 定</el-button>
在doEdit方法中,调用从api目录导入的edit方法发送异步请求,将编辑好的表单作为参数传递。
doEdit(){
edit(this.form).then(res=>{
this.$message({
message: '编辑成功',
type: 'success'
})
});
},
在api目录下js文件中的edit网络请求方法中定义请求的url、请求操作以及传递的参数:
export function edit(data) {
return request ({
url: '/user/edit',
method: 'put',
data // data: {data}
})
}
然后后端服务器接收请求后调用相应控制器的接口进行处理:
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {
@Autowired
private UserMapper userMapper;
@PutMapping("/edit")
public Result editById(@RequestBody User user) {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
int id = user.getId();
System.out.println(user);
wrapper.eq("id",id);
userMapper.update(user, wrapper);
return Result.ok();
}
}
在用户控制器中定义了一个接收put请求的接口,通过@RequestBody注解接收实体类对象,然后提取对象中的id作为条件对象传给MybatisPlus中的update更新方法进行修改。因是更新数据,数据库只返回影响行数,我们只需要返回请求成功的结果集即可。
2.3.2 删除
删除比较简单,在表格中点击删除,直接调用doDelete方法传递该行数据的id:
<el-button type="primary" size="mini" @click="doDelete(scope.row.id)">删除</el-button>
在doDelete方法中直接调用网络请求方法:
doDelete(id) {
del(id).then(res => {
this.$message({
message: '删除成功',
type: 'success'
})
})
},
在del方法中发送异步请求:
export function del(id) {
return request ({
url: '/user/del',
method: 'delete',
params: { id } //跟data一样,如果参数为params也可以简写为params
})
}
后端控制器中定义的delete请求接口直接根据传递过来的id参数,调用BaseMapper接口中的deleteById删除用户:
@ApiOperation("根据id删除用户")
@DeleteMapping("/del")
public Result deleteUser(int id){
userMapper.deleteById(id);
return Result.ok();
}