功能实现03

9.功能08-分页显示

9.1需求分析

将查询的数据进行分页显示,要求功能如下:

  1. 显示共多少条记录
  2. 可以设置每页显示几条
  3. 点击第几页,显示对应的数据

9.2思路分析

  1. 后端使用MyBatisPlus分页插件完成查询
  2. 修改FurnController,增加处理分页显示代码
  3. 完成前台代码,加入分页导航,并将分页请求和后台接口结合

9.3代码实现

9.3.1分页插件

创建MyBatisPlusConfig.java,在配置类中引入MyBatis-Plus分页插件

package com.li.furn.config;

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; /**
* @author 李
* @version 1.0
*/
@Configuration
public class MybatisPlusConfig {
/**
* 思路:
* 1.注入MyBatisPlusInterceptor对象/bean
* 2.在注入MyBatisPlusInterceptor对象对象bean中,
* 加入分页插件-PaginationInnerInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页需要指定数据库类型,因为不同的数据库分页的sql语句不同
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

9.3.2Controller层

修改FurnController.java,增加分页查询处理

/**
* 分页查询的接口方法
*
* @param pageNo 显示第几页,默认为1
* @param pageSize 每页显示条数,默认为5
* @return
*/
@GetMapping("/page")
@ResponseBody
public Result listFurnsByPage(@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "5") Integer pageSize) {
//通过page方法,返回page对象,给对象封装分页数据
Page<Furn> page = furnService.page(new Page<>(pageNo, pageSize));
return Result.success(page);
}

使用postman测试:

测试结果:

后端发出的sql:可以看到实际上mybatis-plus底层发出了两次查询,先查询总记录数,再根据总记录数来分页。

9.3.3前端代码

修改HomeView.vue,完成分页导航显示,分页请求。部分代码:

第一部分:引入组件

<!--引入分页组件-可以根据自己的需要进行定制-->
<div style="margin: 10px 0">
<el-pagination
@size-change="handlePageSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5,10,20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>

第二部分:数据池,整个分页导航的功能主要就是依赖于pageSize和total

...
data() {
return {
currentPage: 1,//当前页数
pageSize: 5,//每页显示多少条记录
total: 10,//数据总记录数
...
}
},
...

第三部分:方法池

...
handlePageSizeChange(pageSize) {//处理pageSizes的修改
this.pageSize = pageSize;
//刷新家居列表
this.list();
},
handleCurrentChange(pageNum) {//修改当前要显示的页数
this.currentPage = pageNum;
this.list();
},
list() {//显示所有家居信息,后面再进行分页和考虑检索条件
// request.get("/api/furns").then(res => {
// //将返回的数据和tableData绑定
// this.tableData = res.data;
// })
request.get("/api/furnsByPage", {
params: {//params代表请求的数据
pageNo: this.currentPage,
pageSize: this.pageSize
}
}).then(res => {
// console.log(res)
//将返回的数据和tableData绑定
this.tableData = res.data.records;
//由返回结果修改对应的total
this.total = res.data.total;
})
},
...

页面结果显示:


10.功能09-带条件查询分页显示列表

10.1需求分析

如果在搜索框中根据名称查询家居,可以返回分页显示的家居列表,并且点击下一页时,显示的依然是符合条件的数据。

10.2思路分析

  1. 完成从后端代码从mapper(dao层)-->Service层-->Controller层,并对代码进行测试
  2. 完成前端代码,使用axios发送http请求,完成带条件查询分页显示

10.3代码实现

10.3.1Controller层

修改FurnController.java,添加按条件查询的方法

