分頁組件應將「數據邏輯」(總數、每頁條數、當前頁、SQL LIMIT)與「視圖渲染」(HTML 結構、CSS 類、鏈接格式)完全分離。本文實現一個 Pager 類:
setTotal($count) 設置總記錄數getLimit() 返回 SQL LIMIT 子句render($templateFile) 讀取模板文件,替換標簽輸出 HTML{PAGE_FIRST}、{PAGE_PREV}、{PAGE_LIST}、{PAGE_NEXT}、{PAGE_LAST}、{PAGE_CURRENT}、{PAGE_TOTAL}<?php
class Pager {
// 配置
public int $currentPage = 1;
public int $pageSize = 15;
public int $linkCount = 5; // 數字鏈接顯示個數(奇數最佳)
public string $urlPattern = '?page={page}'; // {page} 會被替換
public string $ajaxTarget = ''; // 若設置,輸出 data-page 屬性供 JS 攔截
// 運行時數據
private int $totalRecords = 0;
private int $totalPages = 0;
public function __construct(array $options = []) {
foreach ($options as $k => $v) if (property_exists($this, $k)) $this->$k = $v;
$this->currentPage = max(1, (int)$this->currentPage);
$this->pageSize = max(1, (int)$this->pageSize);
$this->linkCount = max(3, (int)$this->linkCount | 1); // 確保奇數
}
public function setTotal(int $count): self {
$this->totalRecords = max(0, $count);
$this->totalPages = (int)ceil($this->totalRecords / $this->pageSize);
$this->currentPage = min($this->currentPage, max(1, $this->totalPages));
return $this;
}
/** 返回 SQL LIMIT 子句 */
public function getLimit(): string {
$offset = ($this->currentPage - 1) * $this->pageSize;
return "LIMIT $offset, {$this->pageSize}";
}
/** 獲取頁碼範圍 */
private function getPageRange(): array {
$half = ($this->linkCount - 1) / 2;
$start = max(1, $this->currentPage - $half);
$end = min($this->totalPages, $start + $this->linkCount - 1);
if ($end - $start + 1 < $this->linkCount) {
$start = max(1, $end - $this->linkCount + 1);
}
return [$start, $end];
}
/** 渲染模板文件 */
public function render(string $tplFile): string {
if (!is_file($tplFile)) throw new RuntimeException("模板不存在: $tplFile");
$tpl = file_get_contents($tplFile);
// 標簽替換映射
$tags = [
'PAGE_CURRENT' => $this->currentPage,
'PAGE_TOTAL' => $this->totalPages,
'PAGE_SIZE' => $this->pageSize,
'RECORD_TOTAL' => $this->totalRecords,
];
// 首頁/尾頁
$tags['PAGE_FIRST'] = $this->buildLink(1, '首頁', $this->currentPage === 1);
$tags['PAGE_LAST'] = $this->buildLink($this->totalPages, '尾頁', $this->currentPage === $this->totalPages);
// 上一頁/下一頁
$tags['PAGE_PREV'] = $this->buildLink($this->currentPage - 1, '上一頁', $this->currentPage === 1);
$tags['PAGE_NEXT'] = $this->buildLink($this->currentPage + 1, '下一頁', $this->currentPage === $this->totalPages);
// 數字鏈接列表
[$start, $end] = $this->getPageRange();
$listHtml = '';
for ($i = $start; $i <= $end; $i++) {
$listHtml .= $this->buildLink($i, (string)$i, $i === $this->currentPage);
}
$tags['PAGE_LIST'] = $listHtml;
// 替換
$search = []; $replace = [];
foreach ($tags as $k => $v) {
$search[] = '{' . $k . '}';
$replace[] = $v;
}
return str_replace($search, $replace, $tpl);
}
private function buildLink(int $page, string $text, bool $disabled): string {
if ($disabled) {
return "$text";
}
$url = str_replace('{page}', $page, $this->urlPattern);
$attr = $this->ajaxTarget ? " data-page=\"$page\" data-target=\"{$this->ajaxTarget}\"" : '';
return "$text";
}
}
?>
pager.tpl<nav class="pagination" aria-label="Page navigation">
{PAGE_FIRST}
{PAGE_PREV}
{PAGE_LIST}
{PAGE_NEXT}
{PAGE_LAST}
<span class="page-info">共 {RECORD_TOTAL} 條 / {PAGE_TOTAL} 頁,當前第 {PAGE_CURRENT} 頁</span>
</nav>
<?php
// 1. 獲取總數(示例使用 PDO)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pwd');
$total = $pdo->query("SELECT COUNT(*) FROM articles WHERE status=1")->fetchColumn();
// 2. 實例化分頁
$pager = new Pager([
'currentPage' => $_GET['page'] ?? 1,
'pageSize' => 10,
'linkCount' => 7,
'urlPattern' => 'article_list.php?page={page}',
// 'ajaxTarget' => '#article-list', // 啟用 AJAX 模式
]);
$pager->setTotal($total);
// 3. 查詢當前頁數據
$stmt = $pdo->prepare("SELECT id, title, created_at FROM articles WHERE status=1 ORDER BY id DESC " . $pager->getLimit());
$stmt->execute();
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 4. 輸出列表
foreach ($articles as $art) {
echo "{$art['title']}
";
}
// 5. 輸出分頁導航
echo $pager->render(__DIR__ . '/templates/pager.tpl');
?>
<script>
document.addEventListener('click', e => {
const a = e.target.closest('a[data-page]');
if (!a) return;
e.preventDefault();
const target = document.querySelector(a.dataset.target);
if (!target) return;
fetch(a.href, {headers: {'X-Requested-With': 'XMLHttpRequest'}})
.then(r => r.text())
.then(html => target.innerHTML = html);
});
</script>
後端檢測 $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest' 時只輸出列表 HTML 片段(不含分頁導航),或輸出 JSON {listHtml: '...', pagerHtml: '...'} 由前端分別填充。
本分頁類通過「模板文件 + 標簽替換」實現視圖與邏輯解耦,支持自定義 URL 模式、鏈接數量、AJAX 掛載,代碼精簡、無第三方依賴,易於集成到任何 PHP 項目(原生、Laravel、ThinkPHP、Symfony 等)。