列表頁幾乎是每個專案的標配,但分頁邏輯(總筆數、每頁筆數、當前頁碼、URL 參數保持、導航列渲染)若分散在各控制器中,不僅重複造輪子,維護時更得改十幾個檔案。將分頁封裝為獨立函數 pageft(),放入 pageft.php,隨時 include 即用,才是專業開發的基本功。
<ul>、<nav> 或 Bootstrap 元件渲染| 變數 | 型別 | 用途 |
|---|---|---|
$firstcount | int | SQL LIMIT 起始位移(($page-1)*$pagesize) |
$displaypg | int | 當前頁碼(已修正邊界) |
$pagenav | array | 導航列資料結構,含首頁、上一頁、頁碼陣列、下一頁、尾頁、總頁數等鍵值 |
<?php
/**
* 通用分頁函數
* @param int $total_rows 總筆數
* @param int $pagesize 每頁筆數,預設 20
* @param int $pagenum 導航列顯示頁碼數,預設 10
* @param string $url_base 基礎 URL(含 ? 之前部分),預設自動偵測
*/
function pageft($total_rows, $pagesize = 20, $pagenum = 10, $url_base = '') {
global $firstcount, $displaypg, $pagenav;
// 1. 計算總頁數
$total_pages = ceil($total_rows / $pagesize);
if ($total_pages < 1) $total_pages = 1;
// 2. 取得當前頁碼(支援 $_GET['page'] 或 $_GET['p'])
$page = isset($_GET['page']) ? (int)$_GET['page'] : (isset($_GET['p']) ? (int)$_GET['p'] : 1);
if ($page < 1) $page = 1;
if ($page > $total_pages) $page = $total_pages;
// 3. 產生 LIMIT 位移
$firstcount = ($page - 1) * $pagesize;
$displaypg = $page;
// 4. 建構基礎 URL(保留除 page 外的所有查詢參數)
if ($url_base === '') {
$url_base = $_SERVER['PHP_SELF'] . '?' . http_build_query(array_diff_key($_GET, ['page' => '', 'p' => '']));
if ($url_base !== '') $url_base .= '&';
}
// 5. 產生導航陣列
$nav = [
'first' => 1,
'prev' => max(1, $page - 1),
'next' => min($total_pages, $page + 1),
'last' => $total_pages,
'current' => $page,
'total' => $total_pages,
'pages' => [],
'url_base' => $url_base,
];
// 頁碼範圍計算(以當前頁為中心,前後各 $pagenum/2)
$half = (int)($pagenum / 2);
$start = max(1, $page - $half);
$end = min($total_pages, $start + $pagenum - 1);
if ($end - $start + 1 < $pagenum) {
$start = max(1, $end - $pagenum + 1);
}
for ($i = $start; $i <= $end; $i++) {
$nav['pages'][] = $i;
}
$pagenav = $nav;
}
?>
<?php
require_once 'db.php'; // 您的資料庫連線
require_once 'pageft.php'; // 載入分頁函數
// 1. 取得總筆數
$total = $pdo->query("SELECT COUNT(*) FROM articles WHERE status=1")->fetchColumn();
// 2. 呼叫分頁函數(每頁 15 筆,導航列顯示 10 個頁碼)
pageft($total, 15, 10);
// 3. 取得資料(使用 $firstcount 做 LIMIT)
$stmt = $pdo->prepare("SELECT id, title, created_at FROM articles WHERE status=1 ORDER BY id DESC LIMIT ?, 15");
$stmt->execute([$firstcount]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html>
<head><title>文章列表</title></head>
<body>
<table>
<thead><tr><th>ID</th><th>標題</th><th>日期</th></tr></thead>
<tbody>
<?php foreach ($rows as $r): ?>
<tr><td><?=$r['id']?></td><td><?=htmlspecialchars($r['title'])?></td><td><?=$r['created_at']?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 導航列渲染 -->
<nav aria-label="Pagination">
<ul class="pagination">
<?php
$n = $pagenav;
// 首頁 / 上一頁
echo '<li><a href="' . $n['url_base'] . 'page=1">首頁</a></li>';
echo '<li><a href="' . $n['url_base'] . 'page=' . $n['prev'] . '">上一頁</a></li>';
// 頁碼
foreach ($n['pages'] as $p) {
$cls = ($p == $n['current']) ? ' class="active"' : '';
echo '<li' . $cls . '><a href="' . $n['url_base'] . 'page=' . $p . '">' . $p . '</a></li>';
}
// 下一頁 / 尾頁
echo '<li><a href="' . $n['url_base'] . 'page=' . $n['next'] . '">下一頁</a></li>';
echo '<li><a href="' . $n['url_base'] . 'page=' . $n['last'] . '">尾頁</a></li>';
?>
</ul>
</nav>
<p>共 <?=$pagenav['total']?> 頁 / <?=$total?> 筆資料</p>
</body>
</html>
Pager 類別,支援多實例、鏈式呼叫($pager->setTotal($total)->setPageSize(15)->render())。getApiResponse() 回傳 JSON 結構,前端以 fetch 串接。url_pattern = '/article/page/{page}',配合 Nginx/Apache Rewrite 產生美觀網址。COUNT(*) 結果寫入 Redis 緩存 30-60 秒,大幅降低 DB 壓力。一個優良的分頁函數,核心在於「只做運算、不做輸出」。將分頁邏輯與呈現層徹底分離,才能在 MVC、API、SSR、CSR 等各種架構下複用。將 pageft.php 放入專案共用函式庫,下次建列表頁時,只需三行代碼即可完成專業級分頁——這才是「一勞永逸」的正確姿勢。