前端技术栈:Vue3 + TypeScript + Element Plus + el-pagination
后端技术栈:Java + Spring Boot + Mybatis
应用异常情况说明:点击页码2,会发送两次请求,并且自动跳回页码1
代码: Reagent.vue
<script setup lang="ts" name="Reagent">
import { reagentQueryService } from "@/api/reagent";
import BasePreventReClickButton from "@/components/base/BasePreventReClickButton.vue";
import type { IPageListResponse } from "@/interface";
import { Search } from "@element-plus/icons-vue";
import { ref } from "vue";
import type { IReagent, IReagentQueryObj } from "./reagent/types";// 试剂分页列表
const reagentPageList = ref<IReagent[]>([]);
// 查询总数
const total = ref(0);
// 加载标识
const loading = ref(false);
// 查询对象
const queryObj = ref<IReagentQueryObj>({pageHelper: {page: 1,size: 20},reagentCategory: "",reagentNo: "",reagentName: ""
});
// 分页页数及显示数量变动监听标识,默认不监听
const onPageOrSizeChangeValid = ref(false);// 查询
const onQueryClick = async () => {try {loading.value = true;// 把列表数据清空,点击查询时,视觉上感知数据正在变化reagentPageList.value = [];total.value = 0;let result = <IPageListResponse<IReagent>>(await reagentQueryService(queryObj.value)).data;reagentPageList.value = result.rows;total.value = result.total;} catch (error) {} finally {loading.value = false;onPageOrSizeChangeValid.value = true;}
};// 改变页码、显示数量,重新获取数据
const onPageOrSizeChange = async (currentPage: number, pageSize: number) => {if (!onPageOrSizeChangeValid.value) {return;}queryObj.value.pageHelper.page = currentPage;queryObj.value.pageHelper.size = pageSize;// 获取数据await onQueryClick();
};
</script><template><el-container class="container"><el-header class="header"><!-- 标题 --><div class="header-title">试剂耗材管理</div><!-- 操作栏 --><div class="header-operation"><div><el-button class="header-btn" type="primary" plain> 入库 </el-button><el-button class="header-btn" type="primary" plain> 申领 </el-button><el-button class="header-btn" type="primary" plain> 出库 </el-button><el-button class="header-btn" type="primary" plain> 明细查询 </el-button><el-button class="header-btn" type="primary" plain> 刷新数据 </el-button></div><div class="query-div"><el-input v-model="queryObj.reagentName" placeholder="请输入试剂关键字进行查询" clearable><template #prefix><el-icon><Search /></el-icon></template></el-input><BasePreventReClickButton class="query-btn" type="primary" plain :onClick="onQueryClick" :delay="100">查询</BasePreventReClickButton></div></div></el-header><el-main class="main"><!-- 展示区 --><el-table:data="reagentPageList"v-loading="loading"highlight-current-rowstripestyle="width: 100%; height: 100%"><el-table-columnprop="reagentName"label="名称 规格"min-width="200"fixed="left"header-align="left"sortableshow-overflow-tooltip><template #default="scope"><span class="material-name">{{ scope.row.reagentName }}</span><span class="material-spec">{{ scope.row.reagentSpec }}</span></template></el-table-column><el-table-column prop="batchNo" label="批号" width="120" header-align="left" show-overflow-tooltip /><el-table-columnprop="validityDate"label="有效期至"width="110"header-align="center"align="center"show-overflow-tooltip /><el-table-column prop="amount" width="80" header-align="center" align="center" show-overflow-tooltip><template #header><div class="custom-table-column-header-amount-unit-div">库存数量</div></template><template #default="scope"><div class="custom-table-row-default-amount-unit-div"><span>{{ scope.row.amount }}</span><span>{{ scope.row.reagentUnit }}</span></div></template></el-table-column></el-table></el-main><el-footer class="footer"><!-- 分页 --><el-pagination:total="total":page-sizes="[20, 50, 100, 200, 500]"v-model:page-size="queryObj.pageHelper.size"v-model:current-page="queryObj.pageHelper.page"backgroundlayout="total, sizes, prev, pager, next, jumper":small="false"@change="onPageOrSizeChange" /></el-footer></el-container>
</template><style scoped lang="scss">
// 选择 container 所有直接子元素(不包括孙级)
.container > * {margin: 0;padding: 0;
}
.container {height: 100%;border: 1px solid #ebeef5;.header {height: auto;.header-title {margin: 10px 10px 20px 10px;font-size: 18px;}.header-operation {margin: 10px;display: flex;justify-content: space-between;.query-div {display: flex;justify-content: flex-end;// 设置固定宽度,避免由于宽度自适应,导致页面跳动width: 300px;// 因为点击查询按钮时,会显示加载动画,撑大了原来的按钮宽度,导致页面跳动,所以设置宽度为100px,预留足够的空间,避免页面跳动.query-btn {width: 100px;}}}}.footer {height: auto;padding: 0 10px;}
}
.table-btn {margin: 0;padding: 0 5px;
}
.custom-table-column-header-amount-unit-div {text-align: justify;text-align-last: justify;
}
.custom-table-row-default-amount-unit-div {display: flex;justify-content: space-between;
}
</style>
问题分析:
都是 total.value = 0; 引起的
因为分页组件 total 变化,会触发分页组件重新计算页数(total / page-size),并且重置当前页数 current-page 为 1,page 变化,又会触发 onPageOrSizeChange 事件
最终修改后的完整代码: Reagent.vue
<script setup lang="ts" name="Reagent">
import { reagentQueryService } from "@/api/reagent";
import BasePreventReClickButton from "@/components/base/BasePreventReClickButton.vue";
import type { IPageListResponse } from "@/interface";
import { Search } from "@element-plus/icons-vue";
import { ref } from "vue";
import type { IReagent, IReagentQueryObj } from "./reagent/types";// 试剂分页列表
const reagentPageList = ref<IReagent[]>([]);
// 查询总数
const total = ref(0);
// 加载标识
const loading = ref(false);
// 查询对象
const queryObj = ref<IReagentQueryObj>({pageHelper: {page: 1,size: 20},reagentCategory: "",reagentNo: "",reagentName: ""
});
// 分页页数及显示数量变动监听标识,默认不监听
const onPageOrSizeChangeValid = ref(false);// 查询
const onQueryClick = async () => {try {loading.value = true;// 把列表数据清空,点击查询时,视觉上感知数据正在变化reagentPageList.value = [];// 这个位置(发送查询请求前),不能 total.value = 0; 改变 total.value 的值// 因为分页组件 total 变化,会触发分页组件重新计算页数(total / page-size),并且重置当前页数 current-page 为 1// page 变化,又会触发 onPageOrSizeChange 事件// total.value = 0;let result = <IPageListResponse<IReagent>>(await reagentQueryService(queryObj.value)).data;reagentPageList.value = result.rows;total.value = result.total;} catch (error) {// 出错了,在这里设置查询总数为 0total.value = 0;} finally {loading.value = false;onPageOrSizeChangeValid.value = true;}
};// 改变页码、显示数量,重新获取数据
const onPageOrSizeChange = async (currentPage: number, pageSize: number) => {if (!onPageOrSizeChangeValid.value) {return;}queryObj.value.pageHelper.page = currentPage;queryObj.value.pageHelper.size = pageSize;// 获取数据await onQueryClick();
};
</script><template><el-container class="container"><el-header class="header"><!-- 标题 --><div class="header-title">试剂耗材管理</div><!-- 操作栏 --><div class="header-operation"><div><el-button class="header-btn" type="primary" plain> 入库 </el-button><el-button class="header-btn" type="primary" plain> 申领 </el-button><el-button class="header-btn" type="primary" plain> 出库 </el-button><el-button class="header-btn" type="primary" plain> 明细查询 </el-button><el-button class="header-btn" type="primary" plain> 刷新数据 </el-button></div><div class="query-div"><el-input v-model="queryObj.reagentName" placeholder="请输入试剂关键字进行查询" clearable><template #prefix><el-icon><Search /></el-icon></template></el-input><BasePreventReClickButton class="query-btn" type="primary" plain :onClick="onQueryClick" :delay="100">查询</BasePreventReClickButton></div></div></el-header><el-main class="main"><!-- 展示区 --><el-table:data="reagentPageList"v-loading="loading"highlight-current-rowstripestyle="width: 100%; height: 100%"><el-table-columnprop="reagentName"label="名称 规格"min-width="200"fixed="left"header-align="left"sortableshow-overflow-tooltip><template #default="scope"><span class="material-name">{{ scope.row.reagentName }}</span><span class="material-spec">{{ scope.row.reagentSpec }}</span></template></el-table-column><el-table-column prop="batchNo" label="批号" width="120" header-align="left" show-overflow-tooltip /><el-table-columnprop="validityDate"label="有效期至"width="110"header-align="center"align="center"show-overflow-tooltip /><el-table-column prop="amount" width="80" header-align="center" align="center" show-overflow-tooltip><template #header><div class="custom-table-column-header-amount-unit-div">库存数量</div></template><template #default="scope"><div class="custom-table-row-default-amount-unit-div"><span>{{ scope.row.amount }}</span><span>{{ scope.row.reagentUnit }}</span></div></template></el-table-column></el-table></el-main><el-footer class="footer"><!-- 分页 --><el-pagination:total="total":page-sizes="[20, 50, 100, 200, 500]"v-model:page-size="queryObj.pageHelper.size"v-model:current-page="queryObj.pageHelper.page"backgroundlayout="total, sizes, prev, pager, next, jumper":small="false"@change="onPageOrSizeChange" /></el-footer></el-container>
</template><style scoped lang="scss">
// 选择 container 所有直接子元素(不包括孙级)
.container > * {margin: 0;padding: 0;
}
.container {height: 100%;border: 1px solid #ebeef5;.header {height: auto;.header-title {margin: 10px 10px 20px 10px;font-size: 18px;}.header-operation {margin: 10px;display: flex;justify-content: space-between;.query-div {display: flex;justify-content: flex-end;// 设置固定宽度,避免由于宽度自适应,导致页面跳动width: 300px;// 因为点击查询按钮时,会显示加载动画,撑大了原来的按钮宽度,导致页面跳动,所以设置宽度为100px,预留足够的空间,避免页面跳动.query-btn {width: 100px;}}}}.footer {height: auto;padding: 0 10px;}
}
.table-btn {margin: 0;padding: 0 5px;
}
.custom-table-column-header-amount-unit-div {text-align: justify;text-align-last: justify;
}
.custom-table-row-default-amount-unit-div {display: flex;justify-content: space-between;
}
</style>
后端分页相关代码:ReagentServiceImpl.java
// 查询试剂列表@Override@Transactional(readOnly = true) // 优化性能,避免写锁public PageBean<Reagent> query(ReagentQueryDTO queryDTO) {PageHelper.startPage(queryDTO.getPageHelper().getPage(), queryDTO.getPageHelper().getSize());List<Reagent> reagentList = reagentMapper.select(queryDTO, PublicUtils.getUserName());Page<Reagent> reagentPage = (Page<Reagent>) reagentList;return new PageBean<>(reagentPage.getTotal(), reagentPage.getResult());}