游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
ThinkPHP6 MySQL分页查询实现代码超详细教程附完整示例

这篇教程专门解决这些痛点:从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();,但要注意:转成数组后,totalcurrent_page这些字段会变成键名(比如$list['total'])。

  • 用表格理清楚paginate的参数
  • 我整理了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参数很关键——它会把keywordcategory_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,

    ]);

    }

    }

    前端模板里,你可以这样显示数据:

    
     {foreach $list as $item}
     
     {/foreach}
     
    商品ID 商品名 分类 价格 创建时间
    {$item.id} {$item.name} {$item.category_name} {$item.price}元 {$item.create_time|date='Y-m-d H:i:s'}

    <!-

  • 分页组件 >
  • 这个代码我朋友用了大半年,没再出现过分页问题——他说“比之前自己写的乱七八糟的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页”的场景(比如滚动加载更多),你可以根据项目需求选。