文章目录
- 1、前后端调用:axios发送异步请求
- 2、添加功能
- 3、删除功能
- 4、修改功能
- 5、异常消息处理
- 6、分页功能
- 7、分页Bug处理
- 8、条件查询
接下来加入前端页面,使用axios发送异步请求调用上篇的接口。调前端代码时,发现还挺有趣,刷新、隐藏、调用、以及一些交互逻辑的代码翻译,等框架学完看看前端的东西。
1、前后端调用:axios发送异步请求
关于前端资源文件的位置:
- 前后端分离结构设计中页面归属前端服务器
- 单体工程中页面放置在resources目录下的static目录中(建议执行clean)
调用下上篇的getAll接口,并将结果打印到console控制台来先调试下。前端发送异步请求,调用后端接口:
//列表
getAll() {axios.get("/books").then((res)=>{console.log(res.data);});
},
created钩子函数用于初始化页面时发起调用,如页面加载完后要调接口查全部图书:
//钩子函数,VUE对象初始化完成后自动执行
created() {//查全部this.getAll();
}
将查询数据返回到页面,利用前端数据双向绑定进行数据展示
:
//列表
getAll() {axios.get("/books").then((res)=>{this.dataList = res.data.data;});
},
重启服务,刷新页面,列表功能实现。
2、添加功能
弹出添加窗口:
//弹出添加窗口
handleCreate() {this.dialogFormVisible = true; //true即弹出
},
这样写,弹窗的窗口带有上次添加时的数据,需要在每次弹出前清除旧数据,即重置表单:
//重置表单
resetForm() {this.formData = {};
},
因此,弹出窗口应该是:
//弹出添加窗口
handleCreate() {this.dialogFormVisible = true;this.resetForm();
},
弹出添加窗口后,写数据,然后提交给后端:
//添加
handleAdd () {//发送异步请求axios.post("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层,显示数据if(res.data.flag){this.dialogFormVisible = false;this.$message.success("添加成功");}else {this.$message.error("添加失败");}}).finally(()=>{this.getAll();});
},
以上即添加成功,则关闭弹层并给用户一个message添加成功。添加失败时不关闭弹层,方便用户修改后继续添加。不管成功与否,最后finally都刷新下列表。最后,当点弹窗的取消时,给用户一个取消成功的提示:
//取消
cancel(){this.dialogFormVisible = false; //关闭弹窗this.$message.info("当前操作取消");
},
3、删除功能
基本逻辑和添加类似:删除接口返回成功时弹窗给用户提示删除成功,接口失败则返回删除失败。
// 删除
handleDelete(row) {axios.delete("/books/"+row.id).then((res)=>{if(res.data.flag){this.$message.success("删除成功");}else{this.$message.error("删除失败");}}).finally(()=>{this.getAll();});
}
这样一点删除按钮,就会调用删除接口,为了防止用户误操作,加$confirm,catch后面就是取消删除的逻辑:
// 删除
handleDelete(row) {//1.弹出提示框this.$confirm("确认要删除这条数据吗?","Tip",{type:'info'}).then(()=>{//2.做删除业务axios.delete("/books/"+row.id).then((res)=>{……}).finally(()=>{this.getAll();});}).catch(()=>{//3.取消删除this.$message.info("取消删除操作");});
}
删除这块,注意三点:
- row对象,删除操作需要传递当前行数据对应的id值到后台,
row.id
(row的数据可以log.console看下) - 删除操作结束后动态刷新页面加载数据
- 删除操作前弹出提示框避免误操作
4、修改功能
首先是弹出修改窗口,里面包含当前这条数据的信息:
//弹出编辑窗口
handleUpdate(row) {axios.get("/books/"+row.id).then((res)=>{if(res.data.flag){//展示弹层,加载数据this.formData = res.data.data;this.dialogFormVisible4Edit = true;}else{this.$message.error("数据不存在,已自动刷新页面");}});
},
(以下这个场景复现:复制页面,在一个页面后删除后,另一个页面未刷新,在这个页面点编辑,即编辑一条不存在的数据。PS:前面的删除应该也有这个场景,需要加个else)
以上:
- 加载要修改数据通过传递当前行数据对应的id值到后台查询数据
- 利用前端数据双向绑定将查询到的数据进行回显
写修改的逻辑:
//修改
handleEdit() {axios.put("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层并刷新页面if(res.data.flag){this.dialogFormVisible4Edit = false;this.$message.success("修改成功");}else {this.$message.error("修改失败,请重试");}}).finally(()=>{this.getAll();});
},
取消编辑:
//和之前的取消复用一个函数
cancel(){this.dialogFormVisible = false; //关闭新增弹窗窗口this.dialogFormVisible4Edit = false; //关闭编辑弹窗窗口this.$message.info("操作取消");
},
到此,基本的增删改查的接口与页面完成。
5、异常消息处理
当接口请求出现异常时,返回体如下,和统一结果类R不一样,影响前端取值:
{
"timestamp": "2021-09-15T03:27:31.038+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/books"
}
鉴于此,加统一异常处理器,将异常处理成统一结果类返回给前端。修改R类,加msg字段:
@Data
public class R{private Boolean flag;private Object data;private String msg;public R(Boolean flag,Object data){this.flag = flag;this.data = data;}public R(Boolean flag,String msg){this.flag = flag;this.msg = msg;}//也可以定义个静态方法,里面封装构造方法public static R success(Object data){return new R(ture,data)}public static R error (String msg){return new R(false,msg);}}
定义全局异常处理器
:
@RestControllerAdvice
public class ProjectExceptionAdvice {@ExceptionHandler(Exception.class)public R doException(Exception ex){//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员ex.printStackTrace();return R.error("服务异常,请稍后重试!");}
}
此时,前端的msg就别写死了,从响应里拿res.data.msg
:
//添加
handleAdd () {//发送异步请求axios.post("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层,显示数据if(res.data.flag){this.dialogFormVisible = false;this.$message.success("添加成功");}else {this.$message.error(res.data.msg);}}).finally(()=>{this.getAll();});
},
当然,响应成功的提示也可以改为res.data.msg,后端返回结果加msg:
@PostMapping
public R save(@RequestBody Book book) throws IOException {Boolean flag = bookService.insert(book);return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}
最后,小总结:
使用注解@RestControllerAdvice定义SpringMVC异常处理器
用来处理异常的异常处理器必须被扫描加载,否则无法生效
(当然@RestControllerAdvice注解里面包含了@Component,你注意扫描范围就好)- 表现层返回结果的模型类中添加消息属性msg用来传递消息到页面
6、分页功能
页面使用el分页组件
添加分页功能:
<!--分页组件-->
<div class="pagination-container"><el-paginationclass="pagiantion"@current-change="handleCurrentChange":current-page="pagination.currentPage":page-size="pagination.pageSize"layout="total, prev, pager, next, jumper":total="pagination.total"></el-pagination>
</div>
定义分页组件需要使用的数据并将数据绑定到分页组件:
data:{pagination: { //分页相关模型数据currentPage: 1, //当前页码pageSize:10, //每页显示的记录数total:0, //总记录数}
},
将之前的查全部接口改为调用分页功能的接口,传入上面定义的currentPage、pageSize
getAll() {axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {});
},
之前的分页接口:
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){IPage<Book> pageBook = bookService.getPage(currentPage, pageSize);return new R(null != pageBook ,pageBook);}
加载分页数据,并给组件的页码和pageSize赋值,否则页码显示不对:
getAll() {axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {this.pagination.total = res.data.data.total;this.pagination.currentPage = res.data.data.current;this.pagination.pagesize = res.data.data.size;this.dataList = res.data.data.records;});
},
从第一页切换到第二页,即切换页码的实现,就改下currentPage,并调用getAll:
//切换页码
handleCurrentChange(currentPage) {this.pagination.currentPage = currentPage;this.getAll();
},
效果:
分页功能的实现流程:
- 使用el分页组件
定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
7、分页Bug处理
场景:
一共两页数据,第二页仅有一条数据,删除这条数据后,实际只有一页数据,但前端页面仍停留在第二页。以下是查询接口的返回结果:
F12看到此时前端传参currentPage为2,后端接口修改下:对查询结果进行校验,当当前页码值大于总页码值时,就重新查询,使用最大页码值当作currentPage来查。
(IPage对象中就有数据总共能有几页的数据pages)
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){//先按传的查,拿到IPage对象IPage<Book> page = bookService.getPage(currentPage, pageSize);//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值if( currentPage > page.getPages()){page = bookService.getPage((int)page.getPages(), pageSize);}return new R(true, page);
}
8、条件查询
查询条件数据封装,可单独封装,也可和之前的分页一起封装:
pagination: { //分页相关模型数据currentPage: 1, //当前页码pageSize:10, //每页显示的记录数total:0, //总记录数name: "",type: "",description: ""
}
页面数据模型绑定:
<div class="filter-container"><el-input placeholder="图书类别" v-model="pagination.type" class="filter-item"/><el-input placeholder="图书名称" v-model="pagination.name" class="filter-item"/><el-input placeholder="图书描述" v-model="pagination.description" class="filter-item"/><el-button @click="getAll()" class="dalfBut">查询</el-button><el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>
组织数据成为get请求发送的数据,拼出一个请求路径,log.console看下param拼接是否正常:
getAll() {//1.获取查询条件,拼接查询条件param = "?name="+this.pagination.name;param += "&type="+this.pagination.type;param += "&description="+this.pagination.description;console.log("-----------------"+ param);axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res) => {this.pagination.total = res.data.data.total;this.pagination.currentPage = res.data.data.current;this.pagination.pagesize = res.data.data.size;this.dataList = res.data.data.records;});
},
修改Controller,接收条件查询的参数,GET下接收对象,不加@RequestBody:
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);return new R(null != pageBook ,pageBook);}
修改Service层,使用LamdbaQueryWrapper
对象拼接查询条件:
public interface IBookService extends IService<Book> {IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook);
}
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook){IPage page = new Page(currentPage,pageSize);LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();lqw.like(Strings.isNotEmpty(queryBook.getName()),Book::getName,queryBook.getName());lqw.like(Strings.isNotEmpty(queryBook.getType()),Book::getType,queryBook.getType());lqw.like(Strings.isNotEmpty(queryBook.getDescription()),Book::getDescription,queryBook.getDescription());return bookDao.selectPage(page,lqw);}
}
最终效果:
到此,基础篇结束。