商品分类树展开后分页总从第一页开始,是因为分类切换时未将cat_id等参数通过query配置透传至paginate(),导致翻页链接丢失上下文;必须在控制器中显式配置['query' => ['cat_id' => input('cat_id/d')]]并配合手动统计总数、安全渲染,才能保证分页正确联动。

为什么商品分类树展开后,分页列表总是从第一页开始?
因为分类切换时没把当前 cat_id 作为查询参数透传给分页链接,paginate() 默认只保留 page,其他参数(如分类 ID、搜索词)全被丢弃。点“下一页”就退回到无分类状态,数据对不上。
解决方法不是改模板里的 {$list->render()},而是从控制器源头补上 query 配置:
- 用
request()->param()一次性透传全部 GET 参数(含cat_id、keyword等) - 更稳妥的做法是显式列出关键参数:
['query' => ['cat_id' => input('cat_id/d'), 'keyword' => input('keyword')]] - 如果分类 ID 是 URL 路径的一部分(如
/goods/cat/7),需在路由定义里绑定参数,并在paginate()的query中手动塞进去
如何让分页列表只显示当前分类及其子分类下的商品?
不能靠前端传个 cat_id 就直接 where('cat_id', $cat_id)——这只能查主分类,漏掉扩展分类和所有子级。必须先查出分类路径集合,再用 UNION 或关联查询兜住数据范围。
ThinkPHP 本身不支持对 UNION 结果直接调用 paginate(),所以得拆成两步:
立即学习“PHP免费学习笔记(深入)”;
- 先用原生 SQL 查出符合条件的商品 ID 列表:
Db::query($sql),其中$sql包含SELECT goods_id FROM ... UNION SELECT goods_id FROM ... - 再用这些 ID 做二次查询:
GoodsModel::where('goods_id', 'in', $idList)->paginate(10) - 注意:第二次查询的
paginate()必须带上query配置,否则分类参数在翻页时又丢了
分页总数不准,最后几页空白或跳页是怎么回事?
paginate() 内部会自动执行一条 COUNT(*) 查询来算总条数,但遇到 UNION、子查询或复杂 JOIN 时,框架可能无法正确解析 COUNT 逻辑,导致 $list->total() 返回 0 或偏小值。
这时不能依赖自动统计,得手动传入准确总数:
- 在第一步查
goods_id列表时,顺手加个COUNT(DISTINCT goods_id)查询,拿到真实总数 - 调用
paginate()时,用第三个参数显式传入:paginate(10, false, ['total' => $realTotal]) - 别忘了同时传
query,否则总数对了,页码链接还是断的
模板里怎么安全输出分页 HTML 并避免 XSS?
{$list->render()} 输出的是原始 HTML 字符串,如果没做转义,攻击者可能通过构造恶意 cat_id 注入脚本。但也不能简单套 |htmlspecialchars,那会把所有标签变成文本,分页失效。
正确做法只有两个:
- 用
{$list->render()|raw}—— ThinkPHP 模板引擎默认会对变量自动转义,|raw是唯一允许渲染 HTML 的安全通道 - 确保
render()前没调用过toArray()、json()或循环遍历$list,否则分页对象上下文已丢失,render()返回空字符串 - 如果用了自定义分页模板(如复制了
paginate.tpl),检查里面是否硬编码了未过滤的参数,比如__URL__?cat_id={$Request.param.cat_id}这种写法必须改成{:input('cat_id')}或更严格的过滤
query 配置写在了 paginate() 外面。



















