本文目录导读:

这是一个用 PHP 实现的分页功能完整案例,包含数据库结构、核心代码、前端展示及注释说明。
环境准备
- PHP 7.0+
- MySQL 5.6+
- Web 服务器:Apache/Nginx
数据库结构
创建一张测试表 articles:
CREATE DATABASE IF NOT EXISTS `demo` DEFAULT CHARSET utf8mb4; USE `demo`; CREATE TABLE `articles` ( `id` int(11) NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入50条测试数据 INSERT INTO `articles` (`title`, `created_at`) VALUES1', '2024-01-01 10:00:00'),2', '2024-01-02 10:00:00'), ... -- 实际可写脚本批量插入,此处省略50', '2024-02-19 10:00:00');
PHP 分页核心类 Pagination.php
<?php
/**
* 分页类 - 支持样式自定义和URL参数处理
*/
class Pagination {
private $total; // 总记录数
private $pageSize; // 每页显示条数
private $currentPage; // 当前页码
private $totalPages; // 总页数
private $url; // 基础URL(不含page参数)
/**
* @param int $total 总记录数
* @param int $pageSize 每页显示条数
* @param int $currentPage 当前页码
*/
public function __construct($total, $pageSize = 10, $currentPage = 1) {
$this->total = max(0, intval($total));
$this->pageSize = max(1, intval($pageSize));
$this->currentPage = max(1, intval($currentPage));
$this->totalPages = ceil($this->total / $this->pageSize);
// 当前页不能超过总页数
if ($this->currentPage > $this->totalPages && $this->totalPages > 0) {
$this->currentPage = $this->totalPages;
}
// 生成基础URL(剔除原有的page参数)
$this->buildUrl();
}
/**
* 构造基础URL,保留其他GET参数
*/
private function buildUrl() {
$params = $_GET;
unset($params['page']); // 移除page参数
$query = http_build_query($params);
$this->url = $_SERVER['PHP_SELF'] . ($query ? '?' . $query . '&page=' : '?page=');
}
/**
* 获取SQL中的LIMIT起始位置
* @return int
*/
public function getOffset() {
return ($this->currentPage - 1) * $this->pageSize;
}
/**
* 获取每页条数
* @return int
*/
public function getPageSize() {
return $this->pageSize;
}
/**
* 生成HTML分页导航(Bootstrap风格,可替换class)
* @return string
*/
public function render() {
if ($this->totalPages <= 1) {
return ''; // 只有一页不显示分页
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// 上一页
if ($this->currentPage == 1) {
$html .= '<li class="page-item disabled"><span class="page-link">上一页</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $this->url . ($this->currentPage - 1) . '">上一页</a></li>';
}
// 页码按钮 - 显示前后各2页,当前页高亮
$range = 2; // 显示前后页数
$start = max(1, $this->currentPage - $range);
$end = min($this->totalPages, $this->currentPage + $range);
// 第一页
if ($start > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $this->url . '1">1</a></li>';
if ($start > 2) {
$html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
}
for ($i = $start; $i <= $end; $i++) {
if ($i == $this->currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $this->url . $i . '">' . $i . '</a></li>';
}
}
// 最后一页
if ($end < $this->totalPages) {
if ($end < $this->totalPages - 1) {
$html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
$html .= '<li class="page-item"><a class="page-link" href="' . $this->url . $this->totalPages . '">' . $this->totalPages . '</a></li>';
}
// 下一页
if ($this->currentPage == $this->totalPages) {
$html .= '<li class="page-item disabled"><span class="page-link">下一页</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $this->url . ($this->currentPage + 1) . '">下一页</a></li>';
}
$html .= '</ul></nav>';
return $html;
}
/**
* 获取当前页码
* @return int
*/
public function getCurrentPage() {
return $this->currentPage;
}
/**
* 获取总页数
* @return int
*/
public function getTotalPages() {
return $this->totalPages;
}
}
主文件 index.php(数据库连接 + 分页调用)
<?php
require_once 'Pagination.php';
// 数据库配置
$host = 'localhost';
$dbname = 'demo';
$username = 'root';
$password = '123456';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('数据库连接失败: ' . $e->getMessage());
}
// 分页参数
$pageSize = 10; // 每页10条
$currentPage = isset($_GET['page']) ? intval($_GET['page']) : 1;
if ($currentPage < 1) $currentPage = 1;
// 1. 获取总记录数
$stmt = $pdo->query("SELECT COUNT(*) FROM articles");
$total = $stmt->fetchColumn();
// 2. 初始化分页类
$pagination = new Pagination($total, $pageSize, $currentPage);
// 3. 获取当前页数据
$offset = $pagination->getOffset();
$stmt = $pdo->prepare("SELECT * FROM articles ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $pageSize, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">PHP分页案例</title>
<!-- 引入Bootstrap样式(仅用于UI,非必须) -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h2 class="mb-4">文章列表(共<?php echo $total; ?>条,每页<?php echo $pageSize; ?>条)</h2>
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th>ID</th>
<th>标题</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<?php foreach ($articles as $article): ?>
<tr>
<td><?php echo $article['id']; ?></td>
<td><?php echo htmlspecialchars($article['title']); ?></td>
<td><?php echo $article['created_at']; ?></td>
</tr>
<?php endforeach; ?>
<?php if (count($articles) == 0): ?>
<tr><td colspan="3" class="text-center">暂无数据</td></tr>
<?php endif; ?>
</tbody>
</table>
<!-- 分页导航 -->
<div class="d-flex justify-content-center">
<?php echo $pagination->render(); ?>
</div>
<!-- 显示当前页码信息 -->
<p class="text-center text-muted">
第 <?php echo $pagination->getCurrentPage(); ?> / <?php echo $pagination->getTotalPages(); ?> 页
</p>
</div>
</body>
</html>
代码说明
关键点
- SQL 安全:使用 PDO 参数绑定,防止 SQL 注入。
- URL 参数保留:
buildUrl()自动保留除了page外的所有 GET 参数,如?category=1&page=2正常跳转。 - 边界处理:
- 当前页自动限制在
[1, totalPages]范围内。 - 总记录数为 0 或只有一页时不显示分页。
- 当前页自动限制在
- 灵活性:分页类可独立复用,只需传入三个参数。
文件结构
project/
│ index.php # 主页面
│ Pagination.php # 分页类
└─ (可添加 config.php 统一管理数据库配置)
运行效果
- 访问
http://localhost/project/index.php即可看到分页列表。 - 点击页码或上下页正常跳转,地址栏
?page=N自动变化。
扩展建议
- 样式定制:修改
render()中的 HTML 结构即可适配任意 CSS 框架。 - AJAX 分页:在前端用 JS 捕获点击事件,通过
fetch异步请求?page=X&ajax=1实现无刷新分页。 - 大数据量优化:当数据量极大时,COUNT(*) 可能较慢,可改用
EXPLAIN估算或缓存总数。
如果你需要更具体的功能(如搜索分页、排序分页、AJAX 版本),可以告诉我,我会为你补充相应的实现。