> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 分页

> 介绍 Laravel 的分页功能。涵盖 paginate()、simplePaginate()、cursorPaginate() 的区别，Blade 中的展示、API 响应以及 URL 定制。

## 什么是分页

Laravel 的分页与查询构造器和 Eloquent ORM 无缝集成，开箱即用。当前页会自动从 HTTP 请求的 `page` 查询参数读取，并自动附加到生成的链接中。

默认的 HTML 支持 Tailwind CSS，也可以选择 Bootstrap CSS。

## 三种分页方式

| 方法                 | 返回值                    | 特点                 |
| ------------------ | ---------------------- | ------------------ |
| `paginate()`       | `LengthAwarePaginator` | 获取总条数，生成页码链接       |
| `simplePaginate()` | `Paginator`            | 不获取总条数，仅“上一页”“下一页” |
| `cursorPaginate()` | `CursorPaginator`      | 基于游标，非常适合大数据量      |

## 基本用法

### 查询构造器分页

```php theme={null}
use Illuminate\Support\Facades\DB;

// 每页 15 条
$users = DB::table('users')->paginate(15);
```

### Eloquent 分页

```php theme={null}
use App\Models\User;

$users = User::paginate(15);

// 带条件
$users = User::where('votes', '>', 100)->paginate(15);
```

### simplePaginate

如果不需要 `COUNT(*)`（只显示上一页/下一页），使用 `simplePaginate()` 更高效。

```php theme={null}
$users = DB::table('users')->simplePaginate(15);
$users = User::where('active', true)->simplePaginate(15);
```

<Tip>
  如果不需要“共 N 条，当前 M 条”这类展示，就选 `simplePaginate()`。`paginate()` 会额外执行一次 `COUNT(*)`，`simplePaginate()` 更快。
</Tip>

### cursorPaginate（游标分页）

游标分页使用 WHERE 子句代替 OFFSET，在大量数据下性能更好，尤其适合无限滚动的 UI。

```php theme={null}
// 必须指定 orderBy
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
$users = User::where('active', true)->cursorPaginate(15);
```

生成的 URL 中不再是页码，而是一段游标字符串。

```
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
```

<Warning>
  使用游标分页时必须指定 `orderBy`。另外用于排序的列必须属于被分页的表。
</Warning>

### OFFSET 与游标的对比

```sql theme={null}
-- 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 theme={null}
<?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 中显示分页链接

```blade theme={null}
<div class="container">
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach
</div>

{{-- 输出分页链接（默认适配 Tailwind CSS） --}}
{{ $users->links() }}
```

`links()` 方法会自动生成分页链接 HTML。默认会显示当前页前后各 3 页的链接。

### 调整两侧显示的链接数

`onEachSide()` 用来指定当前页两侧显示的链接数量。

```blade theme={null}
{{-- 当前页两侧各显示 5 页 --}}
{{ $users->onEachSide(5)->links() }}
```

## 从请求获取每页条数

```php theme={null}
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` 参数会互相冲突。可以通过第三个参数改名。

```php theme={null}
$users = User::paginate(
    perPage: 15,
    columns: ['*'],
    pageName: 'users'
);

$posts = Post::paginate(
    perPage: 10,
    columns: ['*'],
    pageName: 'posts'
);
```

## URL 自定义

### 修改基础 URL

```php theme={null}
$users = User::paginate(15);

// 生成 /admin/users?page=N 形式的 URL
$users->withPath('/admin/users');
```

### 追加查询参数

```php theme={null}
// 在每个分页链接上追加 sort=votes
$users->appends(['sort' => 'votes']);

// 继承当前请求的所有查询参数
$users->withQueryString();
```

### 添加 hash 片段

```php theme={null}
// URL 末尾追加 #users
$users->fragment('users');
```

## API 响应（JSON 输出）

从路由或控制器直接返回分页器时，会自动转换为 JSON。

```php theme={null}
use App\Models\User;

Route::get('/api/users', function () {
    return User::paginate(15);
});
```

响应的 JSON 结构：

```json theme={null}
{
    "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()`。

```php theme={null}
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/api/users', function () {
    $users = User::paginate(15);

    return UserResource::collection($users);
});
```

把分页器传给 `UserResource::collection()` 后，分页信息会作为元数据自动附加。

<Info>
  `cursorPaginate()` 的 JSON 中不会有页码，而是 `next_cursor` 和 `prev_cursor`。API 客户端应使用它们作为下一次请求的 `cursor` 参数。
</Info>

## 自定义分页视图

### 视图文件直接指定

```blade theme={null}
{{-- 使用自定义视图输出链接 --}}
{{ $paginator->links('vendor.pagination.custom') }}

{{-- 传入额外数据 --}}
{{ $paginator->links('vendor.pagination.custom', ['theme' => 'dark']) }}
```

### 修改默认视图为自定义文件

先发布官方视图再进行定制。

```shell theme={null}
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` 中指定。

```php theme={null}
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()` 中指定。

```php theme={null}
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::useBootstrapFive(); // Bootstrap 5
    // Paginator::useBootstrapFour(); // Bootstrap 4
}
```

## 手动创建分页器

对数组等现有数据做分页时，可以直接实例化分页器类。

```php theme={null}
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()]
);
```

## 常用实例方法

```php theme={null}
$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();           // 当前页的条目数组
```

## 小结

<AccordionGroup>
  <Accordion title="如何选择分页方式">
    * **`paginate()`** — 需要总条数和页码链接（常见列表页）
    * **`simplePaginate()`** — 只需要上一页/下一页链接（更快）
    * **`cursorPaginate()`** — 大数据量、无限滚动、高频写入（性能最好）
  </Accordion>

  <Accordion title="Blade 展示基本模式">
    ```blade theme={null}
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach

    {{ $users->links() }}
    ```

    把 `paginate()` 的结果传给视图，用 `links()` 输出分页链接即可。
    当前页会自动从 `page` 查询参数读取。
  </Accordion>

  <Accordion title="API 中的分页">
    从路由直接返回分页器会自动转 JSON。
    与 API 资源结合时，返回 `UserResource::collection($paginator)`。
    响应中包含 `data`（记录数组）以及各种元信息。
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel Scout](/zh/scout.md)
- [Eloquent API 资源](/zh/eloquent-resources.md)
- [Laravel 12 升级到 13 指南](/zh/blog/upgrade-12-to-13.md)
- [集合](/zh/collections.md)
- [⚡ Livewire 4 入门 —— 无需 JavaScript 也能做出响应式 UI](/zh/blog/livewire-introduction.md)
