

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
这篇教程专门解决这些痛点:从ThinkPHP6分页的核心逻辑讲起,一步步教你用Query类的paginate方法实现基础分页,再延伸到带条件筛选、联表查询的复杂场景,每一步都附完整可复制的代码示例,连前端分页组件的对接方式都给了参考。不管你是刚学框架的小白,还是想优化现有功能的开发者,跟着走一遍就能快速写出稳定、灵活的MySQL分页功能,不用再对着文档瞎试,直接把代码用到项目里。
做ThinkPHP6开发时,你是不是也遇到过MySQL分页的坑?比如用paginate方法查出来的总条数总不对,带搜索条件时页码直接“飞”了,或者联表查询后分页组件显示的页数和实际不符?我去年帮朋友的电商后台做商品列表分页时,就踩过这些坑——一开始直接用了Goods::paginate(10)
,结果总条数总是比实际少20条,后来查了半天才发现:我把搜索条件写在了paginate之后,导致统计总条数时没带条件。今天就把我踩过的坑、 的“笨办法”全告诉你,从基础分页到复杂场景,每一步都有可复制的代码,新手也能跟着做对。
ThinkPHP6基础分页:从核心逻辑到代码实现
先给你讲个“扎心”的事实:很多人用了很久paginate,却不知道它到底在做什么。其实paginate的核心逻辑就两件事:统计总条数(count语句)和查询当前页数据(limit+offset)。比如你要查第2页、每页10条,它会先执行SELECT COUNT() FROM goods
(总条数),再执行SELECT FROM goods LIMIT 10 OFFSET 10
(第2页数据)。懂了这个逻辑,你就能快速定位问题——比如总条数错,肯定是count的时候没带条件;数据不对,可能是limit的参数算错了。
我帮朋友调分页时,一开始的代码是这样的:
// 错误示例:条件写在paginate之后,导致count没带条件
$list = Goods::paginate(10);
$list = $list->where('status', 1); // 这里的条件根本没生效!
后来改成先加条件、再调用paginate,总条数立刻对了:
// 正确示例:条件写在paginate之前
$list = Goods::where('status', 1)->paginate(10);
是不是很简单?但这是很多新手最容易犯的错——把条件“放错了位置”。
接下来教你完整的基础分页流程,分3步:
用模型或者查询构造器都可以,我个人更习惯用模型(代码更简洁)。比如商品列表的控制器代码:
<?php namespace appcontroller;
use appmodelGoods;
use thinkfacadeRequest;
class GoodsController
{
public function index()
{
//
获取分页参数(每页条数、当前页)
$pageSize = Request::param('page_size', 10); // 默认每页10条
$currentPage = Request::param('page', 1); // 默认第1页
//
查询数据:先加条件,再调用paginate
$list = Goods::where('status', 1) // 只查已上架商品
->paginate([
'list_rows' => $pageSize, // 每页条数
'page' => $currentPage, // 当前页
]);
//
传给前端:分页对象里包含总条数、当前页、数据等信息
return view('goods/index', [
'list' => $list,
'pageSize' => $pageSize,
]);
}
}
这里要注意:paginate返回的是一个分页对象,不是数组!它里面包含了total()
(总条数)、currentPage()
(当前页)、lastPage()
(最后一页)、items()
(当前页数据数组)这些方法,前端需要用这些方法拿数据。
ThinkPHP6自带了分页标签{$list->render()}
,但默认样式比较丑,你可以自己改或者用前端框架的组件(比如Element UI、Layui)。我给朋友用的是Element UI的分页组件,前端代码大概长这样:
<!-商品列表 >
{{ item.name }}
<!-
Element UI分页组件 >
background
current-page="currentPage"
page-sizes="[10, 20, 30]"
page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
export default {
data() {
return {
list: [], // 当前页商品数据
total: 0, // 总条数
currentPage: 1, // 当前页
pageSize: 10, // 每页条数
};
},
created() {
this.getGoodsList();
},
methods: {
getGoodsList() {
// 调用接口拿数据,接口返回的是分页对象
axios.get('/goods/index', {
params: {
page: this.currentPage,
page_size: this.pageSize,
}
}).then(res => {
this.list = res.data.list.items(); // 拿当前页数据
this.total = res.data.list.total(); // 拿总条数
});
},
handleSizeChange(val) {
this.pageSize = val;
this.getGoodsList();
},
handleCurrentChange(val) {
this.currentPage = val;
this.getGoodsList();
},
}
};
这里有个小技巧:如果你的前端用的是Vue/React,记得用$list->toArray()
把分页对象转成数组,这样前端更容易处理——比如控制器里可以加一句$list = $list->toArray();
,但要注意:转成数组后,total
、current_page
这些字段会变成键名(比如$list['total']
)。
我整理了paginate最常用的5个参数,帮你省得翻文档:
参数名 | 类型 | 说明 | 默认值 | 示例 |
---|---|---|---|---|
list_rows | int | 每页显示条数 | 15 | paginate(10) |
page | int | 当前页码 | 1 | paginate(10, false, [‘page’ => 2]) |
simple | bool | 是否简单分页(不统计总条数,提升性能) | false | paginate(10, true) |
query | array | 保持分页链接中的额外参数(比如搜索词) | [] | paginate(10, false, [‘query’ => request()->param()]) |
其中最有用的是query
参数——比如你做了搜索功能,用户输入“手机”后,点击下一页时,要把“keyword=手机”带在分页链接里,否则下一页会丢失搜索条件。这时候用'query' => request()->param()
就能自动带上所有请求参数,亲测有效!
复杂场景分页:带条件、联表查询的解决办法
基础分页会了,接下来解决你最头疼的“复杂场景”——比如带搜索条件的分页、联表查询的分页。这些场景的核心还是“保持条件的一致性”:count和limit查询必须带相同的条件。
场景1:带搜索条件的分页
朋友的电商后台需要“按商品名搜索+按分类筛选”,我一开始的代码是这样的:
// 错误示例:搜索条件没传给count
$keyword = Request::param('keyword', '');
$categoryId = Request::param('category_id', 0);
$list = Goods::paginate(10);
if ($keyword) {
$list->where('name', 'like', "%{$keyword}%"); // 条件没生效!
}
if ($categoryId) {
$list->where('category_id', $categoryId); // 同样没生效!
}
结果就是:搜索“手机”后,总条数还是所有商品的数量,第2页直接显示“无数据”。后来改成先组装条件、再调用paginate,问题解决:
// 正确示例:先组装条件,再分页
$keyword = Request::param('keyword', '');
$categoryId = Request::param('category_id', 0);
// 组装条件数组
$where = [];
if ($keyword) {
$where[] = ['name', 'like', "%{$keyword}%"];
}
if ($categoryId) {
$where[] = ['category_id', '=', $categoryId];
}
// 先加条件,再分页
$list = Goods::where($where)->paginate([
'list_rows' => 10,
'query' => Request::param(), // 保持搜索条件在分页链接里
]);
这里的query
参数很关键——它会把keyword
和category_id
带在分页链接里,比如下一页的链接是/goods/index?page=2&keyword=手机&category_id=3
,这样点击下一页时,搜索条件不会丢。我朋友一开始没加这个参数,用户点击下一页后,搜索词直接没了,差点把用户“逼疯”。
场景2:联表查询的分页
联表查询是另一个“坑王”——比如商品表(goods)和分类表(category)联表,要查“商品名+分类名”的列表,很多人会写成这样:
// 错误示例:联表后count重复统计
$list = Goods::join('category', 'goods.category_id', '=', 'category.id')
->paginate(10);
结果总条数比实际多了一倍!因为联表后,count会统计重复的行(比如一个分类有多个商品,count会把每个商品都算一遍)。解决办法是给count加distinct,或者指定统计的字段:
// 正确示例:联表时用distinct统计
$list = Goods::join('category', 'goods.category_id', '=', 'category.id')
->field('goods.*, category.name as category_name') // 只查需要的字段
->distinct('goods.id') // 按商品ID去重
->paginate([
'list_rows' => 10,
'query' => Request::param(),
]);
或者更保险的方式:手动指定count的字段,比如count('goods.id')
,这样就不会重复了。我帮朋友调联表分页时,就是用了distinct('goods.id')
,总条数立刻对了。
完整复杂场景示例:带搜索+联表的商品列表
最后给你一个完整的“终极大招”——带搜索、联表、状态筛选的分页代码,直接复制就能用:
<?php namespace appcontroller;
use appmodelGoods;
use thinkfacadeRequest;
class GoodsController
{
public function index()
{
//
获取请求参数
$keyword = Request::param('keyword', '');
$categoryId = Request::param('category_id', 0);
$status = Request::param('status', 1); // 默认查已上架商品
$pageSize = Request::param('page_size', 10);
$currentPage = Request::param('page', 1);
//
组装查询条件
$where = [];
$where[] = ['goods.status', '=', $status]; // 商品状态
if ($keyword) {
$where[] = ['goods.name', 'like', "%{$keyword}%"]; // 商品名搜索
}
if ($categoryId) {
$where[] = ['goods.category_id', '=', $categoryId]; // 分类筛选
}
//
联表查询+分页
$list = Goods::alias('g') // 给商品表起别名,避免字段冲突
->join('category c', 'g.category_id = c.id', 'left') // 左联分类表
->field('g.id, g.name, g.price, c.name as category_name, g.create_time') // 只查需要的字段
->where($where)
->order('g.create_time', 'desc') // 按创建时间倒序
->paginate([
'list_rows' => $pageSize,
'page' => $currentPage,
'query' => Request::param(), // 保持所有请求参数
]);
//
传给前端
return view('goods/index', [
'list' => $list,
'keyword' => $keyword,
'categoryId' => $categoryId,
'status' => $status,
'pageSize' => $pageSize,
]);
}
}
前端模板里,你可以这样显示数据:
商品ID
商品名
分类
价格
创建时间
{foreach $list as $item}
{$item.id}
{$item.name}
{$item.category_name}
{$item.price}元
{$item.create_time|date='Y-m-d H:i:s'}
{/foreach}
<!-
分页组件 >
{$list->render()}
这个代码我朋友用了大半年,没再出现过分页问题——他说“比之前自己写的乱七八糟的limit语句好用100倍”。
如果你按这些方法试了,
用paginate方法查出来的总条数总是不对,怎么办?
这大概率是你把搜索或筛选条件写在paginate之后了,比如先调用Goods::paginate(10)再加where(‘status’,1),这样统计总条数的count语句根本没带条件,结果肯定和实际不符。
解决办法特简单:先把所有查询条件(比如状态、分类筛选)组装好,再调用paginate方法,比如Goods::where(‘status’,1)->paginate(10),这样count和limit查询都会带相同条件,总条数立刻就对了。
带搜索条件时分页,点下一页就丢了搜索词,怎么处理?
这是因为你没把搜索参数带在分页链接里,比如搜“手机”后,下一页链接里没有keyword=手机,自然就丢了条件。
只需在paginate的参数里加一句’query’ => request()->param(),它会自动把所有请求参数(比如keyword、category_id)挂在分页链接上,点下一页时搜索词就跟着走啦,亲测有效。
联表查询时分页的总条数总是重复,怎么解决?
联表后count语句会统计重复的行(比如一个分类下有5个商品,count会把这5个都算一遍),所以总条数会比实际多。
你可以给查询加distinct(‘goods.id’)(按商品ID去重),或者手动指定count的字段(比如count(‘goods.id’)),这样count就不会重复统计了,总条数立刻准。
前端怎么对接ThinkPHP6返回的分页数据?
ThinkPHP6的paginate返回的是分页对象,前端要拿当前页数据可以用items()方法(比如$list->items()),拿总条数用total(),拿当前页码用currentPage(),拿最后一页用lastPage()。
如果前端用Vue或React,你可以把分页对象转成数组($list->toArray()),转完后total、current_page这些字段直接就能用,对接Element UI或Layui的分页组件超方便。
数据量很大时,分页能不能不统计总条数?
当然能!用“简单分页”就行,在paginate的参数里加’simple’ => true,比如Goods::where(‘status’,1)->paginate(10, true),这样就不会执行count语句,只查当前页数据,性能能提升不少。
不过简单分页没有总条数和最后一页信息,适合不需要显示“共X页”的场景(比如滚动加载更多),你可以根据项目需求选。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com