分頁核心參數
- 總記錄數($total)
- 每頁條數($perPage)
- 當前頁碼($currentPage)
- URL 模板($urlPattern,如
/list?page={page})
- 顯示頁碼數($pageRange,兩側各幾個)
通用分頁類實現
<?php
/**
* 通用分頁器
* 支援:數字鏈接、首尾頁、上下頁、省略號、自定義模板、AJAX 模式
*/
class Paginator
{
public function __construct(
private int $total = 0,
private int $perPage = 15,
private int $currentPage = 1,
private string $urlPattern = '?page={page}',
private int $pageRange = 2,
private array $options = []
) {
$this->currentPage = max(1, min($this->currentPage, $this->lastPage()));
}
public function total(int $total): self
{
$this->total = $total;
$this->currentPage = min($this->currentPage, $this->lastPage());
return $this;
}
public function perPage(int $perPage): self
{
$this->perPage = max(1, $perPage);
return $this;
}
public function currentPage(int $page): self
{
$this->currentPage = max(1, min($page, $this->lastPage()));
return $this;
}
public function urlPattern(string $pattern): self
{
$this->urlPattern = $pattern;
return $this;
}
public function lastPage(): int
{
return max(1, (int)ceil($this->total / $this->perPage));
}
public function offset(): int
{
return ($this->currentPage - 1) * $this->perPage;
}
public function limit(): int
{
return $this->perPage;
}
/** 獲取頁碼範圍 */
public function getPageRange(): array
{
$last = $this->lastPage();
if ($last <= 2 * $this->pageRange + 1) {
return range(1, $last);
}
$start = max(1, $this->currentPage - $this->pageRange);
$end = min($last, $start + 2 * $this->pageRange);
if ($end - $start < 2 * $this->pageRange) {
$start = max(1, $end - 2 * $this->pageRange);
}
return range($start, $end);
}
/** 生成單個鏈接 */
private function link(int $page, string $text, array $attrs = []): string
{
$url = str_replace('{page}', (string)$page, $this->urlPattern);
$attrStr = '';
foreach ($attrs as $k => $v) {
$attrStr .= " $k=\"" . htmlspecialchars($v, ENT_QUOTES) . "\"";
}
if ($page === $this->currentPage) {
return "$text";
}
return "$text";
}
/** 渲染 HTML(默認 Bootstrap 5 風格) */
public function render(array $custom = []): string
{
$last = $this->lastPage();
if ($last <= 1) return '';
$html = '';
return $html;
}
/** AJAX 模式:輸出 data-page 屬性,由前端 JS 接管 */
public function renderAjax(string $containerSelector = '#list-container'): string
{
$last = $this->lastPage();
if ($last <= 1) return '';
$html = '';
return $html;
}
/** 獲取數據庫查詢 LIMIT 子句 */
public function getLimitClause(): string
{
return "LIMIT {$this->offset()}, {$this->perPage}";
}
/** 靜態工廠方法 */
public static function make(int $total, int $perPage = 15): self
{
$page = (int)($_GET['page'] ?? 1);
return new self($total, $perPage, $page);
}
}
使用示例
1. 基礎用法(MVC 控制器)
<?php
// 控制器
$total = $db->query("SELECT COUNT(*) FROM articles WHERE status=1")->fetchColumn();
$paginator = Paginator::make($total, 10);
$articles = $db->query("
SELECT * FROM articles WHERE status=1
ORDER BY created_at DESC
{$paginator->getLimitClause()}
")->fetchAll();
// 視圖
echo $paginator->render();
2. 自定義 URL(路由模式)
$paginator = new Paginator($total, 10, $currentPage, '/articles/page/{page}');
// 生成:/articles/page/1, /articles/page/2 ...
3. AJAX 無刷新分頁
<!-- 列表容器 -->
<div id="article-list">...</div>
<!-- 分頁 -->
<?= $paginator->renderAjax('#article-list') ?>
<script>
document.querySelectorAll('.pagination-ajax .page-btn').forEach(btn => {
btn.addEventListener('click', function() {
const page = this.dataset.page;
const container = document.querySelector(this.closest('.pagination-ajax').dataset.container);
fetch(`?page=${page}&ajax=1`)
.then(r => r.text())
.then(html => container.innerHTML = html);
});
});
</script>
4. API 分頁響應格式
<?php
// API 控制器
$paginator = Paginator::make($total, $perPage);
$data = $model->limit($paginator->perPage)->offset($paginator->offset())->get();
return response()->json([
'data' => $data,
'pagination' => [
'total' => $paginator->total,
'per_page' => $paginator->perPage,
'current_page' => $paginator->currentPage,
'last_page' => $paginator->lastPage(),
'from' => $paginator->offset() + 1,
'to' => min($paginator->offset() + $paginator->perPage, $paginator->total),
],
'links' => [
'first' => $paginator->link(1, 'first'),
'last' => $paginator->link($paginator->lastPage(), 'last'),
'prev' => $paginator->currentPage > 1 ? $paginator->link($paginator->currentPage - 1, 'prev') : null,
'next' => $paginator->currentPage < $paginator->lastPage() ? $paginator->link($paginator->currentPage + 1, 'next') : null,
],
]);
常見分頁風格 CSS 類名對照
| 框架 | 容器 | 項目 | 激活 | 禁用 | 鏈接 |
| Bootstrap 5 | .pagination | .page-item | .active | .disabled | .page-link |
| Bootstrap 4 | .pagination | .page-item | .active | .disabled | .page-link |
| Tailwind | .flex | - | bg-blue-600 | opacity-50 | px-3 py-1 |
| Layui | .layui-laypage | a/span | .layui-laypage-curr | - | - |
性能優化提示
- 大數據量:避免
COUNT(*) 全表掃描,使用觸發器維護計數表、或 EXPLAIN 估算行數
- 深分頁:
LIMIT 100000, 20 極慢,改用 WHERE id > last_id ORDER BY id LIMIT 20(游標分頁)
- 緩存總數:列表首頁緩存
total 60 秒,減少 COUNT 查詢
- 靜態化:首頁、熱門分類首頁生成靜態 HTML 含分頁片段