主流框架SSM
- 实现功能03-添加家居信息
- 需求分析/图解
- 思路分析
- 代码实现
- 注意事项和细节
- 实现功能04-显示家居信息
- 需求分析/图解
- 思路分析
- 代码实现
- 实现功能05-修改家居信息
- 需求分析/图解
- 思路分析
- 代码实现
- 注意事项和细节
- 实现功能06-删除家居信息
- 需求分析/图解
- 思路分析
- 代码实现
- 课后作业
- 实现功能07-分页显示列表
- 需求分析/图解
- 思路分析
- 代码实现
- 完成测试
- 实现功能08-带条件查询分页显示列表
- 需求分析/图解
- 思路分析
- 代码实现
- 实现功能09-添加家居表单前端校验
- 需求分析/图解
- 思路分析
- 代码实现
- 实现功能10-添加家居表单后端校验
- 需求分析/图解
- 思路分析
- 代码实现
- SSM项目[前后端分离]小结+作业要求
上一章, SSM整合-前后端分离-项目环境搭建 (上)
实现功能03-添加家居信息
需求分析/图解
思路分析
1.完成后台代码从 dao -> service -> controller
, 并对每层代码进行测试, 到controller
这一层, 使用Postman
发送http post
请求完成测试.
2.完成前端代码, 使用axios发送ajax
(json
数据)给后台, 实现添加家居信息
代码实现
回忆JavaWeb版本家居购项目
1创建src/main/java/com/zzw/furn/service/FurnService.java
public interface FurnService {
//添加
public void save(Furn furn);
}
2.创建src/main/java/com/zzw/furn/service/impl/FurnServiceImpl.java
@Service
public class FurnServiceImpl implements FurnService {
//注入/装配FurnMapper接口对象[代理对象]
@Resource
private FurnMapper furnMapper;
@Override
public void save(Furn furn) {
//解读
//1.使用insertSelective
//2.因为我们furn表的id是自增的, 就使用insertSelective
furnMapper.insertSelective(furn);
}
}
3.测试 src/test/java/com/zzw/furn/service/FurnServiceTest.java
动态代理jdk的Proxy和Spring的CGlib, getBean方法何时会返回代理对象
public class FurnServiceTest {
//属性
private ApplicationContext ioc;
//从spring容器中,获取的是FurnService接口对象/代理对象
private FurnService furnService;
/**
* 解读
* 1. 编写方法完成初始化
* 2. 当方法标注 @Before, 表示在执行你的目标测试方法前, 会先执行该方法
*/
@Before
public void init() {
ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
//说明
//1.通过FurnService.class类型获取 FurnService接口代理对象
furnService = ioc.getBean(FurnService.class);
System.out.println("furnService=" + furnService.getClass());
}
@Test
public void save() {
Furn furn =
new Furn(null, "你好", "你好", new BigDecimal(100), 123, 2, "image");
furnService.save(furn);
System.out.println("添加成功");
}
}
4.修改Furn.java
, 当创建Furn
对象 imgPath
为null
时, imgPath
给默认值
//当创建Furn对象 imgPath 为 null时, imgPath 给默认值
private String imgPath = "assets/images/product-image/1.jpg";
public Furn(Integer id, String name, String maker, BigDecimal price, Integer sales, Integer stock, String imgPath) {
this.id = id;
this.name = name;
this.maker = maker;
this.price = price;
this.sales = sales;
this.stock = stock;
//解读
//如果imgPath不为null, 而且是有数据, 就设置给this.imgPath; 否则就使用默认值
//imgPath != null && !imgPath.equals("") => 使用一个工具类方法完成 => org.springframework.util
//public static boolean hasText(@Nullable String str) {
// return str != null && !str.isEmpty() && containsText(str);
//}
//StringUtils.hasText(imgPath) 就是要求imgPath 不是null, 而且不是"", 而且不是" "
if (StringUtils.hasText(imgPath)) {
this.imgPath = imgPath;
} else {
this.imgPath = "assets/images/product-image/1.jpg";
}
}
5.创建src/main/java/com/zzw/furn/bean/Msg.java
, 用来返回json
数据的通用类.
/**
* Msg: 后端程序返回给前端的json数据的Msg对象=>本质就是数据规则
*/
@Getter
@Setter
public class Msg {
//状态码 200-成功 400-失败
private int code;
//信息-返回数据的说明
private String msg;
//返回给客户端/浏览器的数据-Map集合
private Map<String, Object> extend = new HashMap<>();
//编写几个常用的方法-封装好msg
//返回success对应的msg
public static Msg success() {
Msg msg = new Msg();
msg.setCode(200);
msg.setMsg("success");
return msg;
}
//返回fail对应的msg
public static Msg fail() {
Msg msg = new Msg();
msg.setCode(400);
msg.setMsg("fail");
return msg;
}
//给返回的msg设置数据
public Msg add(String key, Object value) {
extend.put(key, value);
return this;
}
}
6.创建com/zzw/furn/controller/FurnController.java
,处理添加请求.
@Controller
public class FurnController {
//注入配置FurnService
@Resource
private FurnService furnService;
/**
* 1.响应客户端的添加请求
* 2.@RequestBody:
* 使用SpringMVC的 @RequestBody 将客户端提交的json数据, 封装成JavaBean对象
* 3.@ResponseBody:
* 我们往往需要服务器返回的数据格式是按照json来返回的(底层是按照http协议进行协商)
*
* @param furn
* @return
*/
@PostMapping("/save")
@ResponseBody
public Msg save(@RequestBody Furn furn) {
furnService.save(furn);
//没有抛出异常, 就返回成功信息
Msg success = Msg.success();
return success;
}
}
7.使用Postman
来完成Controller
层测试, 通过Postman
添加Furn
数据
1)使用Postman
进行测试. Postman如何发送json数据
{
"name":"海澜之家衣柜",
"maker":"小米之家",
"price":13.3,
"sales":300,
"stock":1000
}
2)使用Postman
测试时, 因为我们前台发送的是json
数据, 被服务器接收到后, 转成javabean
数据, 因此pom.xml需要引入jackson, 处理json
数据, 否则后台会报错.
<!--引入jackson, 处理json数据-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
8.点击添加按钮, 可以出现添加家居的对话框, 修改D:\idea_project\SSM-Vue整合项目\ssm_vue\src\views\HomeView.vue
.
说明一下 el-dialog从Dialog对话框获取▶️, 表单代码从Form表单获取▶️, 组合一下即可
js定义对象
<template>
<div>
<!--增加按钮和搜索框-->
<div style="margin: 10px 5px">
<!--解读
1.@click="add" 表示点击新增, 就会触发add方法
-->
<el-button type="primary" @click="add">新增</el-button>
<el-button>其它</el-button>
</div>
<!--margin: 顶部距离 左侧距离-->
<div style="margin: 10px 5px">
<el-input v-model="search" style="width: 20%" placeholder="请输入家居名"/>
<el-button style="margin: 10px" type="primary">查询</el-button>
</div>
<el-table :data="tableData" stripe style="width: 90%">
<el-table-column sortable prop="date" label="日期"/>
<el-table-column prop="name" label="名字"/>
<el-table-column prop="address" label="地址"/>
<el-table-column fixed="right" label="操作" width="120">
<template #default>
<el-button link type="primary">
编辑
</el-button>
<el-button link type="primary">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--说明家居的弹窗
说明:
1.el-dialog v-model="dialogFormVisible" 表示对话框, 和 dialogFormVisible 变量双向绑定, 控制是否显示对话框
2.el-form :model="form" 表示 表单数据 和 form数据变量 双向绑定
3.el-input v-model="form.name" 表示表单的input控件, 名字为name, 需要和后台JavaBean属性一致
4.在前端中, 对象的属性是可以动态生成的. 这个知识点, 我们在讲解前端技术栈讲过.
-->
<el-dialog v-model="dialogVisible" title="提示" width="500">
<el-form :model="form" label-width="120px">
<el-form-item label="家居名">
<el-input v-model="form.name" style="width: 80%"/>
</el-form-item>
<el-form-item label="厂商">
<el-input v-model="form.marker" style="width: 80%"/>
</el-form-item>
<el-form-item label="价格">
<el-input v-model="form.price" style="width: 80%"/>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.sales" style="width: 80%"/>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.stock" style="width: 80%"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
// @ is an alias to /src
// 导出组件
export default {
name: 'HomeView',
components: {},
data() {
return {
search: '',
dialogVisible: false,
form: {},//定义一个空表单
}
},
methods: {
add() {
//显示对话框
this.dialogVisible = true;
//情况添加表单数据
this.form = {}
}
}
}
</script>
7.完成测试, 点击新增按钮, 看看能否正常的弹出添加家居的对话框(含有表单).
8.项目前端: 安装axios, 用于发送Ajax
请求给后台
npm i axios -S
9.创建工具文件 D:\idea_project\SSM-Vue整合项目\ssm_vue\src\utils\request.js
//引入axios包
//重要提示: 如果在启动前端项目时, 提示找不到axios, 把光标放在 import axios from "axios"的'axios', 会有一个修复提示, 导入axios, 导入即可正常使用.
import axios from "axios";
//通过axios创建对象-request对象, 用于发送请求到后端
const request = axios.create({
timeout: 5000
})
//request拦截器的处理
//1.可以对请求做统一的处理
//2.比如统一地假如token, Content-Type等
request.interceptors.request.use(config => {
config.headers['Content-Type'] = "application/json;charset=utf-8";
return config;
}, error => {
return Promise.reject(error);
})
//导出request对象, 在其它文件引入就可以使用
export default request;
10.修改HomeView.vue
, 在methods
编写save
方法, 并测试, 会出现跨域问题.
发送axios请求
<script>
// @ is an alias to /src
//导入request对象
import request from "@/utils/request";
//导出组件
export default {
name: 'HomeView',
components: {},
methods: {
save() {//将填写的表单数据,发送给后端
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("http://localhost:8080/ssm/save", this.form).then(res => {
console.log("res=", res);
console.log("res.data=", res.data);
this.dialogVisible = false;
})
}
}
}
</script>
11.修改D:\idea_project\SSM-Vue整合项目\ssm_vue\vue.config.js
, 解决跨域问题.
因为修改了配置文件, npm serve 需要重启, 否则不能识别.
module.exports = {
devServer: {
port: 10000, //前端vue启动端口
//解读:如果我们请求的地址 /api/save => 代理到 http://localhost:8080/ssm/save
proxy: { //设置代理, 必须填
'/api': { //设置拦截器, 拦截器格式 斜杠+拦截器名字, 名字可以自己定
target: 'http://localhost:8080/ssm', //代理的目标地址, 就是/api 代理 http://localhost:8080/ssm
changeOrigin: true, //是否设置同源, 输入是的, 浏览器就允许跨域
pathRewrite: { //路径重写
'/api':'' //选择忽略拦截器里面的单词
}
}
}
}
}
12.老师提醒, 这里容易出现的问题
1)一定要确定 request.post(“/api/save”) 被代理后的url, 是项目后台服务对应提供的API接口.
2)跨域执行请求时, 如果报错, 浏览器还是提示http://localhost:10000/api/xxx, 所以不要认为是api没有替换你的配置.
注意事项和细节
1.Postman
测试时, 要制定Content-Type
, 否则会报错
2.如果需要将提交的json
数据, 封装到对应的JaveBean
, 需要配置@RequestBody
, 否则会报错500
.
如果需要返回json
数据, 需要在方法上, 配置@ResponseBody
, 否则会报错404
.
实现功能04-显示家居信息
需求分析/图解
思路分析
1.完成后台代码从dao -> service ->controller
, 并对每层代码进行测试.
2.完成前台代码, 使用axios
发送http
请求, 返回所有家居数据, 将数据绑定显示.
代码实现
1.修改FurnService.java
和FurnServiceImpl.java
, 增加findAll
方法
//查询所有的家居信息
public List<Furn> findAll();
@Override
public List<Furn> findAll() {
//查看分析FurnMapper.xml文件
//传入是null, 表示返回所有的家居信息
return furnMapper.selectByExample(null);
}
2.修改FurnServiceTest.java
, 测试find All
方法.
@Test
public void findAll() {
List<Furn> furns = furnService.findAll();
for (Furn furn : furns) {
System.out.println("furn--" + furn);
}
}
3.修改FurnController.java
, 处理修改请求, 并使用Postman完成测试.
/**
* 响应客户的查询请求
* @param furn
* @return
*/
@GetMapping("/furns")
@ResponseBody
public Msg listFurns() {
List<Furn> furns = furnService.findAll();;
没有抛出异常, 就返回成功信息
//Msg msg = Msg.success();
把家居信息, 封装到msg对象
//return msg.add("furns", furns);
return Msg.success().add("furns", furns);
}
4.修改HomeView.vue
, 编写list
方法. vue生命周期
1)修改 el-table
<el-table :data="tableData" stripe style="width: 90%">
<el-table-column sortable prop="id" v-model="tableData.id"/>
<el-table-column prop="name" v-model="tableData.name" label="家居名"/>
<el-table-column prop="maker" v-model="tableData.maker" label="厂商"/>
<el-table-column prop="sales" v-model="tableData.sales" label="销量"/>
<el-table-column prop="price" v-model="tableData.price" label="价格"/>
<el-table-column prop="stock" v-model="tableData.stock" label="库存"/>
<el-table-column fixed="right" label="操作" width="120">
<template #default>
<el-button link type="primary">
编辑
</el-button>
<el-button link type="primary">删除</el-button>
</template>
</el-table-column>
</el-table>
2)修改 tableData: []
data() {
return {
search: '',
dialogVisible: false,
form: {},//定义一个空表单
tableData: []
}
},
3)在created() 调用list() 完成页面数据获取
methods: {
save() {//将填写的表单数据,发送给后端
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("/api/save", this.form).then(res => {
console.log("res=", res);
console.log("res.data=", res.data);
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
})
},
//编写list方法, 请求返回家居信息
//list方法应该是自动调用
list() {
request.get("/api/furns").then(res => {
console.log("res.data--", res.data);
//解读: 为什么是res.data.extend.furns, 可以根据console输出的数据结构来查看
this.tableData = res.data.extend.furns;
})
}
},
created() {//生命周期函数
this.list();//调用list方法
}
5.完成测试, 看看是否可以显示家居列表信息
6.修改src/utils/request.js
, 增加response
拦截器, 统一处理响应后结果
字符串转JSON
//可以在调用接口响应后, 统一地处理返回结果
request.interceptors.response.use(response => {
let res = response.data;
//如果返回的是文件
if (response.config.responseType === 'blob') {
return res;
}
//如果是string, 就转成json
if (typeof res === 'string') {
//如果res 不为'', 就转换成json对象
res = res ? JSON.parse(res) : res;
}
return res;
}, error => {
console.log("error=", error);
return Promise.reject(error);
})
7.修改HomeView.vue
, 简化返回处理
list() {
request.get("/api/furns").then(res => {
console.log("res--", res);
//解读: 为什么是res.data.extend.furns, 可以根据console输出的数据结构来查看
//因为我们对返回response结果进行了统一拦截处理 let res = response.data;
//, 所以我们这里直接使用 res.extend.furns
this.tableData = res.extend.furns;
})
}
8.完成测试
实现功能05-修改家居信息
需求分析/图解
思路分析
1.完成后台代码从 dao ->service -> controller
, 并对每层代码进行测试
2.完成前台代码, 回显家居信息, 再使用axios
发送http
请求, 更新数据, 将数据绑定显示.
代码实现
1.修改FurnService.java
和FurnServiceImpl.java
, 增加update
方法
//修改家居
public void update(Furn furn);
@Override
public void update(Furn furn) {
furnMapper.updateByPrimaryKeySelective(furn);
}
2.修改FurnServiceTest.java
, 测试update
方法.
@Test
public void update() {
Furn furn = new Furn();
furn.setId(18);
furn.setName("大象家居");
furn.setMaker("大象传人");
//因为imgPath属性有一个默认值, 所以如果我们不希望生成的update语句对img_path字段修改, 就显示地设置null
furn.setImgPath(null);
furnService.update(furn);
System.out.println("修改成功");
}
3.修改FurnController.java
, 处理修改请求, 并使用Postman完成测试.
rest请求风格
/**
* 1.响应客户端的修改请求
* @param furn
* @return
*/
@PutMapping("/update")
@ResponseBody
public Msg update(@RequestBody Furn furn) {
furnService.update(furn);
//没有抛出异常, 就返回成功信息
Msg success = Msg.success();
return success;
}
4.修改HomeView.vue
, 编写handleEdit
方法, 回显数据 并测试.
<el-table-column fixed="right" label="操作" width="120">
<!--说明
1.这里通过 handleEdit(scope.row)
2.可以将当前行数据传递给 handleEdit
-->
<template #default="scope">
<el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button link type="primary">删除</el-button>
</template>
</el-table-column>
methods: {
handleEdit(row) {
//console.log("row--", row);
//将当前的家居信息绑定到弹出对话框的表单上
//1. 方式1: 可以通过row.id 到后端-DB去获取对应的家居信息, 返回后将其绑定到this.form[小伙伴自己思考解决]
//2. 方式2: 把获取的row的数据通过处理, 绑定到this.form 进行显示
//3. JSON.stringify(row): 将row 转成json字符串
//4. JSON.parse(JSON.stringify(row)) 将json字符串 转成json对象
this.form = JSON.parse(JSON.stringify(row));
//显示对话框
this.dialogVisible = true;
}
}
5.修改HomeView.vue
, 修改save
方法, 处理修改请求, 添加 更新成功的消息框, 不需要做额外处理
save() {//将填写的表单数据,发送给后端
//修改和添加时走的同一个方法
if (this.form.id) {//表示修改
request.put("/api/update", this.form).then(res => {
// console.log("res=", res);
if (res.code === 200) {//修改成功
//提示一个成功的消息框
this.$message({
message: "修改成功",
type: "success",
});
} else {
this.$message({
message: "修改失败",
type: "error",
});
}
//关闭对话框
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
})
} else {//表示添加
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("/api/save", this.form).then(res => {
// console.log("res=", res);
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
});
}
//这里有一个注意事项...
},
注意事项和细节
1.<template #default="scope">
2.调用list()
刷新数据需要注意的地方. 这样写是错误的, axios本质是ajax请求, 异步处理机制.
save() {//将填写的表单数据,发送给后端
//修改和添加时走的同一个方法
if (this.form.id) {//表示修改
//本质发出ajax请求-异步处理
request.put("/api/update", this.form).then(res => {
// console.log("res=", res);
if (res.code === 200) {//修改成功
//提示一个成功的消息框
this.$message({
message: "修改成功",
type: "success",
});
} else {
this.$message({
message: "修改失败",
type: "error",
});
}
})
} else {//表示添加
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("/api/save", this.form).then(res => {
// console.log("res=", res);
});
}
//关闭对话框
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
//这里有一个注意事项...
},
实现功能06-删除家居信息
需求分析/图解
思路分析
代码实现
1.修改FurnService.java
和FurnServiceImpl.java
, 增加del
方法
//删除家居
public void del(Integer id);
@Override
public void del(Integer id) {
furnMapper.deleteByPrimaryKey(id);
}
2.修改FurnServiceTest.java
, 测试del
方法.
@Test
public void del() {
furnService.del(22);
System.out.println("删除成功");
}
3.修改FurnController.java
, 处理修改请求, 并使用Postman完成测试.
URL占位符
/**
* 1.响应客户端的删除请求
* @param furn
* @return
*/
@DeleteMapping("/del/{id}")
@ResponseBody
public Msg del(@PathVariable("id") Integer id) {
furnService.del(id);
//没有抛出异常, 就返回成功信息
Msg success = Msg.success();
return success;
}
4.修改HomeView.vue
, 编写handleDel
方法, 完成删除 并测试.
<el-table-column fixed="right" label="操作" width="120">
<!--说明
1.这里通过 handleEdit(scope.row)
2.可以将当前行数据传递给 handleEdit
-->
<template #default="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<!--说明
1.如果你点击的是确定, 就会触发handleDel
2.如果你点击的是取消, 就不会触发handleDel
-->
<el-popconfirm title="确定要删除吗?" @confirm="handleDel(scope.row.id)">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column><el-table-column fixed="right" label="操作" width="120">
<!--说明
1.这里通过 handleEdit(scope.row)
2.可以将当前行数据传递给 handleEdit
-->
<template #default="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<!--说明
1.如果你点击的是确定, 就会触发handleDel
2.如果你点击的是取消, 就不会触发handleDel
-->
<el-popconfirm title="确定要删除吗?" @confirm="handleDel(scope.row.id)">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
handleDel(id) {
console.log("id=", id);
request.delete('/api/del/' + id).then(res => {
if (res.code === 200) {//删除成功
this.$message({
message: res.msg,
type: "success",
});
} else {//删除失败
this.$message({
message: res.msg,
type: "error",
});
}
//调用list方法, 刷新数据
this.list();
})
},
课后作业
1.把前面我们实现的 Vue+SSM
各个功能, 自己写一遍.
- 一定要自己写一遍, 否则没有印象, 理解不会深入.
- 如果出现问题, 一定是代码问题, 要静下心来, 认真排查, 要逐步锻炼自己的排错能力.
2.修改家居信息功能, 回显家居表单数据, 改成从后端-DB获取
●代码实现
1.修改FurnService.java
和FurnServiceImpl.java
, 增加findFurnById
方法
//根据id查询家居信息
public Furn findFurnById(Integer id);
@Override
public Furn findFurnById(Integer id) {
return furnMapper.selectByPrimaryKey(id);
}
2.修改FurnServiceTest.java
, 测试findFurnById
方法.
@Test
public void findFurnById() {
Furn furn = furnService.findFurnById(7);
System.out.println("furn--" + furn);
}
3.修改FurnController.java
, 处理修改请求, 并使用Postman完成测试.
/**
* 1.响应客户端的查询请求
* @param furn
* @return
*/
@GetMapping("/findFurnById/{id}")
@ResponseBody
public Msg findFurnById(@PathVariable("id") Integer id) {
Furn furn = furnService.findFurnById(id);
//没有抛出异常, 就返回成功信息
return Msg.success().add("furn", furn);
}
4.修改HomeView.vue
, 编写handleEdit
方法, 并测试.
handleEdit(row) {
// console.log("row--", row);
//将当前的家居信息绑定到弹出对话框的表单上
//1. 方式1: 可以通过row.id 到后端-DB去获取对应的家居信息, 返回后将其绑定到this.form[小伙伴自己思考解决]
request.get('/api/findFurnById/' + row.id).then(res => {
this.form = res.extend.furn;
})
//2. 方式2: 把获取的row的数据通过处理, 绑定到this.form 进行显示
//3. JSON.stringify(row): 将row 转成json字符串
//4. JSON.parse(JSON.stringify(row)) 将json字符串 转成json对象
// this.form = JSON.parse(JSON.stringify(row));
//显示对话框
this.dialogVisible = true;
},
实现功能07-分页显示列表
需求分析/图解
思路分析
1.后台使用MyBatis PageHelper
插件完成分页查询.前端使用分页组件.
2.修改FurnController
, 增加处理分页显示代码 API
/接口
3.完成前台代码, 加入分页导航, 并将分页请求和后台接口结合.
4.简单回顾JavaWeb原生项目的分页模型. 说明: 有了MyBatis PageHelper
和前端的分页组件, 完成分页就非常的方便, 但是底层的分页模型和前面我们的java web
原生项目一样.
代码实现
1.修改pom.xml
, 加入分页插件
<!--引入mybatis pageHelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.1</version>
</dependency>
2.修改mybatis-config.xml
, 配置分页拦截器
<!--说明
1.plugins标签需要放在typeAliases标签后, 是DOCTYPE约束的
2.配置分页拦截器
-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--解读
配置分页合理化
1.如果用户请求的pageNum > pages, 就显示查询最后一页
2.如果用户请求的pageNum < 0, 就显示查询第一页
-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
3.修改FurnController.java
, 增加分页查询处理
/**
* 分页请求接口
*
* @param pageNum: 要显示第几页, 默认为1
* @param pageSize: 每页要显示几条记录, 默认为5
* @return
*/
@RequestMapping("/furnsByPage")
@ResponseBody
public Msg listFurnsByPage(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize) {
//设置分页参数
//解读
//1.调用findAll完成查询, 底层会进行物理分页, 而不是逻辑分页
//2.会根据分页参数来计算 limit ?, ?, 在发出sql语句时, 会带limit
//3.我们后面会给大家抓取SQL
PageHelper.startPage(pageNum, pageSize);
List<Furn> furns = furnService.findAll();
//将分页查询的结果, 封装到PageInfo
//PageInfo 对象包含了分页的各个信息, 比如当前页面pageNum, 共有多少记录
PageInfo pageInfo = new PageInfo(furns, pageSize);
//将pageInfo封装到Msg对象, 返回
return Msg.success().add("pageInfo", pageInfo);
}
4.使用Postman
进行测试, 看看分页查询是否OK
数据包含在了list属性里
5.修改HomeView.vue
, 完成分页导航显示, 分页请求. 给标签属性绑定值
1)添加分页导航控件
<div style="margin: 10px 0px">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
v-bind:current-page="currentPage"
v-bind:page-sizes="[5, 10]"
v-bind:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
v-bind:total="total">
</el-pagination>
</div>
2)增加分页初始化数据
data() {
return {
//增加分页相应的数据绑定
currentPage: 1,//当前页
pageSize: 5,//每页显示记录数
total: 10,//共有多少记录
}
}
3)修改list方法, 换成分页请求数据的接口
list() {
//请求分页的接口
request.get("/api/furnsByPage", {
params: {//指定请求携带的参数
pageNum: this.currentPage,
pageSize: this.pageSize,
}
}).then(res => {//处理返回的分页信息
this.tableData = res.extend.pageInfo.list;
this.total = res.extend.pageInfo.total;
})
},
4)处理当前页变化, 比如点击分页链接, 或者go to 第几页
handleCurrentChange(pageNum) {//处理分页请求
//当用户点击分页超链接时, 会携带pageNum
// console.log("pageNum=", pageNum);
this.currentPage = pageNum;
//发出请求
this.list();
},
5)处理每页显示多少条记录变化
handleSizeChange(pageSize) {
this.pageSize = pageSize;
//发出请求
this.list();
},
完成测试
实现功能08-带条件查询分页显示列表
需求分析/图解
思路分析
1.完成后台代码从 dao ->service -> controller
, 并对每层代码进行测试
2.完成前台代码, 使用axios
发送http
请求, 完成带条件查询分页显示
代码实现
1.修改FurnService.java
和FurnServiceImpl.java
, 增加条件查询
//根据家居名查询家居信息
public List<Furn> findByCondition(String name);
@Override
public List<Furn> findByCondition(String name) {
FurnExample furnExample = new FurnExample();
//通过criteria 对象可以设置查询条件
FurnExample.Criteria criteria = furnExample.createCriteria();
//判断name是否有具体的内容
if (StringUtils.hasText(name)) {
criteria.andNameLike("%" + name + "%");
}
//说明: 如果没有传值, 即为null, "", " "时, 依然是查询所有的记录
return furnMapper.selectByExample(furnExample);
}
2.修改FurnServiceTest.java
, 测试findByCondition
方法.
@Test
public void findByCondition() {
List<Furn> furns = furnService.findByCondition("风格");
for (Furn furn : furns) {
System.out.println("furn--" + furn);
}
}
3.修改FurnController.java
, 处理带条件分页查询, 并使用Postman完成测试.
/**
* 根据家居名进行分页查询-带条件
*
* @param pageNum
* @param pageSize
* @return
*/
@RequestMapping("/furnsByConditionPage")
@ResponseBody
public Msg listFurnsByCondition(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String search) {
//设置分页参数
//解读
//1.调用findAll完成查询, 底层会进行物理分页, 而不是逻辑分页
//2.会根据分页参数来计算 limit ?, ?, 在发出sql语句时, 会带limit
//3.我们后面会给大家抓取SQL
PageHelper.startPage(pageNum, pageSize);
List<Furn> furns = furnService.findByCondition(search);
//将分页查询的结果, 封装到PageInfo
//PageInfo 对象包含了分页的各个信息, 比如当前页面pageNum, 共有多少记录
PageInfo pageInfo = new PageInfo(furns, pageSize);
//将pageInfo封装到Msg对象, 返回
return Msg.success().add("pageInfo", pageInfo);
}
4.修改HomeView.vue
<!--margin: 顶部距离 左侧距离-->
<div style="margin: 10px 5px">
<el-input v-model="search" style="width: 20%" placeholder="请输入家居名"/>
<el-button style="margin: 10px" type="primary" @click="list">检索</el-button>
</div>
数据池
data() {
return {
search: '',//检索条件, 可以在进行分页时保留上次的检索条件
}
},
list方法
list() {
//请求分页的接口-带检索条件
request.get("/api/furnsByConditionPage", {
params: {//指定请求携带的参数
pageNum: this.currentPage,
pageSize: this.pageSize,
search: this.search,
}
}).then(res => {//处理返回的分页信息
this.tableData = res.extend.pageInfo.list;
this.total = res.extend.pageInfo.total;
})
},
5.测试
实现功能09-添加家居表单前端校验
需求分析/图解
说明: 参考 element-plus 表单验证
思路分析
1.完成前台代码, 使用ElementPlus
的表单 rules
验证即可
2.参考element-plus 表单验证文档
代码实现
1.修改HomeView.vue
, 增加表单验证处理代码. 验证数字
增加对表单各个字段的校验规则
data() {
return {
//定义添加表单校验规则
rules: {
name: [
//这里我们可以写多个针对name属性的校验规则
{required: true, message: "请输入家居名", trigger: "blur"}
],
maker: [
//这里我们可以写多个针对maker属性的校验规则
{required: true, message: "请输入厂商", trigger: "blur"}
],
price: [
//这里我们可以写多个针对price属性的校验规则
{required: true, message: "请输入价格", trigger: "blur"},
//使用正则表达式对输入的数据进行校验
{pattern: /^([1-9]\d*|0)(\.\d+)?$/, message: "请输入数字", trigger: "blur"}
],
sales: [
//这里我们可以写多个针对sales属性的校验规则
{required: true, message: "请输入销量", trigger: "blur"},
{pattern: /^([1-9]\d*|0)$/, message: "请输入数字", trigger: "blur"}
],
stock: [
//这里我们可以写多个针对stock属性的校验规则
{required: true, message: "请输入库存", trigger: "blur"},
{pattern: /^([1-9]\d*|0)$/, message: "请输入数字", trigger: "blur"}
],
}
}
},
指定将创建的规则引用到form表单, 注意名称要对应
2.测试, 就可以看到验证规则生效了. 是光标离开输出框时, 出现检验效果, 因为是trigger: "blur"事件, 但是用户提交还是能成.
3.修改HomeView.vue
, 但表单验证不通过时, 不提交表单
修改save方法
save() {//将填写的表单数据,发送给后端
//修改和添加时走的同一个方法
if (this.form.id) {//表示修改
//本质发出ajax请求-异步处理
//...
} else {//表示添加
//表单验证是否通过
this.$refs['form'].validate(valid => {
//valid就是表单校验后返回的结果
if (valid) {//如果校验通过
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("/api/save", this.form).then(res => {
// console.log("res=", res);
//关闭对话框
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
});
} else {//校验没有通过
this.$message({
type: "error",
message: "表单校验失败, 不提交"
})
return false;//放弃提交
}
})
}
//这里有一个注意事项...
},
修改add方法
add() {//显示添加对话框-带表单
//显示对话框
this.dialogVisible = true;
//情况添加表单数据
this.form = {};
//清空上次校验的信息
this.$refs['form'].resetFields();
},
实现功能10-添加家居表单后端校验
需求分析/图解
1.为什么前端校验了, 后端还需要校验? -使用Postman
添加数据, 打破前端校验机制
2.后端校验-需求分析, 但后端校验没有通过, 会出现灰色框提示, 后台不真正入库数据
思路分析
1.后台 使用JSR303数据校验, 引入hibernate-validator.jar, 学习SpringMVC JSR303校验时遇到过.
2.前端 使用ElementPlus进行数据绑定, 并显示错误信息
代码实现
1.修改pom.xml
, 引入hibernate-validator
jar文件
<!--JSR303数据校验支持引入hibernate-validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.1.Final</version>
</dependency>
2.修改Furn.java
, 使用hibernate-validator
@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
, 处理带条件分页查询, 并使用Postman完成测试.
@PostMapping("/save")
@ResponseBody
public Msg save(@Validated @RequestBody Furn furn, Errors errors) {
Map<String, Object> map = new HashMap<>();
List<FieldError> fieldErrors = errors.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
}
if (map.isEmpty()) {//说明后端校验通过, 因为没有发现校验错误
furnService.save(furn);
//没有抛出异常, 就返回成功信息
Msg success = Msg.success();
return success;
} else {
//校验失败, 就把错误信息封装到Msg对象, 并返回
return Msg.fail().add("errors", map);
}
}
4.修改HomeView.vue
, 显示服务器校验返回的提示信息
在数据池, 增加显示错误信息的变量
data() {
return {
//存放后端校验的错误信息
serverValidErrors: {},
}
},
修改save方法, 显示错误提示
if (valid) {//如果校验通过
//解读
//1.url: http://localhost:8080/ssm/save
//2.this.form: 携带的数据
request.post("/api/save", this.form).then(res => {
console.log("res=", res);
if (res.code === 200) {//添加成功
//关闭对话框
this.dialogVisible = false;
//调用list方法, 刷新数据
this.list();
} else {//后端校验失败
//取出校验失败的信息, 赋给serverValidErrors
this.serverValidErrors.name = res.extend.errors.name;
this.serverValidErrors.maker = res.extend.errors.maker;
this.serverValidErrors.price = res.extend.errors.price;
this.serverValidErrors.sales = res.extend.errors.sales;
this.serverValidErrors.stock = res.extend.errors.stock;
}
});
}
修改对话框, 显示后台返回的校验错误信息
<el-form ref="form" :model="form" :rules="rules" label-width="120px" style="width: 80%">
<el-form-item label="家居名" prop="name">
<el-input v-model="form.name" style="width: 50%"/>
{{serverValidErrors.name}}
</el-form-item>
<el-form-item label="厂商" prop="maker">
<el-input v-model="form.maker" style="width: 50%"/>
{{serverValidErrors.maker}}
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="form.price" style="width: 50%"/>
{{serverValidErrors.price}}
</el-form-item>
<el-form-item label="销量" prop="sales">
<el-input v-model="form.sales" style="width: 50%"/>
{{serverValidErrors.sales}}
</el-form-item>
<el-form-item label="库存" prop="stock">
<el-input v-model="form.stock" style="width: 50%"/>
{{serverValidErrors.stock}}
</el-form-item>
</el-form>
为了触发后端校验, 先把前端校验解除
} else {//表示添加
//表单验证是否通过
this.$refs['form'].validate(valid => {
//这里我们让前端校验放行, 测试后端校验的效果, 测试完毕, 再改回来
valid = true;
//valid就是表单校验后返回的结果
if (valid) {//如果校验通过
测试
修改add()方法, 清空错误信息
add() {//显示添加对话框-带表单
//显示对话框
this.dialogVisible = true;
//情况添加表单数据
this.form = {};
//清空上次前端校验的信息
this.$refs['form'].resetFields();
//清空上次后端校验的信息
this.serverValidErrors = {};
},
SSM项目[前后端分离]小结+作业要求
●说明: 前后端分离开发, 前端框架Vue
+ 后端框架SSM
1.前端框架Vue
2.后端框架-SSM
(Spring + SpringMVC + MyBatis
)
3.数据库-MySQL
4.项目的依赖管理-Maven
5.分页-pagehelper
6.逆向工程-MyBatis Generator
7.其它…
●前端框架Vue
1.使用了ElementPlus来展示数据
2.使用Axios对象来请求数据 / 接口
●后端框架SSM
1.使用了经典的三层结构
2.使用MyBatis Generator 和 MyBatis pageHelper
●作业
1.把我们写的项目. 小伙伴可以独立地完整地写出来.
2.一定要自己写, 培养自己独立解决问题的能力, 并且提高解决Bug能力
3.逐步形成自己的编程套路 / 思路.
4.前端这块可以展示就可以了, 不需要写的非常好看, 关键是梳理清楚前后端数据通信机制. 多写, 多总结, 多梳理, 形成自己的知识.