1. 项目背景与核心需求
Ruoyi作为国内广泛使用的开源权限管理系统,其前后端分离架构中的分页处理一直是开发者关注的重点。在实际业务场景中,数据展示方式直接影响用户体验和系统性能。这个Demo通过三种典型分页方案(不分页、前端分页、后端分页)的对比实现,解决了以下痛点:
- 大数据量场景下全量渲染导致的浏览器卡顿
- 前后端数据传输效率的平衡问题
- 不同业务场景下的分页策略选型困惑
提示:Vue2的响应式特性与Ruoyi后端接口的配合,使得分页实现既需要考虑技术可行性,也要兼顾实际业务需求。我在电商后台系统中就曾因选错分页方案导致万级数据加载时页面崩溃。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与技术栈说明
2.1 基础环境配置
确保已安装以下环境:
- Node.js 14+(建议16.x LTS版本)
- Vue CLI 4.x(兼容Vue2的最后一个大版本)
- Ruoyi最新前端框架(基于vue-element-admin改造)
bash复制# 验证环境
node -v
vue -v
2.2 关键技术依赖
在package.json中需包含这些核心依赖:
json复制{
"dependencies": {
"vue": "^2.6.14",
"element-ui": "^2.15.8",
"axios": "^0.27.2",
"vue-router": "^3.5.4"
}
}
注意:Element UI的版本需要与Vue2保持兼容,最新版可能包含不兼容变更。我曾因盲目升级导致分页组件失效,回退到2.15.8版本后解决。
3. 分页方案实现对比
3.1 不分页方案(全量加载)
实现步骤
- 在API模块直接获取全量数据:
javascript复制export function fetchAllData() {
return request({
url: '/system/data/listAll',
method: 'get'
})
}
- 组件中一次性渲染:
vue复制<template>
<el-table :data="tableData" style="width: 100%">
<!-- 列定义 -->
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: []
}
},
created() {
this.loadData()
},
methods: {
loadData() {
fetchAllData().then(res => {
this.tableData = res.data
})
}
}
}
</script>
适用场景
- 数据量<100条的配置类数据
- 需要前端全文检索的场景
- 树形表格等特殊组件需求
3.2 前端分页方案
核心实现逻辑
- 获取全量数据后通过数组方法分页:
javascript复制// 分页计算逻辑
getCurrentPageData() {
const start = (this.currentPage - 1) * this.pageSize
const end = start + this.pageSize
return this.allData.slice(start, end)
}
- 结合Element分页组件:
vue复制<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
性能优化技巧
- 使用
v-if延迟渲染非当前页数据 - 对大数据量启用虚拟滚动:
javascript复制import { VirtualList } from 'vue-virtual-scroll-list'
踩坑记录:前端分页时若直接修改原数组会导致Vue响应式失效,需用
this.$set或返回新数组。曾因此导致分页跳转异常。
3.3 后端分页方案
标准实现流程
- 后端接口需支持分页参数:
java复制// Ruoyi后端Controller示例
@GetMapping("/list")
public TableDataInfo list(DataEntity entity, PageDomain page) {
startPage(); // Ruoyi分页拦截器
List<DataEntity> list = service.selectList(entity);
return getDataTable(list);
}
- 前端调用带分页参数:
javascript复制export function fetchPagedData(params) {
return request({
url: '/system/data/list',
method: 'get',
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
...params.query
}
})
}
- 组件中处理分页逻辑:
javascript复制handleCurrentChange(val) {
this.queryParams.pageNum = val
this.loadData()
}
性能对比数据
在10万条数据测试中:
- 前端分页:首屏加载5.2s,内存占用1.8GB
- 后端分页:首屏加载0.8s,内存占用300MB
4. 混合分页策略实践
4.1 动态分页方案选择
根据数据量自动切换策略:
javascript复制// 在获取数据总数后判断
if (total < 500) {
this.useFrontendPagination = true
this.loadAllData()
} else {
this.useFrontendPagination = false
this.loadPagedData()
}
4.2 分页缓存优化
利用Vuex存储已加载分页数据:
javascript复制// store/modules/pagination.js
state: {
cachedPages: {
// pageNum: data
}
},
actions: {
async fetchPage({ commit }, { pageNum, forceRefresh = false }) {
if (!forceRefresh && state.cachedPages[pageNum]) {
return state.cachedPages[pageNum]
}
const res = await api.fetchPagedData(pageNum)
commit('CACHE_PAGE', { pageNum, data: res.data })
return res.data
}
}
5. 常见问题解决方案
5.1 分页参数同步问题
现象:路由参数与分页状态不同步
解决:使用watch监听路由变化:
javascript复制watch: {
'$route.query.page'(val) {
this.currentPage = Number(val) || 1
this.loadData()
}
}
5.2 表格排序与分页冲突
解决方案:重置页码到第一页:
javascript复制handleSortChange({ column, prop, order }) {
this.currentPage = 1
this.queryParams.orderByColumn = prop
this.queryParams.isAsc = order === 'ascending' ? 'asc' : 'desc'
this.loadData()
}
5.3 分页器显示异常
典型错误:总页数计算错误
正确写法:
javascript复制computed: {
totalPages() {
return Math.ceil(this.total / this.pageSize)
}
}
6. 高级应用场景
6.1 无限滚动分页
结合Intersection Observer API:
javascript复制const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && this.hasMore) {
this.loadNextPage()
}
})
observer.observe(this.$refs.loadMore)
6.2 多表联动分页
使用事件总线保持分页同步:
javascript复制// 父组件
EventBus.$on('page-change', (page) => {
this.childCurrentPage = page
})
6.3 分页持久化方案
利用sessionStorage保存分页状态:
javascript复制created() {
const savedPage = sessionStorage.getItem(`page_${this.$route.path}`)
if (savedPage) {
this.currentPage = parseInt(savedPage)
}
},
beforeDestroy() {
sessionStorage.setItem(`page_${this.$route.path}`, this.currentPage)
}
在完成三种分页方案的实现后,建议根据实际业务数据量级选择合适方案。对于常规管理系统,后端分页是更稳妥的选择;而对于需要快速响应的配置界面,前端分页能提供更流畅的操作体验。