/**
* 根据家居名查询家居数据
*
* @param pageNo 当前页
* @param pageSize 每页显示的大小
* @param condition 查询条件:家居名,默认为空串
* @return
*/
@GetMapping("/furnsBySearchPage")
@ResponseBody
public Result listFurnsByConditionPage
(@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String condition) {
//先创建QueryWrapper,可以将检索条件封装到QueryWrapper
QueryWrapper<Furn> queryWrapper = Wrappers.query();
//判断条件是否为可空
if (StringUtils.hasText(condition)) {
//name为表中的字段
queryWrapper.like("name", condition);
}
//有条件的分页查询
Page<Furn> page = furnService.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.success(page);
}

使用postman测试:


10.3.2前端代码

在搜索按钮上绑定list方法,list方法修改请求地址,当condition搜索条件为空时,就查询的是所有的数据

list() {//考虑检索条件
request.get("/api/furnsBySearchPage", {
params: {//params代表请求的数据
pageNo: this.currentPage,
pageSize: this.pageSize,
condition: this.search
}
}).then(res => {
// console.log(res)
//将返回的数据和tableData绑定
this.tableData = res.data.records;
//由返回结果修改对应的total
this.total = res.data.total;
})
},

页面测试:可以看到点击下一页,检索条件没有失效


11.功能10-家居表单前后端校验

11.1需求分析

如果在新增家居表单中,直接提交空表单,后端会报错,因为对应的字段不允许为空。

因此需要在表单前端进行校验,当数据不符合条件时无法提交,并给予提示;同时在后端页应该对接受的数据进行校验,如果后端校验不通过也应该给予相应的提示。

11.2思路分析

  1. 前端使用ElementPlus--表单rules验证
  2. 后端使用JSR303数据校验

11.3代码实现

11.3.1前端代码

修改HomeView.vue,增加表单验证处理代码

部分代码:

<el-dialog title="提示" v-model="dialogVisible" width="35%">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<!--prop表示和rules的哪个规则进行校验-->
<el-form-item label="家居名" prop="name">
<el-input v-model="form.name" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="厂商" prop="maker">
<el-input v-model="form.maker" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="form.price" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="销量" prop="sales">
<el-input v-model="form.sales" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="库存" prop="stock">
<el-input v-model="form.stock" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</span>
</template>
</el-dialog>
data() {//数据池
return {
...
rules: {//提交表单的校验规则
name: [{required: true, message: '请输入家居名', trigger: 'blur'}],
maker: [{required: true, message: '请输入厂商', trigger: 'blur'}],
price: [
{required: true, message: '请输入价格', trigger: 'blur'},
{pattern: /^(([1-9]\d*)|0)(\.\d+)?$/, message: '请输入数字', trigger: 'blur'}
],
sales: [
{required: true, message: '请输入销量', trigger: 'blur'},
{pattern: /^(([1-9]\\d*)|0)$/, message: '请输入数字', trigger: 'blur'}
],
stock: [
{required: true, message: '请输入库存', trigger: 'blur'},
{pattern: /^(([1-9]\\d*)|0)$/, message: '请输入数字', trigger: 'blur'}
],
}
}
},
methods: {
save() {//(1)添加 (2)修改
if (this.form.id) {
...
} else {
//当添加家居时,弹出窗口的表单id是空的
//发送添加家居请求 //添加时和表单验证关联起来,如果没有通过验证,就不能提交
this.$refs['form'].validate(valid => {
if (valid) {//如果校验通过,可以提交表单
request.post("/api/save", this.form)
.then(res => {//res为后端返回的结果
console.log("res=", res)
this.dialogVisible = false;//发送请求后隐藏表单
this.list();
})
} else {//否则不能提交
this.$message({
type: "error",
message: "数据格式不正确"
})
return false;
}
})
}
}
}
...

页面效果:

11.3.2后端代码

为了防止如postman之类的软件绕过前端直接发送数据,还需要在后端进行校验。如果

(1)使用JSR303数据校验,在pom.xml中引入hibernate-validator.jar

<!--JSR303校验-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>

(2)修改Furn.java实体类

