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

PHP + AJAX 無刷新分頁實戰:完整項目結構與代碼模組化

     編輯:PHP 綜合知識

將 AJAX 分頁拆解為可維護的模組化結構:前端組件化、後端 API 標準化、資料庫遷移腳本、樣式隔離。適合團隊協作與長期維護的專案架構。

專案目錄結構

/ajax-pager-demo
├── index.php              # 入口頁
├── api/
│   ├── page.php           # 分頁 API 端點
│   └── config.php         # 資料庫配置
├── assets/
│   ├── js/
│   │   └── ajax-pager.js  # 前端組件
│   └── css/
│       └── pager.css      # 分頁樣式
├── templates/
│   └── page-fragment.php  # HTML 片段模板
├── sql/
│   └── brand.sql          # 測試資料
└── README.md

核心模組代碼

1. 後端配置:api/config.php

 [
        'dsn' => 'mysql:host=127.0.0.1;dbname=demo;charset=utf8mb4',
        'user' => 'root',
        'pass' => '123456',
        'options' => [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ],
    ],
    'page' => [
        'default_size' => 10,
        'max_size' => 100,
    ],
];

2. API 端點:api/page.php

query('SELECT COUNT(*) FROM brand')->fetchColumn();
$pageCount = max(1, (int)ceil($total / $size));
$page = min($page, $pageCount);

$offset = ($page - 1) * $size;
$stmt = $pdo->prepare('SELECT * FROM brand ORDER BY id DESC LIMIT ?, ?');
$stmt->execute([$offset, $size]);
$rows = $stmt->fetchAll();

// 回傳 JSON 格式(現代化:前端渲染)
if (isset($_GET['format']) && $_GET['format'] === 'json') {
    header('Content-Type: application/json');
    echo json_encode([
        'data' => $rows,
        'pagination' => [
            'page' => $page,
            'size' => $size,
            'total' => $total,
            'pageCount' => $pageCount,
        ],
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

// 相容模式:直接輸出 HTML 片段
require __DIR__ . '/../templates/page-fragment.php';

3. 前端組件:assets/js/ajax-pager.js (ES6 類)

class AjaxPager {
    #container = null;
    #apiUrl = '';
    #page = 1;
    #size = 10;
    #abortController = null;

    constructor(containerSelector, apiUrl, options = {}) {
        this.#container = document.querySelector(containerSelector);
        this.#apiUrl = apiUrl;
        this.#size = options.pageSize || 10;
        this.#render = options.render || this.#defaultRender.bind(this);
        this.#bindEvents();
        this.load(1);
    }

    #bindEvents() {
        this.#container.addEventListener('click', e => {
            const link = e.target.closest('[data-page]');
            if (link) {
                e.preventDefault();
                this.load(parseInt(link.dataset.page, 10));
            }
        });
    }

    async load(page) {
        if (this.#abortController) this.#abortController.abort();
        this.#abortController = new AbortController();
        
        this.#showLoading();
        
        try {
            const url = `${this.#apiUrl}?page=${page}&size=${this.#size}&format=json`;
            const resp = await fetch(url, { signal: this.#abortController.signal });
            if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
            
            const { data, pagination } = await resp.json();
            this.#page = pagination.page;
            this.#render(data, pagination);
        } catch (err) {
            if (err.name !== 'AbortError') this.#showError(err.message);
        }
    }

    #defaultRender(data, pagination) {
        let html = '';
        data.forEach(row => {
            html += ``;
        });
        html += '
ID品牌
${row.id}${this.#escape(row.sp_brand)}
'; html += this.#renderPagination(pagination); this.#container.innerHTML = html; } #renderPagination(p) { let nav = ''; return nav; } #showLoading() { this.#container.innerHTML = '
載入中...
'; } #showError(msg) { this.#container.innerHTML = `
錯誤:${this.#escape(msg)}
`; } #escape(str) { return String(str).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } } // 初始化 document.addEventListener('DOMContentLoaded', () => { new AjaxPager('#result', 'api/page.php', { pageSize: 3 }); });

4. 樣式:assets/css/pager.css

/* assets/css/pager.css */
.data-table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
.data-table th, .data-table td { padding: 0.5rem; border: 1px solid #ddd; text-align: left; }
.data-table th { background: #f5f5f5; }

.pagination ul { display: flex; gap: 0.5rem; list-style: none; padding: 0; }
.pagination a { padding: 0.4rem 0.8rem; border: 1px solid #007bff; color: #007bff; 
    text-decoration: none; border-radius: 4px; transition: all 0.2s; }
.pagination a:hover { background: #007bff; color: #fff; }
.pagination .current { padding: 0.4rem 0.8rem; color: #666; }

.loading, .error { padding: 2rem; text-align: center; color: #666; }
.error { color: #dc3545; }

5. 入口頁:index.php




    
    AJAX 分頁模組化演示
    


    

品牌列表(AJAX 分頁)

資料庫初始化:sql/brand.sql

CREATE TABLE `brand` (
  `id` int(7) NOT NULL AUTO_INCREMENT,
  `sp_brand` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `brand` (`sp_brand`) VALUES 
('hello world'), ('你好'), ('恩'), ('fdsafdsafdsa'), ('fdsafdafdsafdas'),
('fdsafdsa'), ('fdsafdsafdas'), ('恩'), ('恩'), ('恩'), ('恩11'),
('恩'), ('恩'), ('恩'), ('恩'), ('恩'), ('恩'), ('恩18');

架構優勢總結

維度傳統混寫本模組化方案
前後端耦合強(HTML 直出)弱(JSON API)
測試性難測試API 可獨立測試
複用性低高(組件可多實例)
維護性代碼糾結職責清晰分離
擴展性困難易加入搜尋/排序/導出

此架構為後續接入 Vue/React/Alpine.js 等前端框架奠定基礎,只需替換 #render 方法即可無縫遷移。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved