paginate()默认不支持每页数量切换,因其将$listrows固化为固定参数且不保留可变上下文;需控制器从请求安全提取并校验白名单值(如[10,15,20,30,50]),再传入paginate(),同时模板下拉框须用array_merge保留全部get参数生成url。

为什么 paginate() 默认不提供每页数量切换
ThinkPHP 的 paginate() 方法本质是封装了数据库分页查询 + 分页 HTML 渲染,它把 $listRows(每页条数)当作固定参数传入,生成的分页对象里不保留“可变页大小”的上下文。换句话说:分页器初始化后,$page 和 $listRows 都已固化,后续无法动态重置 $listRows 而不重查数据。
所以直接在模板里加个下拉框改 $_GET['list_rows'] 是没用的——控制器没读这个参数,模型也没重新调用 paginate(15) 或 paginate(30)。
如何让控制器支持动态 $listRows 参数
核心是:从请求中安全提取用户想设的每页数量,并传给 paginate()。不能硬写死,也不能无校验接收任意值。
- 在控制器方法中,用
input('list_rows/d', 15)获取整型参数,默认 15 - 必须做白名单校验,比如只允许
[10, 15, 20, 30, 50],避免传入list_rows=999999导致 SQL 查询超时或内存溢出 - 将校验后的值传给
paginate(),例如:$list->paginate(['list_rows' => $listRows, 'page' => input('page/d', 1)]) - 注意:要同时保留原始
page参数,否则切换每页数量后会跳回第 1 页(除非你故意要这样)
模板里怎么渲染“每页显示 X 条”下拉框
分页器对象本身不提供页大小选项,得手动生成一个带当前值的 <select></select>,并确保所有选项都携带当前搜索条件(如关键词、分类 ID 等),否则切换后会丢参数。
示例(ThinkPHP 模板语法):
<select onchange="location.href=this.value">
{volist name="pageSizes" id="size"}
<option value="{:url('index', array_merge(input(), ['list_rows' => $size]))}" name="$listRows">
{$size} 条/页
</option>
{/volist}
</select>
关键点:
-
array_merge(input(), [...])确保 URL 保留原有 GET 参数(如?keyword=abc&cid=2) -
url('index', ...)生成的是当前控制器/方法的完整链接,不是硬编码路径 - 不要用 AJAX 提交切换——这会让分页器的
render()输出失效,且翻页时页码逻辑容易错乱
分页 HTML 渲染时如何同步显示当前 $listRows
默认 $pages->render() 只输出页码,不体现每页数量。要在分页栏右侧加一句“共 {$count} 条,每页 {$listRows} 条”,但注意:$count 是总记录数,需提前查出($count = $query->count()),不能依赖分页对象的 total(),因为后者可能被缓存或截断。
更稳妥的做法是:在控制器中显式获取总数,并与 $listRows 一起 assign 到模板:
$count = $model->where($where)->count();
$list = $model->where($where)->paginate(['list_rows' => $listRows]);
$this->assign('count', $count);
$this->assign('listRows', $listRows);
然后模板里直接用 {$count} 和 {$listRows},比从分页对象里反推更可靠。
真正麻烦的不是加下拉框,而是所有链接都要带全当前参数;漏掉一个 category_id,切换页大小后就变成查全部分类了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











