下面我将详细介绍几种主流且高效的方法,并给出代码示例。

(图片来源网络,侵删)
场景设定
假设我们有一个文章列表,需要根据 category_id(分类ID)和 keyword(搜索关键词)进行筛选,并支持按 order 字段排序。
我们的 URL 目标可能是这样的:
http://your-app.com/article/index?page=2&category_id=5&keyword=ThinkPHP&order=hits
使用 paginate() 方法(推荐,ThinkPHP 5.1+)
这是最现代、最推荐的方法。paginate() 方法本身就可以接受一个数组参数,用于在生成分页时附加额外的查询条件。
控制器代码
在控制器中,你首先获取所有请求参数,然后将它们传递给 paginate() 方法。

(图片来源网络,侵删)
<?php
namespace app\controller;
use app\BaseController;
use app\model\Article; // 假设你有一个 Article 模型
class Article extends BaseController
{
public function index()
{
// 1. 获取所有请求参数,包括 get, post 等
$params = $this->request->param();
// 2. 定义查询条件(从参数中提取,并进行安全过滤)
$where = [];
if (!empty($params['category_id'])) {
$where[] = ['category_id', '=', intval($params['category_id'])];
}
if (!empty($params['keyword'])) {
$where[] = ['title', 'like', '%' . trim($params['keyword']) . '%'];
}
// 3. 定义排序规则
$order = !empty($params['order']) ? $params['order'] : 'id desc';
// 4. 使用 paginate 进行分页查询
// 第一个参数是每页显示条数,第二个参数是 'page',第三个参数是 where 条件
// 注意:从 ThinkPHP 6.0 开始,paginate 的参数略有调整,但核心思想一致
$list = Article::where($where)
->order($order)
->paginate([
'list_rows' => 10, // 每页10条
'page' => $this->request->param('page', 1), // 获取页码,默认为1
'path' => '', // 路径信息,留空自动获取
'query' => $params, // 关键!将所有参数附加到分页链接的查询字符串中
]);
// 5. 将查询条件和参数传递给视图,以便在表单中保持状态
$this->assign([
'list' => $list,
'params' => $params, // 传递所有参数
'category_id' => $params['category_id'] ?? null,
'keyword' => $params['keyword'] ?? null,
'order' => $params['order'] ?? 'id desc',
]);
return view();
}
}
代码解析:
$this->request->param(): 获取所有请求参数,非常方便。query => $params: 这是 核心。paginate方法的query参数接收一个数组,这些键值对会自动被附加到分页链接的 URL 后面。category_id=5和keyword=ThinkPHP就会自动加到page=2后面。- 将
$params或单独的变量传递给视图,是为了在页面的搜索表单中能回显用户之前输入的值,提升用户体验。
视图代码
在视图中,使用 {$list->render()} 来渲染分页链接。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">文章列表</title>
</head>
<body>
<!-- 搜索表单 -->
<form action="{:url('article/index')}" method="get">
<input type="text" name="keyword" value="{$keyword|default=''}" placeholder="输入关键词搜索">
<select name="category_id">
<option value="">全部分类</option>
<!-- 这里应该是动态加载的分类列表 -->
<option value="5" {$category_id == 5 ? 'selected' : ''}>技术</option>
</select>
<select name="order">
<option value="id desc" {$order == 'id desc' ? 'selected' : ''}>默认排序</option>
<option value="hits desc" {$order == 'hits desc' ? 'selected' : ''}>按点击量</option>
</select>
<button type="submit">搜索</button>
</form>
<hr>
<!-- 文章列表 -->
{volist name="list" id="item"}
<div>
<h3>{$item.title}</h3>
<p>{$item.content}</p>
</div>
{/volist}
<!-- 分页链接 -->
<div class="pagination">
{$list->render()}
</div>
</body>
</html>
当用户点击第二页时,{$list->render()} 生成的 HTML 链接会自动包含 category_id 和 keyword 等参数。
使用 buildPageUrl() 自定义分页链接(适用于 page 方法)
如果你使用的是传统的 page 方法(Article::where($where)->page($page, 10)->select()),它默认不提供 query 参数,这时,你可以通过自定义分页类来修改链接。

(图片来源网络,侵删)
创建自定义分页类
在 app\common 目录下创建 Page.php 文件。
<?php
// app\common\Page.php
namespace app\common;
use think\Paginator;
class Page extends Paginator
{
/**
* 附加的查询参数
* @var array
*/
protected $params = [];
/**
* 构造函数
* @param array $items
* @param int $perPage
* @param int $currentPage
* @param int $total
* @param array $options
*/
public function __construct(array $items, int $perPage, int $currentPage = 1, int $total = null, array $options = [])
{
parent::__construct($items, $perPage, $currentPage, $total, $options);
// 从 options 中获取自定义参数
$this->params = $options['params'] ?? [];
}
/**
* 生成页码按钮
* @param int $page
* @return string
*/
protected function appends($page)
{
// 合并自定义参数
$appends = $this->params;
$appends['page'] = $page;
return $this->buildUrl($appends);
}
/**
* 构建URL
* @param array $params
* @return string
*/
protected function buildUrl(array $params = [])
{
if (empty($params)) {
return $this->path;
}
$query = http_build_query($params);
if ($this->fragment) {
$this->path .= '#' . $this->fragment;
}
return $this->path . '?' . $query;
}
}
在控制器中使用
// 在控制器中
public function index()
{
$params = $this->request->param();
// ... (where 条件和 order 排序的代码与方法一相同)
// 使用 page 方法
$page = $this->request->param('page', 1);
$list = Article::where($where)->order($order)->page($page, 10)->select();
// 获取总记录数
$total = Article::where($where)->count();
// 使用自定义分页类
$pageObj = \app\common\Page::make($list, 10, $page, $total, [
'path' => $this->request->baseUrl(), // 当前URL路径
'params' => $params, // 传递自定义参数
]);
$this->assign([
'list' => $pageObj,
// ... 其他变量
]);
return view();
}
视图代码
视图中的分页渲染部分与方法一完全相同:
{$list->render()}
手动构建分页链接(不推荐,仅作了解)
这种方法最原始,不推荐在生产环境中使用,因为它维护成本高,容易出错,基本思路是:不使用 render(),而是用 for 循环手动生成所有页码链接。
<div class="pagination">
{for $i=1;$i<=$list->lastPage();$i++}
<a href="{:url('article/index', ['page'=>$i, 'category_id'=>$category_id, 'keyword'=>$keyword])}">{$i}</a>
{/for}
</div>
缺点:
- 代码冗长。
- 如果参数很多,URL 构建会很麻烦。
- 无法自动处理“上一页”、“下一页”、“首页”、“末页”等复杂逻辑。
总结与最佳实践
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
paginate() + query |
代码简洁,官方推荐,功能强大,自动处理所有逻辑 | 对 paginate 方法有依赖 |
强烈推荐,适用于所有 ThinkPHP 5.1+ 版本的项目 |
| 自定义分页类 | 灵活性高,可深度定制 | 需要额外创建文件,代码量稍多 | 仍在使用 page() 方法,或需要对分页进行高度定制化时 |
| 手动构建 | 无依赖,逻辑最直观 | 维护困难,代码冗余,功能不完善 | 学习理解分页原理,或极简单、不变化的分页场景 |
核心要点:
- 首选
paginate()方法,它是最优雅的解决方案。 - 核心在于将所有需要传递的参数数组赋值给
paginate()的query键。 - 在视图中,将参数传递给模板,以便在搜索表单等地方保持用户输入的状态,提升用户体验。
- 使用
{$list->render()}来渲染分页导航,它会自动处理query参数。
