手动分页需自行完成count、计算偏移量、limit查询、渲染四步,不可调用paginate();须手动透传URL参数并手写页码HTML,返回数据为数组或Collection,无render()等Paginator方法。

paginate() 不能用,就别硬套
ThinkPHP 的 paginate() 是封装好的「COUNT + LIMIT」双查询流程,一旦你明确要手动分页(比如已有复杂子查询、需复用已有 SQL、或想彻底控制分页逻辑),就别再试图在 select() 后补 paginate()——那会直接报 Call to undefined method think\Collection::paginate()。手动分页的本质是:自己算总数、自己算偏移量、自己拼 LIMIT、自己构造分页参数和 URL。
手动分页四步:count → 计算 → limit → 渲染
核心不是写得多,而是每步不漏掉关键点:
- 先用原始查询条件单独执行一次
count(),例如User::where($where)->count(),注意别带order或limit,否则总数不准 - 读取当前页码:
$page = input('page/d', 1);每页条数建议固定(如 15),若需动态,必须显式接收:$size = input('size/d', 15) - 计算偏移量:
$offset = ($page - 1) * $size;然后用limit($offset, $size)执行主查询(模型或Db::table()均可) - 把
$page、$size、$count、$totalPage = ceil($count / $size)全部 assign 给模板,前端靠这些变量手写页码 HTML
URL 参数丢失?手动透传 query 才可靠
翻页时搜索关键词、分类 ID 等 GET 参数消失,是因为手动分页没自动继承请求上下文。别依赖框架默认行为:
- 生成页码链接时,必须手动拼接所有需要保留的参数,例如:
url('index/list', ['page' => $i, 'keyword' => input('keyword'), 'type' => input('type')]) - 更安全的做法是先过滤键名:
$query = array_filter(request()->param(), function($k) { return in_array($k, ['keyword', 'type', 'status']); }, ARRAY_FILTER_USE_KEY);,再传入 URL 构造 - 避免直接透传
request()->param(),防止token、_csrf等敏感参数泄露到分页链接里
模板里怎么输出分页 HTML?别调 render()
$list->render() 是 paginate() 返回对象才有的方法,手动分页返回的是纯数组或 Collection,没有该方法。你得自己组织结构:
立即学习“PHP免费学习笔记(深入)”;
- 用
{$page}显示当前页,{$totalPage}显示总页数,{$count}显示总条数 - 循环页码时,用
{volist name=":range(1, $totalPage)" id="i"},再判断{eq name="i" value="$page"}class="active"{/eq} - 上一页链接:
{if condition="$page gt 1"}<a href="https://www.php.cn/link/d213dab3531f5546bd40cc3960c58e00'index/list',%20['page'=>%24page-1]%20+%20%24query)%7D">上一页</a>{/if} - 下一页同理,注意
$page lt $totalPage判断
最容易被忽略的是:手动分页后,$list 是普通数组,不能再调 items() 或 currentPage() ——那些方法只存在于 think\Paginator 实例中。一但混淆类型,后续扩展(比如加简单分页 simple())就完全不可行。



















