分页接口返回空数据是因为后端 paginate() 被提前执行或解包,必须作用于未执行的查询构造器,避免 select()/all()/toarray() 等触发执行,且需显式传入 page/size 参数并正确返回 paginator 实例及元信息。

分页接口返回空数据,不是前端没传参,而是后端 paginate() 被提前“执行”或“解包”了——最常见的是调用了 select()、all() 或 toArray(),导致 Paginator 对象丢失。
paginate() 必须作用于未执行的查询构造器
ThinkPHP 的 paginate() 不是数组处理函数,它需要控制 SQL 的 COUNT(*) 和 LIMIT 生成。一旦查询被提前执行,分页就退化为 PHP 数组切片,总数和页码全错。
- ❌
UserModel::where('status', 1)->select()->paginate(10):select() 已执行 SQL,paginate()只是对数组做假分页 - ❌
$list = UserModel::all(); $list->paginate(10):Collection 没有paginate()方法,直接报错 - ✅
$list = UserModel::where('status', 1)->paginate(15):保持查询未执行,paginate()才能注入分页逻辑
前后端分离时必须手动传入 page/size 参数
默认情况下 paginate() 只读 $_GET['page'],不识别 size、p 等自定义参数名,也不从 POST 或 JSON body 中取值。
- 用
input('page/d', 1)和input('size/d', 15)显式获取参数 - 传数组配置:
UserModel::paginate(['listRows' => input('size/d', 15), 'page' => input('page/d', 1), 'var_page' => 'page']) - 若用 POST 提交搜索条件,翻页时 URL 仍是 GET,筛选条件会丢失 → 前端必须把所有条件拼进 query string,或改用带全参数的 AJAX 请求
返回前千万别调用 toArray()、all() 或 foreach
paginate() 返回的是 Paginator 实例,含 items()、total()、render() 等方法。一旦遍历或转数组,对象就被“解包”成纯 PHP 数组,所有分页元信息消失。
- ❌
return json($list->toArray()):只剩data,无current_page、last_page等字段 - ✅ 正确构造响应:
return json(['data' => $list->items(), 'meta' => ['current_page' => $list->currentPage(), 'last_page' => $list->lastPage(), 'per_page' => $list->listRows(), 'total' => $list->total()]]) - 模板中渲染页码也要用
{$list->render|raw},不能先foreach再渲染
关联查询或 field() 导致 total() 为 0 怎么办
用 with('profile') 或 join() 后 $list->total() 返回 0,通常是因为 COUNT SQL 构建失败,框架自动降级为简单分页(simple => true),不再计算总数。
- 避免在
paginate()前调用field(),尤其不要只选关联表字段,主表关键字段(如主键)必须保留 - 预载入复杂时,改用
load()懒加载单条,不干扰分页主查询 - 实在无法修复,可接受无总数:
UserModel::paginate(['simple' => true, 'listRows' => 15]),此时lastPage()不可用,前端需用hasMore判断
真正容易被忽略的点是:哪怕你写对了 paginate(),只要中间加了一行 var_dump($list); 或 print_r($list),PHP 就会触发 __debugInfo(),间接调用 toArray() —— 分页对象当场失效。调试时务必用 get_class($list) 或 method_exists($list, 'total') 直接验类型。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