package com.li.furn.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range; import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal; /**
* @author 李
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Furn {
//IdType.AUTO-主键类型为自增长
@TableId(type = IdType.AUTO)
private Integer id; //对字符串进行非空校验,应该使用@NotEmpty
@NotEmpty(message = "请输入家居名")
private String name; @NotEmpty(message = "请输入厂商")
private String maker; @NotNull(message = "请输入数字")
@Range(min = 0, message = "价格不能小于0")
private BigDecimal price; @NotNull(message = "请输入数字")
@Range(min = 0, message = "销量不能小于0")
private Integer sales; @NotNull(message = "请输入数字")
@Range(min = 0, message = "库存不能小于0")
private Integer stock;
}

(3)修改FurnController.java的save()方法

@PostMapping("/save")
public Result save(@Validated @RequestBody Furn furn, Errors errors) {
//如果出现校验错误,SpringBoot底层会把错误信息封装到errors
HashMap<String, Object> map = new HashMap<>();
List<FieldError> fieldErrors = errors.getFieldErrors();
//遍历errors,将错误信息放入map中
for (FieldError fieldError : fieldErrors) {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
}
//如果没有错误信息,说明后端校验成功
if (map.isEmpty()) {
furnService.save(furn);
return Result.success();//返回成功数据
} else {//如果有错误信息,就不提交数据,并且将错误信息通过map返回客户端显示
return Result.error("400", "数据校验错误", map);
}
}

(4)Postman测试

测试结果:

(5)接入前端

在HomeView.vue的数据池中定义一个对象validMsg,用于接收后端的校验信息。

在save方法中,发送添加的ajax请求后,如果返回的操作码为400,说明后端校验出错,取出错误信息

将错误信息显示在添加表单中:

(6)页面结果:

12.Lambda的使用

完成带条件查询功能时,使用了QueryWrapper指定检索表的某个字段:

改进方法:可以使用LambdaQueryWrapper,对查询条件进行封装:

//使用LambdaQueryWrapper封装查询条件,完成检索
@GetMapping("/furnsBySearchPage2")
@ResponseBody
public Result listFurnsByConditionPage2
(@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String condition) {
//创建LambdaQueryWrapper对象,封装查询条件
LambdaQueryWrapper<Furn> lambdaQueryWrapper = Wrappers.<Furn>lambdaQuery();
//判断conditions是否为空
if (StringUtils.hasText(condition)) {
lambdaQueryWrapper.like(Furn::getName, condition);//Lambda表达式
}
Page<Furn> page =
furnService.page(new Page<>(pageNo, pageSize), lambdaQueryWrapper);
log.info("page={}", page);
return Result.success(page);
}

这里使用到了Lambda表达式:Furn::getName,对应的是类名::实例方法

其中,实例方法为getName方法,其实就是把Furn::getName赋给SFunction<T,R>(函数式接口,即有且只有一个抽象方法的接口)

java8 函数式接口的使用

lambda表达式" :: "的用法--方法引用

SFunction<T, R>的源码:

package com.baomidou.mybatisplus.core.toolkit.support;

import java.io.Serializable;
import java.util.function.Function; @FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}

SFunction<T, R>是一个自定义接口泛型,它的父接口Function<T, R>的源码如下:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> { R apply(T t);//根据类型T的参数,获取类型R的结果 //后面还有默认实现方法,略
//...
}

当我们传入Furn::getName时,就相当于实现了SFunction<T, R>继承的父接口Function<T, R>的 R apply(T t);方法。底层会根据传入的Furn::getName,取得到该方法(getName方法)对应的属性(name属性)所映射的表的字段。

相比没有使用Lambda表达式的方法,这样的方式更加灵活,并且在表字段修改时,不必重新编译代码来对应修改。

lambdaQueryWrapper.like(Furn::getName, condition);

等价于:

SFunction<Furn, Object> sf = Furn::getName;
lambdaQueryWrapper.like(sf,condition);

12.1Lambda方法引用模拟

package com.li.furn.Lambda;

/**
* @author 李
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
//方法一:实现DemoFunction/得到一个实现类的接口对象,使用匿名内部类实现(传统方式)
DemoFunction<Desk, String> df = new DemoFunction<Desk, String>() {
@Override
public String apply(Desk desk) {
return "Hello,Desk!";
}
};
//调用
String value = df.apply(new Desk());
System.out.println("value=" + value); /**
* 函数式接口的实现需要在匿名内部类中手动实现
* 但是很多时候已经有一个方法,只是没有以匿名内部类的方式实现。
* 能不能通过某种方式,将已有的这个方法直接实现接口方法呢?
* 这就是 Lambda表达式,通过类名引用实例方法(类名::实例方法)的一种好处
* (注意:实例方法要符合接口方法的形式)
*/ //方案二:使用Lambda表达式,将已有的方法直接实现接口的方法
//DemoFunction<Desk, String> df2 = Desk::getName;
DemoFunction<Desk, String> df2 = Desk::getBrand;
DemoFunction<Desk, Integer> df3 = Desk::getId;
//调用
String value2 = df2.apply(new Desk());
System.out.println("value2=" + value2); Integer value3 = df3.apply(new Desk());
System.out.println("value3=" + value3); }
} //定义一个函数式接口(有且只有一个抽象方法的接口)
//我们可以使用@FunctionalInterface来标识一个函数式接口
@FunctionalInterface
interface DemoFunction<T, R> {//带泛型的函数式接口 R apply(T t); //函数式接口依然可以有多个默认实现方法
default public void ok() {
}
} @FunctionalInterface
interface Demo2Function {//不带泛型的函数式接口 void hi();//抽象方法 //函数式接口依然可以有多个默认实现方法
default public void okk() {
}
} class Desk {//Bean
private String name = "Hi,Desk!";
private String brand = "Halo,我是Brand!";
private Integer id = 10086; public Integer getId() {
return id;
} public String getName() {
return name;
} public String getBrand() {
return brand;
}
}

