首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 通用分頁類完整實現:支援下拉跳轉、自定義顯示數量、URL 參數保留

     編輯:PHP 綜合知識

設計目標

  • 零依賴、單檔案、可直接 `require` 使用
  • 支援 **任意查詢參數保留** (如 `?search=foo&cat=bar&page=3`)
  • 靈活配置:每頁筆數、顯示頁碼數量、首尾/上下頁、下拉跳轉
  • 輸出語義化 HTML,易於 CSS 美化
  • 防 XSS:所有動態輸出經 `htmlspecialchars`

完整類代碼 (Pager.php)

<?php
/**
 * 通用分頁器
 * 用法:
 *   $pager = new Pager($totalRows, $perPage, $currentPage, $options);
 *   echo $pager->render();
 */
class Pager
{
    private int $totalRows;
    private int $perPage;
    private int $currentPage;
    private int $totalPages;
    private array $options;

    public function __construct(
        int $totalRows,
        int $perPage = 20,
        ?int $currentPage = null,
        array $options = []
    ) {
        $this->totalRows = max(0, $totalRows);
        $this->perPage   = max(1, $perPage);
        $this->totalPages = (int)ceil($this->totalRows / $this->perPage);

        // 解析當前頁
        if ($currentPage === null) {
            $currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
        }
        $this->currentPage = max(1, min($currentPage, max(1, $this->totalPages)));

        // 預設選項
        $defaults = [
            'showPageCount'   => 9,        // 顯示幾個頁碼 (建議奇數)
            'paramName'       => 'page',   // URL 參數名
            'preserveParams'  => true,     // 保留其他 GET 參數
            'firstText'       => '首頁',
            'lastText'        => '尾頁',
            'prevText'        => '上一頁',
            'nextText'        => '下一頁',
            'dropdown'        => true,     // 顯示下拉跳轉
            'dropdownText'    => '跳轉至',
            'wrapperClass'    => 'pagination',
            'activeClass'     => 'active',
            'disabledClass'   => 'disabled',
            'linkClass'       => 'page-link',
        ];
        $this->options = array_merge($defaults, $options);
    }

    /** 產生單頁連結 */
    private function link(int $page, string $text, array $attrs = []): string
    {
        $url = $this->buildUrl($page);
        $attrs['href'] = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
        $attrs['class'] = ($attrs['class'] ?? '') . ' ' . $this->options['linkClass'];
        $attrStr = '';
        foreach ($attrs as $k => $v) {
            $attrStr .= ' ' . $k . '="' . htmlspecialchars($v, ENT_QUOTES, 'UTF-8') . '"';
        }
        return '<a' . $attrStr . '>' . htmlspecialchars($text) . '</a>';
    }

