搜索全站
主题 14 分钟阅读

paginate_links 与自定义查询分页:paged 参数全解

自定义 WP_Query 的分页按钮点了跳回第一页?讲清 paged 与 page 两个参数的区别、paginate_links 的完整参数、固定链接格式下的 base 写法与第 2 页 404 的修复。

U
UFO_PF 2019-06-09 · 阅读 14 分钟
pa 主题
阅读提示
本文约 5,396 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:分页按钮长得都对,点第 2 页却跳回第 1 页

用 new WP_Query() 做了个”全部产品”页面,paginate_links() 渲染出的页码 1 2 3 … 很正常。鼠标移上去看链接地址:/products/page/2/,点下去页面刷新,内容还是第 1 页的产品。或者更糟:直接 404。

问题出在 paged 参数。自定义查询不会自动读取 URL 里的页码,必须手动传进 WP_Query,而且静态首页与文章列表页用的参数名还不一样。

原理分析:paged 与 page,两个参数两个场景

场景 参数名 读取方式
普通归档、搜索、分类页 paged get_query_var( 'paged' )
静态首页(front page)上的分页 page get_query_var( 'page' )

原因:静态首页的 URL 是 /page/2/,而 WordPress 把 /page/N/ 这个段在普通页面上解释为”文章内部分页”(<!--nextpage-->),在静态首页上才解释为列表分页。所以:

// 通用写法,两种场景都覆盖
$paged = get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : 1;
if ( is_front_page() && get_query_var( 'page' ) ) {
    $paged = absint( get_query_var( 'page' ) );
}

第二条容易漏的规律:paginate_links() 的 base 与 format 要跟固定链接结构匹配。

固定链接结构 URL 形态 base / format
朴素(?p=123) /?paged=2 format => '?paged=%#%',base 用 get_pagenum_link(1) . '%_%'
数字型 /page/2/ format => 'page/%#%/'
带后缀 /page/2/(在 /products/ 下) base => '/products/page/%#%/'

%#% 是页码占位符,%_% 是”是否分页”占位符,两者都由核心替换。

第三个坑是 max_num_pages:自定义查询的分页总数必须来自该查询自己的 $q->max_num_pages,不能拿全局 $wp_query 的。

保姆级实操

第 1 步:把 paged 传进 WP_Query

$paged = max( 1, get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : absint( get_query_var( 'page' ) ) );

$q = new WP_Query( array(
    'post_type'      => 'product',
    'posts_per_page' => 12,
    'paged'          => $paged,
    // 分页时必须让它算总数,不要开 no_found_rows
) );

while ( $q->have_posts() ) :
    $q->the_post();
    get_template_part( 'template-parts/content/content', 'card' );
endwhile;

第 2 步:用 paginate_links 输出页码

$big = 999999999;   // 用一个不可能出现的页码,保证 get_pagenum_link 生成完整 URL

echo paginate_links( array(
    'base'      => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    'format'    => '?paged=%#%',
    'current'   => $paged,
    'total'     => $q->max_num_pages,
    'prev_text' => '&laquo; 上一页',
    'next_text' => '下一页 &raquo;',
    'mid_size'  => 2,     // 当前页两侧各显示几页
    'end_size'  => 1,     // 首尾各显示几页
    'type'      => 'list',// 输出 <ul><li>,便于写样式
) );

type 可选 plain(默认,<a> 串)、list(<ul class="page-numbers">)、array(返回数组,供进一步处理)。做主题时一律用 list,样式更好控制。

第 3 步:带自定义筛选条件时保留查询串

页面上有排序下拉、价格筛选时,翻页必须带上这些参数,否则第 2 页筛选条件丢失:

echo paginate_links( array(
    'base'      => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    'format'    => '?paged=%#%',
    'current'   => $paged,
    'total'     => $q->max_num_pages,
    'add_args'  => array(
        'orderby' => isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : '',
        'min_price' => isset( $_GET['min_price'] ) ? absint( $_GET['min_price'] ) : '',
    ),
    'add_fragment' => '',
    'type'      => 'list',
) );

add_args 里的值会被 urlencode 后拼接,但仍要自己先 sanitize_* 清理——add_args 只负责拼 URL,不做安全过滤。

第 4 步:主查询分页直接用 the_posts_pagination

主循环(不需要自定义查询)时不必手搓 paginate_links,核心提供了封装:

the_posts_pagination( array(
    'mid_size'  => 2,
    'prev_text' => __( '上一页', 'mimangfei' ),
    'next_text' => __( '下一页', 'mimangfei' ),
    'screen_reader_text' => __( '文章导航', 'mimangfei' ),
) );

它内部读的是全局 $wp_query->max_num_pages,只适用于主循环。

第 5 步:文章内部分页用 wp_link_pages

一篇长文拆成多页(<!--nextpage--> 分割)时,用的是另一个函数:

// 在 single.php 的 the_content() 之后
wp_link_pages( array(
    'before'      => '<nav class="page-links"><span class="page-links__title">' . esc_html__( '分页:', 'mimangfei' ) . '</span>',
    'after'       => '</nav>',
    'link_before' => '<span class="page-links__num">',
    'link_after'  => '</span>',
    'next_or_number' => 'number',
    'pagelink'    => '%',
) );

注意:文章内部分页用的是 get_query_var( 'page' ),与列表分页的 paged 不是一回事,别混用。

第 6 步:修复第 2 页 404

自定义文章类型归档页出现”第 1 页正常、第 2 页 404″,多半是 posts_per_page 与后台「设置 → 阅读」的”每页文章数”冲突。解决方式是在 pre_get_posts 里统一数量:

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_post_type_archive( 'product' ) ) {
        $query->set( 'posts_per_page', 12 );
    }
} );

如果用的是自带分页的静态页面(把某个 Page 当”产品列表页”用),必须让主查询的 posts_per_page 与自定义查询一致,否则主查询判定”第 2 页不存在”直接抛 404。自定义查询本身的写法与常见误区见WP_Query 与主查询的正确用法。

常见踩坑

  1. 忘记把 paged 传进 WP_Query。 页码显示正常但内容一直是第 1 页,这是最高频的失误。
  2. 开了 no_found_rows => true。 它会跳过总数计算,max_num_pages 恒为 1,分页只显示一页。分页场景必须关掉。
  3. total 用了 $wp_query->max_num_pages。 自定义查询要用 $q->max_num_pages。
  4. 静态首页没处理 page 参数。 静态首页上 get_query_var('paged') 恒为 0,必须回退到 get_query_var('page')。
  5. paginate_links 的 base 写死。 站点换固定链接结构后分页链接全错。用 get_pagenum_link( $big ) 动态生成。
  6. 案例: 某外贸站做了个”全部案例”页面,用 new WP_Query 查 case 类型,前台分页点击后跳转 404。排查发现:该页面是一个普通 Page,主查询只查出 1 条(这个页面自己),于是 WordPress 判定 /cases/page/2/ 不存在,在模板执行前就抛了 404。修复方式不是改 paginate_links,而是改用「归档页 + pre_get_posts 控制数量」,或给该 Page 加一条重写规则,让主查询也按同样数量分页。判断依据就是:paginate_links 只负责”生成链接”,页面存不存在由主查询说了算。归档页的固定链接与重写规则细节见自定义文章类型归档模板。
  7. wp_reset_postdata() 放在了分页之前。 会导致 paginate_links 拿到的 max_num_pages 出错,顺序应为:循环 → wp_reset_postdata() → 分页(分页只依赖 $q 对象,不受影响,但依赖 $post 的其他函数会出错)。

总结

自定义查询分页的完整公式是三行:$paged 正确读取(paged 优先、page 兜底)→ paged 传进 WP_Query 且不开 no_found_rows → paginate_links 的 current 用 $paged、total 用 $q->max_num_pages、base 用 get_pagenum_link( $big ) 动态生成。主循环直接用 the_posts_pagination(),长文内部分页用 wp_link_pages()。出现 404 时先判断”是链接错了还是页面本身不存在”——后者要去改主查询的 posts_per_page,而不是改分页函数。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。