13.项目小结

使用前后端分离,前端主题框架Vue3+后端的基础框架SpringBoot

  1. 前端技术栈:Vue3 + Axios + ElementPlus
  2. 后端技术栈:Spring Boot + MyBatis Plus
  3. 数据库 - MySql
  4. 项目依赖管理 - Maven
  5. 分页 - MyBatis Plus 分页插件
  6. 切换数据源 - DruidDataSource
  7. 在LambdaQueryWrapper引出Lambda方法引用的 类名::实例方法
  8. 项目前端使用到了request+response拦截器,并解决了跨域问题

day03-功能实现03的更多相关文章

  1. day01-4-订座功能

    满汉楼01-4 4.功能实现03 4.5订座功能 4.5.1功能说明 如果该餐桌处于已经预定或者就餐状态时,不能进行预定,并给出相应提示 4.5.2思路分析 根据显示界面,要考虑以下两种状态 检测餐桌 ...

  2. 前端复习之Ajax,忘完了

    1 * Day01: 2 * Ajax 3 * Asynchronous JavaScript and XML 4 * 直译中文 - JavaScript和XML的异步 5 * (不严格的定义)客户端 ...

  3. 关于Spring的核心组件以及概念

    1.什么是企业级应用 大型企业级应用的结构是非常复杂的,涉及外部资源非常多,事务密集,数据规模大,用户数量多,有较强的安全性考虑和较高的性能要求.   2.Spring概念理解 Spring是一个轻量 ...

  4. S2---深入.NET平台和C#编程的完美总结

    1.NET简单解说 l 面向对象提升 OOP(Object Oriented  Programming)面向对象编程 AOP:(Aspache  Oriented Programming):面向切面编 ...

  5. MODBUS-RTU通讯协议简介

    MODBUS-RTU通讯协议简介   什么是MODBUS? MODBUS 是MODICON公司最先倡导的一种软的通讯规约,经过大多数公司 的实际应用,逐渐被认可,成为一种标准的通讯规约,只要按照这种规 ...

  6. 软件工程(FZU2015)增补作业

    说明 张老师为FZU软件工程2015班级添加了一次增补作业,总分10分,deadline是2016/01/01-2016/01/03 前11次正式作业和练习的迭代评分见:http://www.cnbl ...

  7. ThinkPHP项目CMS内容管理系统开发视频教程【20课】(3.02GB)

    ThinkPHP背景介绍:     ThinkPHP是一个免费开源的,快速.简单的面向对象的轻量级PHP开发框架,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业级应用开发而诞生的. ...

  8. Modbus通信协议详解

    附:http://www.360doc.com/content/14/0214/13/15800361_352436989.shtml 一.Modbus 协议简介 Modbus 协议是应用于电子控制器 ...

  9. MM-科目自动分配

    SAP系统篇 MM自动记账解析之基本概念(01) https://blog.csdn.net/qq_33641781/article/details/78027802 MM自动记账解析之功能实现(02 ...

  10. 软件工程(FZU2015) 增补作业

    SE_FZU目录:1 2 3 4 5 6 7 8 9 10 11 12 13 说明 张老师为FZU软件工程2015班级添加了一次增补作业,总分10分,deadline是2016/01/01-2016/ ...

