什么是分页
Laravel 的分页与查询构造器和 Eloquent ORM 无缝集成,开箱即用。当前页会自动从 HTTP 请求的 page 查询参数读取,并自动附加到生成的链接中。
默认的 HTML 支持 Tailwind CSS,也可以选择 Bootstrap CSS。
三种分页方式
方法 返回值 特点 paginate()LengthAwarePaginator获取总条数,生成页码链接 simplePaginate()Paginator不获取总条数,仅“上一页”“下一页” cursorPaginate()CursorPaginator基于游标,非常适合大数据量
基本用法
查询构造器分页
use Illuminate\Support\Facades\ DB ;
// 每页 15 条
$users = DB :: table ( 'users' ) -> paginate ( 15 );
Eloquent 分页
use App\Models\ User ;
$users = User :: paginate ( 15 );
// 带条件
$users = User :: where ( 'votes' , '>' , 100 ) -> paginate ( 15 );
simplePaginate
如果不需要 COUNT(*)(只显示上一页/下一页),使用 simplePaginate() 更高效。
$users = DB :: table ( 'users' ) -> simplePaginate ( 15 );
$users = User :: where ( 'active' , true ) -> simplePaginate ( 15 );
如果不需要“共 N 条,当前 M 条”这类展示,就选 simplePaginate()。paginate() 会额外执行一次 COUNT(*),simplePaginate() 更快。
cursorPaginate(游标分页)
游标分页使用 WHERE 子句代替 OFFSET,在大量数据下性能更好,尤其适合无限滚动的 UI。
// 必须指定 orderBy
$users = DB :: table ( 'users' ) -> orderBy ( 'id' ) -> cursorPaginate ( 15 );
$users = User :: where ( 'active' , true ) -> cursorPaginate ( 15 );
生成的 URL 中不再是页码,而是一段游标字符串。
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
使用游标分页时必须指定 orderBy。另外用于排序的列必须属于被分页的表。
OFFSET 与游标的对比
-- paginate() / simplePaginate() — 使用 OFFSET
SELECT * FROM users ORDER BY id ASC LIMIT 15 OFFSET 15 ;
-- cursorPaginate() — 使用 WHERE 子句(能利用索引)
SELECT * FROM users WHERE id > 15 ORDER BY id ASC LIMIT 15 ;
游标分页可以充分利用索引,即便数据频繁增删也不容易出现记录重复或遗漏。缺点是无法生成页码链接,只有上一页/下一页。
控制器中的用法
<? php
namespace App\Http\Controllers ;
use App\Models\ User ;
use Illuminate\View\ View ;
class UserController extends Controller
{
public function index () : View
{
$users = User :: orderBy ( 'name' ) -> paginate ( 20 );
return view ( 'users.index' , compact ( 'users' ));
}
}
在 Blade 中显示分页链接
< div class = "container" >
@foreach ( $users as $user )
< p > {{ $user -> name }} </ p >
@endforeach
</ div >
{{-- 输出分页链接(默认适配 Tailwind CSS) --}}
{{ $users -> links () }}
links() 方法会自动生成分页链接 HTML。默认会显示当前页前后各 3 页的链接。
调整两侧显示的链接数
onEachSide() 用来指定当前页两侧显示的链接数量。
{{-- 当前页两侧各显示 5 页 --}}
{{ $users -> onEachSide ( 5 ) -> links () }}
从请求获取每页条数
public function index ( Request $request ) : View
{
$perPage = $request -> integer ( 'per_page' , 15 );
$perPage = min ( max ( $perPage , 1 ), 100 ); // 限制在 1~100
$users = User :: paginate ( $perPage );
return view ( 'users.index' , compact ( 'users' ));
}
同页展示多个分页器
若同一页面上有两个分页器,两个都用 page 参数会互相冲突。可以通过第三个参数改名。
$users = User :: paginate (
perPage : 15 ,
columns : [ '*' ],
pageName : 'users'
);
$posts = Post :: paginate (
perPage : 10 ,
columns : [ '*' ],
pageName : 'posts'
);
URL 自定义
修改基础 URL
$users = User :: paginate ( 15 );
// 生成 /admin/users?page=N 形式的 URL
$users -> withPath ( '/admin/users' );
追加查询参数
// 在每个分页链接上追加 sort=votes
$users -> appends ([ 'sort' => 'votes' ]);
// 继承当前请求的所有查询参数
$users -> withQueryString ();
添加 hash 片段
// URL 末尾追加 #users
$users -> fragment ( 'users' );
API 响应(JSON 输出)
从路由或控制器直接返回分页器时,会自动转换为 JSON。
use App\Models\ User ;
Route :: get ( '/api/users' , function () {
return User :: paginate ( 15 );
});
响应的 JSON 结构:
{
"total" : 50 ,
"per_page" : 15 ,
"current_page" : 1 ,
"last_page" : 4 ,
"first_page_url" : "http://example.com/api/users?page=1" ,
"last_page_url" : "http://example.com/api/users?page=4" ,
"next_page_url" : "http://example.com/api/users?page=2" ,
"prev_page_url" : null ,
"path" : "http://example.com/api/users" ,
"from" : 1 ,
"to" : 15 ,
"data" : [
{ "id" : 1 , "name" : "山田太郎" },
{ "id" : 2 , "name" : "鈴木花子" }
]
}
与 API 资源组合
要用 API 资源集合包装 paginate() 的结果,可以传给 UserResource::collection()。
use App\Http\Resources\ UserResource ;
use App\Models\ User ;
Route :: get ( '/api/users' , function () {
$users = User :: paginate ( 15 );
return UserResource :: collection ( $users );
});
把分页器传给 UserResource::collection() 后,分页信息会作为元数据自动附加。
cursorPaginate() 的 JSON 中不会有页码,而是 next_cursor 和 prev_cursor。API 客户端应使用它们作为下一次请求的 cursor 参数。
自定义分页视图
视图文件直接指定
{{-- 使用自定义视图输出链接 --}}
{{ $paginator -> links ( 'vendor.pagination.custom' ) }}
{{-- 传入额外数据 --}}
{{ $paginator -> links ( 'vendor.pagination.custom' , [ 'theme' => 'dark' ]) }}
修改默认视图为自定义文件
先发布官方视图再进行定制。
php artisan vendor:publish --tag=laravel-pagination
resources/views/vendor/pagination/ 下会生成以下文件:
tailwind.blade.php — 默认(Tailwind CSS)
bootstrap-5.blade.php — Bootstrap 5
simple-tailwind.blade.php — simplePaginate 用
…
你可以直接修改 tailwind.blade.php,也可以新建视图并在 AppServiceProvider 中指定。
use Illuminate\Pagination\ Paginator ;
public function boot () : void
{
Paginator :: defaultView ( 'vendor.pagination.custom' );
Paginator :: defaultSimpleView ( 'vendor.pagination.simple-custom' );
}
使用 Bootstrap CSS
若使用 Bootstrap 而非 Tailwind,可以在 AppServiceProvider 的 boot() 中指定。
use Illuminate\Pagination\ Paginator ;
public function boot () : void
{
Paginator :: useBootstrapFive (); // Bootstrap 5
// Paginator::useBootstrapFour(); // Bootstrap 4
}
手动创建分页器
对数组等现有数据做分页时,可以直接实例化分页器类。
use Illuminate\Pagination\ LengthAwarePaginator ;
$items = collect ( range ( 1 , 200 )) -> map ( fn ( $i ) => [ 'id' => $i , 'name' => "条目{ $i }" ]);
$perPage = 15 ;
$currentPage = request () -> integer ( 'page' , 1 );
$paginator = new LengthAwarePaginator (
items : $items -> forPage ( $currentPage , $perPage ),
total : $items -> count (),
perPage : $perPage ,
currentPage : $currentPage ,
options : [ 'path' => request () -> url ()]
);
常用实例方法
$paginator = User :: paginate ( 15 );
$paginator -> currentPage (); // 当前页码
$paginator -> lastPage (); // 最后一页(simplePaginate 不可用)
$paginator -> total (); // 总条数(simplePaginate 不可用)
$paginator -> perPage (); // 每页条数
$paginator -> count (); // 当前页条数
$paginator -> firstItem (); // 当前页第一条的序号
$paginator -> lastItem (); // 当前页最后一条的序号
$paginator -> hasPages (); // 是否有多页
$paginator -> hasMorePages (); // 是否有下一页
$paginator -> onFirstPage (); // 是否是第一页
$paginator -> onLastPage (); // 是否是最后一页
$paginator -> nextPageUrl (); // 下一页 URL
$paginator -> previousPageUrl (); // 上一页 URL
$paginator -> url ( 3 ); // 第 3 页的 URL
$paginator -> items (); // 当前页的条目数组
paginate() — 需要总条数和页码链接(常见列表页)
simplePaginate() — 只需要上一页/下一页链接(更快)
cursorPaginate() — 大数据量、无限滚动、高频写入(性能最好)
@foreach ( $users as $user )
< p > {{ $user -> name }} </ p >
@endforeach
{{ $users -> links () }}
把 paginate() 的结果传给视图,用 links() 输出分页链接即可。
当前页会自动从 page 查询参数读取。
从路由直接返回分页器会自动转 JSON。
与 API 资源结合时,返回 UserResource::collection($paginator)。
响应中包含 data(记录数组)以及各种元信息。