
本文详解如何解决「分类/子分类筛选 + 分页」场景下常见的数据丢失、页码跳变、总数错乱等问题,核心在于服务端统一维护过滤状态与分页上下文,避免前端页码变量与后端查询逻辑脱节。
本文详解如何解决「分类/子分类筛选 + 分页」场景下常见的数据丢失、页码跳变、总数错乱等问题,核心在于服务端统一维护过滤状态与分页上下文,避免前端页码变量与后端查询逻辑脱节。
在你提供的 JavaScript 代码中,currentPage 是前端维护的全局变量,但问题根源在于:每次点击“下一页”时,前端仅传递 page 参数,却未同步携带当前激活的 subcategory_ids 或 category_ids 过滤条件。导致 get_products.php 或 get_product_category.php 在第2页请求时(如 ?page=2)实际执行的是无过滤的原始查询,从而返回空结果或错误数据集——这正是“点下一页显示0条、再返回首页也空白”的根本原因。
✅ 正确方案:服务端主导分页 + 过滤参数透传
分页不是前端计数器游戏,而是带状态的数据库查询过程。必须确保:
- 每次分页请求都携带完整的业务过滤条件(subcategory_ids / category_ids);
- 总记录数(total_count)和当前页数据必须基于同一 WHERE 条件计算;
- LIMIT 和 OFFSET 必须严格对应过滤后的结果集,而非全表。
▶ 示例:修复后的 PHP 后端逻辑(get_products.php)
<?php
// get_products.php —— 支持子分类过滤的健壮分页接口
require 'db.php'; // 假设已配置 PDO 连接 $pdo
// 1. 安全获取并校验参数
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
$page = max(1, $page);
$limit = 20; // 固定每页条数,禁止用户篡改
$offset = ($page - 1) * $limit;
// 2. 解析子分类过滤条件(支持逗号分隔,如 ?subcategory_ids=5,8,12)
$subcategoryIds = [];
if ($idsStr = filter_input(INPUT_GET, 'subcategory_ids', FILTER_SANITIZE_STRING)) {
$ids = array_filter(array_map('intval', explode(',', $idsStr)));
if (!empty($ids)) {
$subcategoryIds = $ids;
}
}
// 3. 构建动态 WHERE 条件(防SQL注入!使用预处理)
$whereClause = '';
$params = [];
if (!empty($subcategoryIds)) {
$placeholders = str_repeat('?,', count($subcategoryIds) - 1) . '?';
$whereClause = "WHERE subcategory_id IN ($placeholders)";
$params = $subcategoryIds;
}
// 4. 查询总记录数(关键!必须与数据查询条件完全一致)
$countSql = "SELECT COUNT(*) FROM products $whereClause";
$stmt = $pdo->prepare($countSql);
$stmt->execute($params);
$totalCount = (int)$stmt->fetchColumn();
// 5. 查询当前页数据
$sql = "SELECT id, fabric, design_no FROM products $whereClause ORDER BY id DESC LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute($params);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 6. 返回结构化响应
echo json_encode([
'products' => $products,
'total_count' => $totalCount,
'current_page' => $page,
'per_page' => $limit,
'last_page' => ceil($totalCount / $limit)
]);⚠️ 关键注意事项:
- 绝不拼接 SQL 字符串:所有用户输入(尤其是 ID 列表)必须通过 PDO::prepare() + bindValue() 绑定,杜绝 SQL 注入。
- total_count 与数据查询必须共用同一 WHERE 条件:这是分页正确的数学基础。若总数查全表,而数据查过滤后,必然导致页码错乱。
- 固定 $limit:避免恶意请求 ?limit=100000 导致 DoS;如需可配置,应在服务端白名单内校验(如仅允许 10/20/50)。
▶ 前端 JS 调用修正(关键!保持过滤状态)
你当前的 JS 在翻页时丢失了过滤参数。应重构 updateProductList(),使其始终携带最新选中的 ID 列表:
立即学习“PHP免费学习笔记(深入)”;
function buildUrl() {
const selectedSubIds = Array.from(
document.querySelectorAll(".subcategory-checkbox:checked")
).map(cb => cb.getAttribute("data-subcategory-id"));
const params = new URLSearchParams();
params.set('page', currentPage);
if (selectedSubIds.length > 0) {
params.set('subcategory_ids', selectedSubIds.join(','));
}
return `get_products.php?${params.toString()}`;
}
function updateProductList() {
fetch(buildUrl()) // ✅ 每次请求都含完整过滤参数
.then(r => r.json())
.then(data => {
totalProducts = data.total_count;
renderProducts(data.products);
// 动态更新分页按钮状态
const totalPages = Math.ceil(totalProducts / 20);
nextPageButton.disabled = currentPage >= totalPages;
prevPageButton.disabled = currentPage <= 1;
pageElement.textContent = `Page ${currentPage}`;
productCountElement.textContent = totalProducts;
});
}▶ 高级优化建议(百万级数据必看)
若商品表数据量极大(>50万行),传统 OFFSET 分页将严重退化:
- 第1000页需 OFFSET 19980 → MySQL 强制扫描前19980行再丢弃。
-
解决方案:游标分页(Cursor-based Pagination)
基于唯一有序字段(如 id 或 created_at)替代页码:-- 第一页 SELECT * FROM products WHERE subcategory_id IN (5,8) ORDER BY id DESC LIMIT 20; -- 下一页(取上一页最后一条的 id = 10234) SELECT * FROM products WHERE subcategory_id IN (5,8) AND id < 10234 ORDER BY id DESC LIMIT 20;
此方式性能恒定,但需前端保存 cursor 值(如 last_id),且不支持跳转任意页。
✅ 总结:分页+过滤的黄金法则
| 环节 | 正确做法 | 常见错误 |
|---|---|---|
| 参数传递 | 每次请求(含翻页)都携带全部过滤参数 | 仅首页传过滤,翻页时丢失 |
| 总数统计 | COUNT(*) 与数据查询使用完全相同的 WHERE 条件 | 总数查全表,数据查过滤后 |
| SQL 安全 | 全部用户输入用预处理绑定 | 字符串拼接 ID 列表 |
| 页码校验 | 后端强制 max(1, min($page, $lastPage)) | 信任前端 page 值直接计算 offset |
| 性能兜底 | 数据量 >10万行时启用游标分页 | 死守 LIMIT OFFSET 直至超时 |
遵循以上原则,你的分类筛选与分页将彻底解耦、稳定可靠——无论用户勾选1个子类还是10个,无论翻到第1页还是第200页,数据始终连贯、准确、高效。



