随机推荐

  1. Softmax偏导及BP过程的推导

    Softmax求导 其实BP过程在pytorch中可以自动进行,这里进行推导只是强迫症 A Apart证明softmax求导和softmax的BP过程 本来像手打公式的,想想还是算了,引用部分给出la ...

  2. FDMemTable用法

    procedure TForm1.FormCreate(Sender: TObject); Var i:integer; begin // i:=15; self.FDMemTable1.FieldD ...

  3. .NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(下)

    25 | 路由与终结点:如何规划好你的Web API 自定义约束实现了路由约束接口,它只有一个 Match 方法,这个方法传入了 Http 当前的 httpContext,route,routeKey ...

  4. Linux-数据集 TPC-H、TPC-DS的导入和使用(MySQL)

    一. TPC-H 数据集 1.数据集下载 TPC-H数据集: https://github.com/gregrahn/tpch-kit 可采用gcc下载或者直接下载zip包,然后解压即可. 具体使用方 ...

  5. CF1433E Two Round Dances 题解

    题目传送门 前置知识 圆排列 解法 \(\dfrac{Q_{n}^{\frac{n}{2}}Q_{\frac{n}{2}}^{\frac{n}{2}}}{A_{2}^{2}}\) 即为所求. 同时因为 ...

  6. NC13885 Music Problem

    题目链接 题目 题目描述 Listening to the music is relax, but for obsessive(强迫症), it may be unbearable. HH is an ...

  7. react 高效高质量搭建后台系统 系列

    react 高效高质量搭建后台系统 前言 目标:用 react 高效高质量搭建后台系统 如何实现:搞定一个优秀的.通用的.有一定复杂度的react的后台系统.类似项目就可以依葫芦画瓢快速展开. spu ...

  8. 【Unity3D】相机

    1 简介 ​ 相机用于渲染游戏对象,每个场景中可以有多个相机,每个相机独立成像,每个成像都是一个图层,最后渲染的图层在最前面显示. ​ 相机的属性面板如下: Clear Flags:设置清屏颜色,Sk ...

  9. [BUUCTF][WEB][ACTF2020 新生赛]Upload 1

    打开靶机url,右键查看网页源代码 其中有一段代码 <div class="light"><span class="glow"> < ...

  10. djang中orm使用iterator()

    当查询结果有很多对象时,QuerySet的缓存行为会导致使用大量内存.如果你需要对查询结果进行好几次循环,这种缓存是有意义的,但是对于 queryset 只循环一次的情况,缓存就没什么意义了.在这种情 ...