📌 前言:在企业级后台管理系统中,数据分页是刚需中的刚需!当用户数据达到上千、上万条时,一次性加载所有数据会导致页面卡顿、网络拥堵、数据库压力暴增。今天就以 RuoYi-Vue(若依)框架为例,从前端到后端、从请求到渲染,手把手拆解用户管理模块分页功能的完整实现,新手也能轻松看懂~

💡 本文核心收获:掌握前后端分离分页的完整数据流、Vue分页组件配置、PageHelper插件原理、若依代码生成器模板用法,看完直接能套用在自己的项目/作业中!

一、分页整体流程概述(先搞懂宏观逻辑)

很多新手做分页容易陷入“只看前端不看后端”“只写SQL不看插件”的误区,其实若依的分页是一个完整闭环,先记住这个流程图,后续拆解会更清晰:

🔄 完整链路:前端Vue页面(分页参数+组件)→ Axios请求(参数传递)→ 后端Controller(接收+开启分页)→ PageHelper插件(SQL改写)→ 数据库查询 → 后端封装结果 → 前端渲染(表格+分页条)

用通俗的话讲:前端告诉后端“我要第1页,每页10条数据”,后端接到请求后,自动处理分页逻辑,查完数据再告诉前端“总共有100条,这是第1页的10条”,前端再把数据展示出来。

二、前端代码剖析(分页的发起者

前端核心是「传递分页参数、渲染分页组件、接收后端数据」,所有代码都在用户管理页面(index.vue)和相关工具类中,对应截图位置已标注,直接对照看即可。

2.1 Vue页面分页组件(最直观的分页入口)

核心代码(分页组件绑定):

html
<pagination v-show="total > 0"
            :total="total"
            :page.sync="queryParams.pageNum"
            :limit.sync="queryParams.pageSize"
            @pagination="getList"/>

✨ 逐行解读:

  • v-show="total > 0":没有数据时不显示分页条,避免空页面尴尬
  • :total="total":绑定后端返回的总记录数,分页组件据此计算总页数
  • :page.sync="queryParams.pageNum":双向绑定当前页码,用户点击翻页时,自动更新pageNum
  • :limit.sync="queryParams.pageSize":双向绑定每页条数,默认10条/页,可手动修改
  • @pagination="getList":翻页、改每页条数时,自动调用getList()方法重新加载数据

2.2 分页参数定义(前端告诉后端怎么查

核心代码(queryParams对象):

javascript
queryParams: {
    pageNum: 1,           //
核心:当前页码,初始第1页
    pageSize: 10,         // 核心:每页条数,默认10条
    userName: undefined,   // 辅助:用户名模糊搜索(非分页必需)
    phonenumber: undefined,// 辅助:手机号搜索(非分页必需)
    status: undefined,    // 辅助:用户状态筛选(非分页必需)
    deptId: undefined     // 辅助:部门筛选(非分页必需)
},
total: 0,                // 核心:总记录数,后端返回后赋值
userList: null,          // 核心:存储当前页数据,渲染表格

⚠️ 重点:pageNum和pageSize是分页的“灵魂参数”,必须和后端保持一致,否则分页会失效。

2.3 数据加载方法getList()(发起请求的核心)

核心代码(请求后端数据):

javascript
getList() {
    this.loading = true;  //
开启加载动画,提升用户体验
    // 调用API,传入分页参数+日期筛选条件
    listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
        this.userList = response.rows;  // 赋值当前页数据,渲染表格
        this.total = response.total;    // 赋值总记录数,更新分页组件
        this.loading = false;           // 关闭加载动画
    });
}

💡 关键逻辑:

1. 页面初始化时(created钩子),会自动调用getList(),加载第一页数据;

2. 调用listUser() API,将queryParams(含分页参数)传给后端;

3. 接收后端返回的response,其中rows是当前页数据,total是总记录数。

2.4 API接口封装(统一请求路径)

核心代码(user.js中封装API):

javascript
// 查询用户列表(分页请求)
export function listUser(query) {
  return request({
    url: '/system/user/list',  // 对应后端接口路径
    method: 'get',             // GET请求,参数拼在URL后
    params: query              // 传入分页参数(pageNum、pageSize等)
  })
}

✅ 说明:这里的request是Axios实例,参数query会自动拼接到URL中,最终请求地址是:http://localhost/dev-api/system/user/list?pageNum=1&pageSize=10