    /** 建構保留參數的 URL */
    private function buildUrl(int $page): string
    {
        $params = $_GET;
        if (!$this->options['preserveParams']) {
            $params = [];
        }
        $params[$this->options['paramName']] = $page;
        // 移除空值
        $params = array_filter($params, fn($v) => $v !== '' && $v !== null);
        $base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http')
              . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];
        return $base . '?' . http_build_query($params);
    }

    /** 計算顯示頁碼範圍 */
    private function getPageRange(): array
    {
        $show = $this->options['showPageCount'];
        $half = (int)floor($show / 2);
        $start = max(1, $this->currentPage - $half);
        $end   = min($this->totalPages, $start + $show - 1);
        // 向前修正
        if ($end - $start + 1 < $show) {
            $start = max(1, $end - $show + 1);
        }
        return [$start, $end];
    }

    /** 主渲染方法 */
    public function render(): string
    {
        if ($this->totalPages <= 1) return ''; // 無需分頁

        $opt = $this->options;
        $html = '<nav aria-label="Pagination"><ul class="' . $opt['wrapperClass'] . '">';

        // 總筆數資訊
        $html .= '<li class="page-info">共 ' . $this->totalRows . ' 筆 / ' . $this->perPage . ' 筆每頁,第 ' . $this->currentPage . ' / ' . $this->totalPages . ' 頁</li>';

        // 首頁
        if ($this->currentPage > 1) {
            $html .= '<li>' . $this->link(1, $opt['firstText']) . '</li>';
        } else {
            $html .= '<li class="' . $opt['disabledClass'] . '"><span>' . $opt['firstText'] . '</span></li>';
        }

        // 上一頁
        if ($this->currentPage > 1) {
            $html .= '<li>' . $this->link($this->currentPage - 1, $opt['prevText']) . '</li>';
        } else {
            $html .= '<li class="' . $opt['disabledClass'] . '"><span>' . $opt['prevText'] . '</span></li>';
        }

        // 頁碼區間
        [$start, $end] = $this->getPageRange();
        for ($i = $start; $i <= $end; $i++) {
            if ($i === $this->currentPage) {
                $html .= '<li class="' . $opt['activeClass'] . '" aria-current="page"><span>' . $i . '</span></li>';
            } else {
                $html .= '<li>' . $this->link($i, (string)$i) . '</li>';
            }
        }

        // 下一頁
        if ($this->currentPage < $this->totalPages) {
            $html .= '<li>' . $this->link($this->currentPage + 1, $opt['nextText']) . '</li>';
        } else {
            $html .= '<li class="' . $opt['disabledClass'] . '"><span>' . $opt['nextText'] . '</span></li>';
        }

        // 尾頁
        if ($this->currentPage < $this->totalPages) {
            $html .= '<li>' . $this->link($this->totalPages, $opt['lastText']) . '</li>';
        } else {
            $html .= '<li class="' . $opt['disabledClass'] . '"><span>' . $opt['lastText'] . '</span></li>';
        }

        // 下拉跳轉
        if ($opt['dropdown'] && $this->totalPages > 1) {
            $html .= '<li class="page-dropdown">';
            $html .= '<label for="pager-dropdown">' . $opt['dropdownText'] . '</label>';
            $html .= '<select id="pager-dropdown" onchange="window.location.href=this.value">';
            for ($i = 1; $i <= $this->totalPages; $i++) {
                $selected = ($i === $this->currentPage) ? ' selected' : '';
                $html .= '<option value="' . htmlspecialchars($this->buildUrl($i)) . '"' . $selected . '>第 ' . $i . ' 頁</option>';
            }
            $html .= '</select></li>';
        }

        $html .= '</ul></nav>';
        return $html;
    }

    /** 靜態快速建立 */
    public static function create(int $totalRows, int $perPage = 20, array $options = []): self
    {
        return new self($totalRows, $perPage, null, $options);
    }
}

使用範例

<?php
require_once 'Pager.php';

// 模擬資料庫查詢
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$total = $pdo->query('SELECT COUNT(*) FROM articles WHERE status=1')->fetchColumn();
$page  = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 15;
$offset  = ($page - 1) * $perPage;
$stmt = $pdo->prepare('SELECT * FROM articles WHERE status=1 ORDER BY id DESC LIMIT ?, ?');
$stmt->execute([$offset, $perPage]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 實例化分頁器 (保留 search、cat 參數)
$pager = new Pager($total, $perPage, $page, [
    'showPageCount' => 7,
    'preserveParams' => true,
    'wrapperClass' => 'pagination justify-content-center',
    'linkClass' => 'page-link text-dark',
    'activeClass' => 'active bg-primary',
    'disabledClass' => 'disabled',
]);

// 輸出列表
foreach ($rows as $row) { /* ... */ }

// 輸出分頁
echo $pager->render();
?>

CSS 美化範例 (Bootstrap 5 風格)

.pagination { display:flex; flex-wrap:wrap; gap:0.25rem; list-style:none; padding:0; margin:1rem 0; }
.pagination li { display:flex; align-items:center; }
.pagination .page-link { display:block; padding:0.375rem 0.75rem; color:#0d6efd; text-decoration:none; border:1px solid #dee2e6; border-radius:0.25rem; transition:all .2s; }
.pagination .page-link:hover { background:#e9ecef; }
.pagination .active .page-link { background:#0d6efd; border-color:#0d6efd; color:#fff; z-index:3; }
.pagination .disabled .page-link { color:#6c757d; pointer-events:none; background:#fff; border-color:#dee2e6; }
.pagination .page-info { align-self:center; padding:0 0.5rem; font-size:0.875rem; color:#6c757d; }
.pagination .page-dropdown select { padding:0.25rem 0.5rem; border:1px solid #dee2e6; border-radius:0.25rem; }

進階擴展建議

  • Ajax 分頁:`render()` 回傳 JSON (`{html:'...', current:3, total:10}`),前端 `fetch` 替換列表區塊。
  • 游標分頁:大數據量 (百萬級) 避免 `OFFSET` 深分頁效能問題,改用 `WHERE id > ? LIMIT ?` + `next_cursor`。
  • SEO 友善:加入 `rel="prev/next"`、``、結構化資料 `ItemList`。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved