• 【基础篇】六、基于SpringBoot来整合SSM的案例(下)


    接下来加入前端页面,使用axios发送异步请求调用上篇的接口。调前端代码时,发现还挺有趣,刷新、隐藏、调用、以及一些交互逻辑的代码翻译,等框架学完看看前端的东西。
    在这里插入图片描述

    1、前后端调用:axios发送异步请求

    关于前端资源文件的位置:

    • 前后端分离结构设计中页面归属前端服务器
    • 单体工程中页面放置在resources目录下的static目录中(建议执行clean)

    调用下上篇的getAll接口,并将结果打印到console控制台来先调试下。前端发送异步请求,调用后端接口:

    //列表
    getAll() {
    	axios.get("/books").then((res)=>{
    		console.log(res.data);
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    created钩子函数用于初始化页面时发起调用,如页面加载完后要调接口查全部图书:

    //钩子函数,VUE对象初始化完成后自动执行
    created() {
    	//查全部
    	this.getAll();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    将查询数据返回到页面,利用前端数据双向绑定进行数据展示:

    //列表
    getAll() {
    	axios.get("/books").then((res)=>{
    		this.dataList = res.data.data;
    	});
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重启服务,刷新页面,列表功能实现。

    在这里插入图片描述

    2、添加功能

    弹出添加窗口:

    //弹出添加窗口
    handleCreate() {
    	this.dialogFormVisible = true;  //true即弹出
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样写,弹窗的窗口带有上次添加时的数据,需要在每次弹出前清除旧数据,即重置表单:

    //重置表单
    resetForm() {
    	this.formData = {};
    },
    
    • 1
    • 2
    • 3
    • 4

    因此,弹出窗口应该是:

    //弹出添加窗口
    handleCreate() {
    	this.dialogFormVisible = true;
    	this.resetForm();
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    弹出添加窗口后,写数据,然后提交给后端:

    //添加
    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();
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    以上即添加成功,则关闭弹层并给用户一个message添加成功。添加失败时不关闭弹层,方便用户修改后继续添加。不管成功与否,最后finally都刷新下列表。最后,当点弹窗的取消时,给用户一个取消成功的提示:

    //取消
    cancel(){
    	this.dialogFormVisible = false;  //关闭弹窗
    	this.$message.info("当前操作取消");
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    3、删除功能

    基本逻辑和添加类似:删除接口返回成功时弹窗给用户提示删除成功,接口失败则返回删除失败。

    // 删除
    handleDelete(row) {
    	axios.delete("/books/"+row.id).then((res)=>{
    		if(res.data.flag){
    			this.$message.success("删除成功");
    		}else{
    			this.$message.error("删除失败");
    		}
    	}).finally(()=>{
    		this.getAll();
    	});
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这样一点删除按钮,就会调用删除接口,为了防止用户误操作,加$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("取消删除操作");
    	});
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    删除这块,注意三点:

    • 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("数据不存在,已自动刷新页面");
    		}
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (以下这个场景复现:复制页面,在一个页面后删除后,另一个页面未刷新,在这个页面点编辑,即编辑一条不存在的数据。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();
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    取消编辑:

    //和之前的取消复用一个函数
    cancel(){
    	this.dialogFormVisible = false;    //关闭新增弹窗窗口
    	this.dialogFormVisible4Edit = false;  //关闭编辑弹窗窗口
    	this.$message.info("操作取消");
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    到此,基本的增删改查的接口与页面完成。

    5、异常消息处理

    当接口请求出现异常时,返回体如下,和统一结果类R不一样,影响前端取值:

    {
    "timestamp": "2021-09-15T03:27:31.038+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/books"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    鉴于此,加统一异常处理器,将异常处理成统一结果类返回给前端。修改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);
    	}
    
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    定义全局异常处理器

    @RestControllerAdvice
    public class ProjectExceptionAdvice {
    	@ExceptionHandler(Exception.class)
    	public R doException(Exception ex){
    		//记录日志
    		//发送消息给运维
    		//发送邮件给开发人员,ex对象发送给开发人员
    		ex.printStackTrace();
    		return R.error("服务异常,请稍后重试!");
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    此时,前端的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();
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    当然,响应成功的提示也可以改为res.data.msg,后端返回结果加msg:

    @PostMapping
    public R save(@RequestBody Book book) throws IOException {
    	Boolean flag = bookService.insert(book);
    	return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    最后,小总结:

    • 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
    • 异常处理器必须被扫描加载,否则无法生效(当然@RestControllerAdvice注解里面包含了@Component,你注意扫描范围就好)
    • 表现层返回结果的模型类中添加消息属性msg用来传递消息到页面

    6、分页功能

    页面使用el分页组件添加分页功能:

    <!--分页组件-->
    <div class="pagination-container">
    	<el-pagination
    		class="pagiantion"
    		@current-change="handleCurrentChange"
    		:current-page="pagination.currentPage"
    		:page-size="pagination.pageSize"
    		layout="total, prev, pager, next, jumper"
    		:total="pagination.total">
    	</el-pagination>
    </div>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    定义分页组件需要使用的数据并将数据绑定到分页组件:

    data:{
    	pagination: { //分页相关模型数据
    		currentPage: 1, //当前页码
    		pageSize:10, //每页显示的记录数
    		total:0, //总记录数
    	}
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    将之前的查全部接口改为调用分页功能的接口,传入上面定义的currentPage、pageSize

    getAll() {
    	axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    之前的分页接口:

    @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);
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    加载分页数据,并给组件的页码和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;
    	});
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    从第一页切换到第二页,即切换页码的实现,就改下currentPage,并调用getAll:

    //切换页码
    handleCurrentChange(currentPage) {
    	this.pagination.currentPage = currentPage;
    	this.getAll();
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    效果:

    在这里插入图片描述
    分页功能的实现流程:

    • 使用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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8、条件查询

    查询条件数据封装,可单独封装,也可和之前的分页一起封装:

    pagination: { //分页相关模型数据
    	currentPage: 1, //当前页码
    	pageSize:10, //每页显示的记录数
    	total:0, //总记录数
    	name: "",
    	type: "",
    	description: ""
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    页面数据模型绑定:

    <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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    组织数据成为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;
    	});
    },
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    修改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);
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    修改Service层,使用LamdbaQueryWrapper对象拼接查询条件:

    public interface IBookService extends IService<Book> {
    	IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook);
    }
    
    • 1
    • 2
    • 3
    @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);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    最终效果:

    在这里插入图片描述

    到此,基础篇结束。

  • 相关阅读:
    事务死锁排查
    java计算机毕业设计甜趣网上蛋糕店订购系统源码+系统+数据库+lw文档+mybatis+运行部署
    三相组合式过电压保护器试验
    SAP替代物料的解决方案详解
    学习无人机代码框架【第一天】---VMware 安装Ubuntu16.04时显示不全的解决方法
    使用tesseract-ocr实现图片中的中英文字符提取
    Python实现 Leecodet
    [附源码]java毕业设计心理测评系统
    [晕事]今天做了件晕事26;gcc对strcmp/strncmp的优化
    ADS差分传输线前仿真
  • 原文地址:https://blog.csdn.net/llg___/article/details/132948524