2.5 Axios全局配置(请求的基础设置

核心代码(request.js全局配置):

javascript
// 设置请求头格式(JSON)
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';

// 创建Axios实例
const service = axios.create({
    baseURL: process.env.VUE_APP_BASE_API,  // 环境变量,开发环境是/dev-api
    timeout: 10000                          // 超时时间10秒,防止请求卡死
});

// 请求拦截器:自动添加Token(前后端认证必备)
service.interceptors.request.use(
    config => {
        const token = getToken();
        if (token) {
            config.headers['Authorization'] = 'Bearer ' + token;
        }
        return config;
    }
);

⚠️ 注意:这个配置是所有前端请求的基础,分页请求也依赖它,不用单独修改,直接复用即可。

三、后端代码剖析(分页的处理者

后端核心是「接收前端参数、开启分页、执行SQL、封装结果」,核心依赖MyBatis的PageHelper分页插件,不用手动写LIMIT,非常便捷。

3.1 Controller层分页接口(请求入口)

核心代码(SysUserController.java):

java
/**
 *
获取用户列表(分页接口)
 */
@PreAuthorize("@ss.hasPermi('system:user:list')")  // 权限校验(非分页核心)
@GetMapping("/list")
public TableDataInfo list(SysUser user) {
    startPage();                                    // 核心:开启分页
    List<SysUser> list = userService.selectUserList(user);  // 执行查询
    return getDataTable(list);                      // 核心:封装分页结果
}

✨ 核心方法解读:

  • startPage():来自BaseController父类,作用是读取前端传递的pageNum和pageSize,告诉PageHelper插件“要分页”,自动拦截后续SQL。
  • getDataTable(list):将查询到的list(当前页数据)封装成TableDataInfo对象,该对象包含rows(数据列表)和total(总记录数),正好对应前端需要的格式。

3.2 Controller类结构(接口基础)

核心代码(类定义):

java
@RestController
@RequestMapping("/system/user")  //
接口基础路径,和前端API对应
public class SysUserController extends BaseController {
   
    @Autowired
    private ISysUserService userService;  // 注入Service层,用于调用查询方法
   
    // 其他注入和接口...
}

✅ 说明:@RequestMapping("/system/user") + @GetMapping("/list"),组成完整接口路径/system/user/list,和前端API的url完全对应。

3.3 Service层与Mapper层(执行查询)

Service层负责调用Mapper层执行SQL,本身不参与分页逻辑:

java
// Service层(SysUserServiceImpl.java)
@Override
@DataScope(deptAlias = "d", userAlias = "u")  // 数据权限(非分页核心)
public List<SysUser> selectUserList(SysUser user) {
    return userMapper.selectUserList(user);  // 调用Mapper层查询
}

Mapper层(SysUserMapper.xml)定义SQL,注意:没有手动写LIMIT

xml
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
    select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email,
           u.phonenumber, u.status, d.dept_name
    from sys_user u
    left join sys_dept d on u.dept_id = d.dept_id
    where u.del_flag = '0'
    <!--
动态筛选条件(非分页核心) -->
    <if test="userName != null and userName != ''">
        AND u.user_name like concat('%', #{userName}, '%')
    </if>
    ${params.dataScope}  // 数据权限过滤
</select>

⚠️ 关键:SQL的分页逻辑由PageHelper插件自动处理,我们只需要写“查询所有符合条件的数据”即可。

四、网络请求抓包验证(分页是否生效?看这里!)

打开浏览器开发者工具(F12),切换到Network面板,刷新用户管理页面,就能看到分页请求的详细信息:

  • 请求地址:http://localhost/dev-api/system/user/list?pageNum=1&pageSize=10
  • 请求方法:GET
  • 状态码:200 OK(请求成功)
  • 响应数据:包含code(状态码)、msg(提示)、rows(当前页数据)、total(总记录数)

响应数据示例(简化版):

json
{
  "code": 200,
  "msg": "
查询成功",
  "rows": [{"userId":1,"userName":"admin",...}],  // 当前页10条数据
  "total": 100  // 总共有100条用户数据
}

✅ 验证要点:只要能看到pageNum、pageSize参数,且响应中有rows和total,说明分页请求正常。

五、分页原理讲解(PageHelper插件到底怎么工作?)

疑问:为什么不用写LIMIT,分页就能生效?核心就是PageHelper插件,它的工作原理很简单,就是「拦截SQL、自动改写」。

5.1 PageHelper核心工作流程

  1. 后端调用startPage()方法,PageHelper会从请求中读取pageNum和pageSize,存入线程变量;
  1. 当执行Mapper层的selectUserList()方法时,PageHelper拦截原始SQL;
  1. 自动生成两条SQL:① 统计总记录数(SELECT count(0) FROM ...);② 改写原始SQL,添加LIMIT子句;
  1. 执行两条SQL,将结果封装成Page对象,total来自count查询,rows来自LIMIT查询;
  1. getDataTable()方法从Page对象中提取rows和total,返回给前端。

5.2 分页参数与SQL转换示例

假设前端请求pageNum=2、pageSize=10:

  • 计算偏移量:offset = (pageNum - 1) × pageSize = (2-1)×10 = 10;
  • 原始SQL:SELECT ... FROM sys_user WHERE del_flag = '0';
  • 改写后SQL(分页):SELECT ... FROM sys_user WHERE del_flag = '0' LIMIT 10, 10;
  • 含义:从第11条数据开始,取10条(即第2页数据)。

六、若依代码生成器模板(分页接口自动生成!)

若依框架最方便的就是代码生成器,分页接口不用手动写,模板自动生成,核心模板(controller.java.vm)如下:

velocity
#if($table.crud || $table.sub)
    /**
     *
获取${functionName}列表
     */
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')")
    @GetMapping("/list")
    public TableDataInfo list(${ClassName} ${className}) {
        startPage();  // 自动添加分页核心方法
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        return getDataTable(list);  // 自动封装分页结果
    }
#elseif($table.tree)
    // 树形结构表,不分页,直接返回全部数据
    @GetMapping("/list")
    public AjaxResult list(${ClassName} ${className}) {
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        return success(list);
    }
#end

💡 模板逻辑:

1. 普通CRUD表(如sys_user):自动生成带startPage()和getDataTable()的分页接口;

2. 树形表(如部门表):生成不分页接口,直接返回所有数据;

3. 我们的用户管理模块是普通CRUD表,所以分页接口是模板自动生成的,直接复用即可。

七、运行效果展示(分页功能最终呈现)

用户管理页面运行效果如下,完美实现分页功能:

  • 左侧:部门树形结构,可按部门筛选用户;
  • 中间:用户表格,显示当前页数据(10条/页);
  • 底部:分页组件,显示总记录数、当前页码、每页条数,支持翻页和页码跳转。

✅ 交互效果:点击“下一页”,前端自动更新pageNum=2,发起新请求,表格数据和分页条同步更新,整个过程无卡顿。

八、完整流程总结

再梳理一遍完整闭环,加深记忆:

  1. 前端初始化:created钩子调用getList(),传递pageNum=1、pageSize=10;
  1. 发起请求:Axios携带参数,调用/list接口,请求头自动添加Token;
  1. 后端接收:Controller的list()方法调用startPage(),开启分页;
  1. SQL执行:PageHelper拦截SQL,自动生成count查询和LIMIT查询;
  1. 结果返回:Service调用Mapper查询,Controller封装rows和total,返回给前端;
  1. 前端渲染:将rows赋值给userList,total赋值给分页组件,完成页面展示。

核心知识点速记

层级

核心代码

作用

前端Vue

queryParams、<pagination>组件

定义分页参数、渲染分页条

前端API

listUser(query)

封装分页请求

后端Controller

startPage()、getDataTable()

开启分页、封装结果

MyBatis

PageHelper插件

自动改写SQL,添加LIMIT

九、常见问题排查

  • 分页不生效:检查Controller是否调用startPage(),Mapper XML的SQL是否有语法错误;
  • 总记录数不对:检查是否有数据权限过滤,或WHERE条件是否正确;
  • 页码跳转异常:检查前端queryParams的pageNum是否双向绑定,@pagination事件是否绑定getList()。

结语

以上就是RuoYi-Vue框架用户管理模块分页功能的完整剖析,从前端到后端、从原理到实战,覆盖了所有核心知识点。其实分页的本质就是“分段查询、按需加载”,若依已经帮我们封装好了大部分逻辑,我们只需要理解其流程,就能轻松复用和定制。

更多推荐