一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

在PHP分页中如何正确集成分类与子分类过滤功能

时间:2026-07-07 11:18:51 编辑:袖梨 来源:一聚教程网

本文详解如何解决「分类/子分类筛选 + 分页」场景下常见的数据丢失、页码跳变、总数错乱等问题,核心在于服务端统一维护过滤状态与分页上下文,避免前端页码变量与后端查询逻辑脱节。

本文详解如何解决「分类/子分类筛选 + 分页」场景下常见的数据丢失、页码跳变、总数错乱等问题,核心在于服务端统一维护过滤状态与分页上下文,避免前端页码变量与后端查询逻辑脱节。

在你提供的 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页,数据始终连贯、准确、高效。

热门栏目