目录
功能一:分页查询医生基础信息(介绍MybatisPlus如何使用分页)
一、医生功能模块
模块需求:
本模块设计,我负责后端接口开发,另一位成员负责前端开发。
------------前端实现------------
<template>
<div>
<div style="margin: 20px 0px;">
<el-row>
<el-col :span="10" class="center">
<!-- 输入标签组件 -->
<el-input v-model="search" @focus="focus" @blur="blur" @keyup.enter.native="searchHandler"
placeholder="搜索医生名称" clearable>
<el-button slot="append" icon="el-icon-search" id="search" @click="searchHandler"></el-button>
</el-input>
<!---设置z-index优先级,防止被走马灯效果遮挡-->
<el-card @mouseenter="enterSearchBoxHanlder" v-if="isSearch" class="box-card" id="search-box"
style="position:absolute;z-index:15">
<dl v-if="isHistorySearch">
<dt class="search-title" v-show="history">历史搜索</dt>
<dt class="remove-history" v-show="history" @click="removeAllHistory">
<i class="el-icon-delete"></i>清空历史记录
</dt>
<el-tag v-for="search in historySearchList" :key="search.id" closable :type="search.type"
@close="closeHandler(search)" @click="searchistory(search)"
style="margin-right:5px; margin-bottom:5px;">{{ search.name }}</el-tag>
<dt class="search-title">热门搜索</dt>
<dd v-for="search in hotSearchList" :key="search.id">{{ search }}</dd>
</dl>
<dl v-if="isSearchList">
<dd v-for="search in searchList" :key="search.id">{{ search }}</dd>
</dl>
</el-card>
</el-col>
</el-row>
<el-dialog title="简介" :visible.sync="introductionDialog" width="30%">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="头像">
<!-- action表示为上传文件的url -->
<el-upload class="avatar-uploader">
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="姓名">
<h2>{{ form.name }}</h2>
</el-form-item>
<el-form-item label="个人简介">
<p>{{ form.introduction }}</p>
</el-form-item>
</el-form>
</el-dialog>
<el-dialog title="预约" :visible.sync="appointmentDialog1" width="40%">
<el-calendar v-model="value">
<div slot="dateCell" slot-scope="{ data }" @click="allcalendar(data)" class="temp">
<span>{{ data.day.split("-")[2] }}</span>
</div>
</el-calendar>
</el-dialog>
<el-dialog title="预约" :visible.sync="appointmentDialog2" width="40%">
<el-table :data="bookTime" style="width: 100%">
<el-table-column label="日期" width="180">
<template slot-scope="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.time }}</span>
</template>
</el-table-column>
<el-table-column label="剩余" width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>人数: {{ scope.row.Remain }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.Remain }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" @click="handleBooktime(scope.$index, scope.row)">预约</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="major" label="科室" sortable width="180" column-key="date" :filters="[{ text: '内科', value: '内科' },
{ text: '妇科', value: '妇科专业' },
{ text: '儿科', value: '儿科' },
{ text: '外科', value: '外科手术' }]" :filter-method="filterHandler">
</el-table-column>
<el-table-column prop="host_id" label="医生编号" width="120">
</el-table-column>
<el-table-column prop="name" label="医生名称" width="120">
</el-table-column>
<el-table-column prop="image" label="医生帅照" width="120">
</el-table-column>
<el-table-column prop="phone" label="电话" width="300">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template slot-scope="scope">
<el-button @click="handleCheck(scope.row)" type="text" size="small">查看</el-button>
<el-button @click="handleBook(scope.row)" type="text" size="small">预约</el-button>
</template>
</el-table-column>
</el-table>
<!-- 定义一个分页标签 -->
<div>
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageNum"
:page-sizes="[3, 8, 15, 30]" :page-size="3" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import RandomUtil from "../utils/randomUtil";
import Store from "../utils/store";
//导入request工具
import request from "@/utils/request";
export default {
data() {
return {
search: "", //当前输入框的值
isFocus: false, //是否聚焦
hotSearchList: ["暂无热门搜索"], //热门搜索数据
historySearchList: [], //历史搜索数据
searchList: ["暂无数据"], //搜索返回数据,
history: false,
bookDoctor: '',
bookTime: [
{
time: "8:00~9:00",
Remain: 3,
},
],
introductionDialog: false,
appointmentDialog1: false,
appointmentDialog2: false,
total: 0,
pageNum: 1,
pageSize: 3,
value: new Date(),
imageUrl: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
types: ["", "success", "info", "warning", "danger"], //搜索历史tag式样
tableData: [
],
form: {
doctorId: '',
department: '',
name: '',
province: '',
city: '',
address: '',
culture: '',
phone: '',
image: '',
sex: '',
age: '',
introduction: '',
},
};
},
// 通过在生命周期创建时候就使用查询方法
created() {
this.query();
},
methods: {
focus() {
this.isFocus = true;
this.historySearchList =
Store.loadHistory() == null ? [] : Store.loadHistory();
this.history = this.historySearchList.length == 0 ? false : true;
},
blur() {
var self = this;
this.searchBoxTimeout = setTimeout(function () {
self.isFocus = false;
}, 300);
},
enterSearchBoxHanlder() {
clearTimeout(this.searchBoxTimeout);
},
searchHandler() {
//随机生成搜索历史tag式样
let n = RandomUtil.getRandomNumber(0, 5);
//发起一个异步请求,查询分类的数据
request
// get表示指定请求地址 和 请求参数
.get("/vaccinum/doctor/SerchByName", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.search,
},
})
// then表示请求后的回调函数
.then((res) => {
console.log(res);
// 把后台的响应的数据赋值给data中的tableData
this.tableData = res.list;
this.form = res.list;
this.total = res.total;
});
let exist =
this.historySearchList.filter(value => {
return value.name == this.search;
}).length == 0
? false
: true;
if (!exist) {
this.historySearchList.push({ name: this.search, type: this.types[n] });
Store.saveHistory(this.historySearchList);
}
this.history = this.historySearchList.length == 0 ? false : true;
},
closeHandler(search) {
this.historySearchList.splice(this.historySearchList.indexOf(search), 1);
Store.saveHistory(this.historySearchList);
clearTimeout(this.searchBoxTimeout);
if (this.historySearchList.length == 0) {
this.history = false;
}
},
searchistory(search) {
this.search = search.name;
},
removeAllHistory() {
Store.removeAllHistory();
},
// 执行搜索函数,将后端数据展示出来
query() {
//发起一个异步请求,查询分类的数据
request
// get表示指定请求地址 和 请求参数
.get("/vaccinum/doctor/list", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
},
})
// then表示请求后的回调函数
.then((res) => {
console.log(res);
// 把后台的响应的数据赋值给data中的tableData
this.tableData = res.list;
this.form = res.list;
this.total = res.total;
});
},
handleCheck(row) {
console.log(row);
this.introductionDialog = true;
this.doctorId = row.doctorId;
this.form.department = row.department;
this.form.name = row.name;
this.form.province = row.province;
this.form.city = row.city;
this.form.address = row.address;
this.form.culture = row.culture;
this.form.phone = row.phone;
this.form.image = row.image;
this.form.introduction = row.introduction;
},
handleBook(row) {
console.log(row);
this.bookDoctor = row.name;
this.appointmentDialog1 = true;
},
handleBooktime(index,row){
// TODO 向后端发送用户提交的预约信息
console.log(row.time);
this.bookTime[index].Remain=this.bookTime[index].Remain-1;
},
//筛选功能实现函数
resetDateFilter() {
this.$refs.filterTable.clearFilter('date');
},
clearFilter() {
this.$refs.filterTable.clearFilter();
},
formatter(row, column) {
return row.address;
},
filterTag(value, row) {
return row.tag === value;
},
filterHandler(value, row, column) {
const property = column['property'];
return row[property] === value;
},
//修改单页数据数量
handleSizeChange(val) {
this.pageSize = val;
this.query();
},
//跳转页码
handleCurrentChange(val) {
console.log(val);
this.pageNum = val;
this.query();
},
// 日历触发事件
allcalendar(data) {
const loading = this.$loading({
// lock: true, //加上这个 页面点击日历的时候会莫名其妙抖动一下 因为我界面上有滚动条,所以我注释了
text: "Loading",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
setTimeout(() => {
this.value = data.day; //取到你需要的日期data.day
//需要用到这日期做啥事,比如做为调接口的参数
request
// get表示指定请求地址 和 请求参数
.get("/doctor/freetime", {
params: {
name: this.bookDoctor,
date: data.day,
},
})
// then表示请求后的回调函数
.then((res) => {
console.log(res);
});
this.appointmentDialog1 = false;
this.appointmentDialog2 = true;
loading.close();
}, 500);
},
},
computed: {
isHistorySearch() {
return this.isFocus && !this.search;
},
isSearchList() {
return this.isFocus && this.search;
},
isSearch() {
return this.isFocus;
}
},
};
</script>
<style>
.left {
margin-left: 200px;
}
.center {
margin-top: 15px;
margin-left: 200px;
}
#search {
background-color: #616cee;
border-radius: 0%;
}
.search-title {
color: #bdbaba;
font-size: 15px;
margin-bottom: 5px;
}
.remove-history {
color: #bdbaba;
font-size: 15px;
float: right;
margin-top: -22px;
}
#search-box {
width: 555px;
height: 300px;
margin-top: 0px;
padding-bottom: 20px;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.temp {
padding: 20px;
}
</style>
------------后端接口------------
功能一:分页查询医生基础信息(介绍MybatisPlus如何使用分页)
// 接口
// TODO:接口功能:查询医生一般信息
@RequestMapping("/list")
public String query(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "2") Integer pageSize) throws JsonProcessingException {
// 创建分页对象
Page<Doctor> page = new Page<>(pageNum, pageSize);
// 执行分页查询
IPage<Doctor> doctorIPage = doctorService.page(page);
// 从分页对象中获取分页数据和总记录数
List<Doctor> pageInfoList = doctorIPage.getRecords();
long total = doctorIPage.getTotal();
// 构造返回的医生信息列表
List<DoctorSummary> doctorSummaryList = new ArrayList<>();
for (Doctor doctor:pageInfoList){
doctorSummaryList.add(new DoctorSummary(doctor.getHostId(),doctor.getName(),doctor.getPhone(),doctor.getImage(),doctor.getMajor()));
}
// 返回医生不敏感信息的信息
return JsonTool.writeValueAsString(doctorSummaryList);
}
收获:MyBatis-Plus的分页插件与MyBatis的PageHelper的区别和使用。
区别:
使用范围:
- MyBatis-Plus 是一个全面的持久层框架,提供了更多的 CRUD 操作和通用的数据库操作,同时包含了一些方便的工具类和增强功能。
- PageHelper 是一个简单且专注于分页功能的插件,主要用于处理基本的分页查询需求,不包含其他复杂的数据库操作。
使用方式:
- MyBatis-Plus 的分页插件是内置在框架中的,无需额外引入依赖,可以直接使用其中的
Page
类进行分页查询,并且提供了丰富的分页查询方法。- PageHelper 需要单独引入依赖,根据具体的项目和需求选择相应的版本。使用 PageHelper 需要在 MyBatis 的配置文件中进行配置,并通过拦截器实现分页功能。
API 和功能支持:
- MyBatis-Plus 提供了更多的 API 和功能,如分页查询、排序、条件查询、自定义查询等。同时也支持 Lambda 表达式来简化条件查询的编写。
- PageHelper 的功能相对较为简单,主要提供了基本的分页功能,可以通过设置分页参数进行分页查询。
使用MyBatisPlus的方法,如下:
1.引入mybatis-plus依赖
<!--mybatis-plus插件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
2.进行配置,在config包下写对应配置类,按照如下代码进行配置
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 创建 MybatisPlusInterceptor 对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 创建并添加 PaginationInnerInterceptor 分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 返回 MybatisPlusInterceptor 对象,作为一个 Bean 注入到 Spring 容器中
return interceptor;
}
}
3.在控制层使用插件(通过案例代码解析插件使用步骤)
-
创建分页对象
Page<Doctor> page = new Page<>(pageNum, pageSize);
:pageNum
表示当前页码,指定要查询的页数。pageSize
表示每页显示的记录数,指定每页要展示多少条数据。通过这两个参数,可以确定分页查询的起始位置和返回的记录数量。
-
执行分页查询
IPage<Doctor> doctorIPage = doctorService.page(page);
:doctorService.page(page)
是调用doctorService
中的page
方法来执行分页查询操作。IPage<Doctor>
是 MyBatis-Plus 框架提供的分页结果对象,其中包含了分页查询的结果数据和总记录数。
-
从分页对象中获取分页数据和总记录数:
List<Doctor> pageInfoList = doctorIPage.getRecords();
获取当前页的数据列表。long total = doctorIPage.getTotal();
获取查询结果的总记录数。
总结:
Page<Doctor>
对象是 MyBatis-Plus 提供的分页对象,用于指定当前页码和每页显示的记录数。IPage<Doctor>
对象是分页查询的结果对象,包含了分页查询的数据列表和总记录数。- 通过
getRecords()
方法可以获取当前页的数据列表。- 通过
getTotal()
方法可以获取查询结果的总记录数。
下面介绍IPage的常用方法获取分页信息:
方法 | 描述 |
---|---|
long getTotal() | 获取查询结果的总记录数。 |
List<T> getRecords() | 获取当前页的数据列表。 |
int getCurrent() | 获取当前页码。 |
int getSize() | 获取每页显示的记录数。 |
int getPages() | 获取总页数。 |
default boolean hasPrevious() | 判断是否有上一页。 |
default boolean hasNext() | 判断是否有下一页。 |
List<OrderItem> orders() | 获取排序的字段和排序方式。可以用于在分页查询时设置排序规则。 |
Api接口测试结果:
前端功能显示:
3条每页展示
8条每页展示
功能二:根据搜索栏名称查找对应医生(讲解自定义查询集)
// TODO:根据搜索栏查找对应医生
@RequestMapping("/SerchByName")
public String SerchByName(@RequestParam("name")String name,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "8") Integer pageSize) throws JsonProcessingException {
// 创建分页对象
Page<Doctor> page = new Page<>(pageNum,pageSize);
// 自定义查询
QueryWrapper<Doctor> wrapper = new QueryWrapper<>();
wrapper.eq("name",name);
// 创建分页结果对象
IPage<Doctor> iPage = doctorService.page(page,wrapper);
List<Doctor> list = iPage.getRecords();
List<DoctorSummary> doctorSummaryList = new ArrayList<>();
for (Doctor doctor:list){
doctorSummaryList.add(new DoctorSummary(doctor.getHostId(),doctor.getName(),doctor.getPhone(),doctor.getImage(),doctor.getMajor()));
}
return JsonTool.writeValueAsString(doctorSummaryList);
}
收获:学习查询模块的使用(QueryWrapper)
使用步骤(三层框架)
1.首先创建QueryWrapper对象
2.通过调用对象内部方法完善自定义查询的条件,以下是常用的方法
方法名 | 说明 |
---|---|
eq | 设置等于条件 |
ne | 设置不等于条件 |
gt | 设置大于条件 |
ge | 设置大于等于条件 |
lt | 设置小于条件 |
le | 设置小于等于条件 |
between | 设置范围条件 |
like | 设置模糊查询条件 |
in | 设置包含在列表中的条件 |
orderByAsc | 设置升序排序字段 |
orderByDesc | 设置降序排序字段 |
and | 设置并且连接的条件 |
or | 设置或者连接的条件 |
last | 直接拼接在 SQL 语句的最后 |
select | 设置要查询的字段 |
groupBy | 设置分组字段 |
having | 设置聚合条件 |
3.通过自动注入对应的service层,实现对应的操作
MyBatisPlus扩展下service层的所有快捷方法:
方法名 | 作用 |
---|---|
save | 保存实体对象到数据库 |
saveBatch | 批量保存实体对象到数据库 |
removeById | 根据ID删除数据库中的记录 |
removeByMap | 根据条件删除数据库中的记录 |
remove | 根据条件删除数据库中的记录 |
removeByIds | 根据ID列表批量删除数据库中的记录 |
updateById | 根据ID更新数据库中的记录 |
update | 根据条件更新数据库中的记录 |
updateBatchById | 根据ID列表批量更新数据库中的记录 |
saveOrUpdate | 如果存在则更新,否则保存实体对象到数据库 |
getById | 根据ID从数据库中查询对应的记录 |
listByIds | 根据ID列表从数据库中查询对应的记录 |
listByMap | 根据条件从数据库中查询对应的记录 |
getOne | 根据条件从数据库中查询一条记录 |
getMap | 根据条件从数据库中查询一条记录,并返回Map形式的结果 |
getObj | 根据条件从数据库中查询一条记录,并通过自定义函数进行映射转换 |
count | 计算数据库中符合条件的记录数量 |
list | 根据条件从数据库中查询多条记录 |
page | 根据条件从数据库中分页查询记录 |
listMaps | 根据条件从数据库中查询多条记录,并以Map形式返回结果 |
listObjs | 根据条件从数据库中查询多条记录,并返回Object类型的结果列表 |
pageMaps | 根据条件从数据库中分页查询记录,并以Map形式返回结果 |
getBaseMapper | 获取基础的Mapper接口,用于执行底层的数据库操作 |
getEntityClass | 获取实体类的Class对象 |
query | 创建一个QueryChainWrapper对象,用于构建查询条件 |
lambdaQuery | 创建一个LambdaQueryChainWrapper对象,用于构建Lambda表达式查询条件 |
ktQuery | 创建一个KtQueryChainWrapper对象,用于构建Kotlin DSL查询条件 |
ktUpdate | 创建一个KtUpdateChainWrapper对象,用于构建Kotlin DSL更新条件 |
update | 创建一个UpdateChainWrapper对象,用于构建更新条件 |
lambdaUpdate | 创建一个LambdaUpdateChainWrapper对象,用于构建Lambda表达式更新条件 |
saveOrUpdate | 如果存在则根据条件更新数据库中的记录,否则保存实体对象到数据库 |
api调试结果:
前端功能显示:
功能三: 实现获取医生的详细信息
代码如下:
// TODO:获取医生的详细信息
@RequestMapping("/getDetail")
public String getDetail(@RequestParam("id") Integer id) throws JsonProcessingException {
QueryWrapper<Doctor> wrapper = new QueryWrapper<>();
wrapper.eq("id",id);
Doctor doctorServiceById = doctorService.getOne(wrapper);
DoctorDetailSummary doctorDetailSummary =
new DoctorDetailSummary(doctorServiceById.getHostId(),
doctorServiceById.getName(),
doctorServiceById.getPhone(),
doctorServiceById.getImage(),
doctorServiceById.getMajor(),
doctorServiceById.getSchool());
return JsonTool.writeValueAsString(doctorDetailSummary);
}
这个功能需要前端在展示医生基础数据时候,还能进行查看更多详细但是不敏感的信息。
api测试结果:
前端功能实现展示:
功能四:实现预约功能
@RestController
@RequestMapping("/vaccinum/registration")
public class RegistrationController {
@Autowired
IRegistrationService registrationService;
ObjectMapper JSon_Tool = new ObjectMapper();
@RequestMapping("/insert")
public String register(Registration registration) throws JsonProcessingException {
HashMap map = new HashMap();
boolean save = registrationService.save(registration);
map.put("flag",save);
return JSon_Tool.writeValueAsString(map);
}
}
功能五:根据医生科室查询医生
// TODO:根据医院的科室查询对应医生
@RequestMapping("/SerchByMajor")
public String SerchByMajor(@RequestParam("major") String major,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "8") Integer pageSize) throws JsonProcessingException {
HashMap map = new HashMap<>();
Page<Doctor> page = new Page<>(pageNum,pageSize);
QueryWrapper<Doctor> wrapper = new QueryWrapper<>();
wrapper.eq("major",major);
IPage<Doctor> iPage = doctorService.page(page,wrapper);
List<Doctor> list = iPage.getRecords();
// 获取信息
List<DoctorSummary> doctorSummaryList = new ArrayList<>();
for (Doctor doctor:list){
doctorSummaryList.add(new DoctorSummary(doctor.getHostId(),doctor.getName(),doctor.getPhone(),doctor.getImage(),doctor.getMajor()));
}
map.put("list",doctorSummaryList);
map.put("total",iPage.getTotal());
return JsonTool.writeValueAsString(doctorSummaryList);
}
Apifox测试:
注意:
由于这里没有和前端沟通好,前端直接使用了elementUi中自带的种类查询,这样会导致一个问题,那就是后端给的数据在前端筛选,导致分页功能无法正常使用。
如图:
3条每页的数据只有两条,甚至一条或者空,这是因为,在前端访问后端并返回数据时候,这个页面是固定数据的3条每一页。而elementUi在此基础上进行筛选,三条数据中major==外科手术的只有两条,那么就保留两条。
收获与反思
收获:
本次我负责后端的接口开发,这次的模块功能开发,
1.让我了解了MybatisPlus的分页是如何实现的,与一般的MyBatis的区别之处。
2.然后就是自定义结果集(QueryWrapper)的基本使用。
3.MyBatisPlus提供的快速方法,进行一些基本的了解
反思:
- 第一,代码需要规范,与前端交接时候,前端代码应该有条理性,前端模块开发中函数设计分区块,即前端自行处理的区块,和后端交互的函数区块需要分开。并且注意在函数前加上注释。否则若只给后端前端的代码,后端只能看到一坨捆绑的线团。不知所云。
- 第二,并且需要基于事实实现对应的功能。根据数据库实际的列去实现,这里前端做的预约功能虽然看似合理,但是缺少了对应的信息填写表格。
- 第三, 后端方面的问题,第一,返回的格式类型应该以map形式传递,这样能保证传输格式的灵活性。这里一开始就仅仅传递一个链表返回,并未考虑可能有其它参数需要返回。
- 第四,如果写好了开发文档,请前端务必按照开发文档中来做,这里我后端其实还实现一个功能,根据科室进行查找对应的医生,但是前端中使用组件的方式,
- 最后,在前后端的开发中一定一定要保持交流,任何的改动都需要跟对方谈好,否则会给双方带来极大的困扰!!!