搜索全站
主题 15 分钟阅读

自定义文章类型列表页样式统一:归档模板复用主循环的做法

新建的产品、案例、FAQ 三种文章类型列表页长得各不相同?本文讲清归档模板层级与查找顺序,给出一套模板复用、主循环参数统一、固定链接刷新的完整方案。

U
UFO_PF 2021-04-24 · 阅读 15 分钟
自定 主题
阅读提示
本文约 5,797 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:三种内容类型,三个长得不一样的列表页

站点做到一定规模,往往会注册多个自定义文章类型(Custom Post Type,简称 CPT):产品、案例、FAQ、下载、课程……注册完发现一个尴尬问题——每个类型的列表页长得都不一样。有的用了主题的 archive.php,两栏带侧边栏;有的因为没有专属模板,直接回退到 index.php,变成全宽单栏;还有的每页显示条数和主博客不一致,排序也不一样。前台看着像三个不同的网站拼在一起。

很多人的做法是逐个复制 archive-product.php、archive-case.php、archive-faq.php,每个文件里改几行样式。文件一多,改一处公共样式要动三份文件,久而久之必然不同步。更麻烦的是这类复制往往连同循环代码一起复制,主循环里的 posts_per_page 写死,后台「设置 → 阅读」的条数设置对它完全无效。本文给出复用方案:一个统一模板 + 主循环参数集中管理,三种类型共用同一套结构和样式。

原理分析:模板层级与回退链

WordPress 的归档页模板查找顺序是「从具体到通用」的一条回退链:

  1. archive-{post_type}.php(例如 archive-case.php)
  2. archive.php
  3. index.php

这意味着只要主题里有 archive.php,所有带 has_archive 的文章类型都会用它——理论上样式本来就该统一。实际不统一的常见原因有三:一是主题没提供 archive.php,直接回退到 index.php;二是主题为某个常见类型(比如 product)单独做了模板;三是 CPT 注册时 has_archive 设成了 false,压根没有归档页。

同时要区分两个概念:模板文件决定长什么样,主循环参数决定取哪些数据。后者由 WP_Query 决定,而前台主查询在 pre_get_posts 动作里可以安全地改。pre_get_posts 传入的是查询对象引用,必须用 $query->is_main_query() 判断,否则会误伤小工具、菜单和后台查询。

环节 控制手段 常见错误
有没有归档页 register_post_type 的 has_archive 设成 false 后归档页 404
用哪个模板 archive-{post_type}.php > archive.php 主题缺少 archive.php
强制指定模板 template_include 过滤器 忘记判断 is_post_type_archive()
取多少条 pre_get_posts 里 set('posts_per_page') 在模板里写死参数
排序 pre_get_posts 里 set('orderby') 后台设置被覆盖
链接是否生效 固定链接刷新 注册后不刷新导致 404

模板层级机制本身和 WordPress 模板层级详解 是同一套逻辑,理解一次可通用于分类页、标签页和作者页。

保姆级实操

  1. 确认归档页存在:检查 CPT 注册参数里 has_archive 是否为 true。设为 true 时归档链接默认是文章类型名,也可指定字符串自定义别名。
  2. 建一个统一模板:在子主题里新建 archive-uniform.php,结构直接照搬主循环写法,只保留一份 HTML 骨架和样式类。
  3. 用 template_include 挂载:判断 is_post_type_archive() 且属于目标类型数组时,返回统一模板。新增类型只需往数组里加一项。
  4. 统一主循环参数:在 pre_get_posts 里集中设置 posts_per_page、orderby、order,用一份配置数组管理,避免每个模板各写一套。
  5. 刷新固定链接:后台「设置 → 固定链接」直接点一次保存,或代码里调用 flush_rewrite_rules()。不要在每次请求都调用,只在主题启用或类型注册变更时执行一次。
  6. 验证:逐个访问三个归档页,确认 HTML 结构一致、分页条数一致、分页链接可点击且第二页 404 不存在。
<?php
// 子主题 functions.php:注册时务必开启归档
add_action( 'init', function() {
    register_post_type( 'case', array(
        'labels'      => array( 'name' => '客户案例', 'singular_name' => '案例' ),
        'public'      => true,
        'has_archive' => true,
        'rewrite'     => array( 'slug' => 'case', 'with_front' => false ),
        'supports'    => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'menu_icon'   => 'dashicons-portfolio',
        'show_in_rest'=> true,
    ) );
} );
<?php
// 多个类型共用一个归档模板
add_filter( 'template_include', function( $template ) {
    $uniform_types = array( 'case', 'faq', 'download' );
    if ( is_post_type_archive( $uniform_types ) ) {
        $new = locate_template( 'archive-uniform.php' );
        if ( $new ) {
            return $new;
        }
    }
    return $template;
} );
<?php
// archive-uniform.php:一份骨架,全站复用
get_header();
?>
<main id="primary" class="site-main archive-uniform">
    <header class="page-header">
        <h1 class="page-title"><?php echo esc_html( post_type_archive_title( '', false ) ); ?></h1>
        <?php
        $desc = get_the_post_type_description();
        if ( $desc ) {
            echo '<div class="archive-description">' . wp_kses_post( $desc ) . '</div>';
        }
        ?>
    </header>

    <?php if ( have_posts() ) : ?>
        <div class="archive-grid">
            <?php
            while ( have_posts() ) :
                the_post();
                ?>
                <article id="post-<?php the_ID(); ?>" <?php post_class( 'archive-card' ); ?>>
                    <?php if ( has_post_thumbnail() ) : ?>
                        <a class="archive-card__thumb" href="<?php the_permalink(); ?>">
                            <?php the_post_thumbnail( 'medium_large', array( 'loading' => 'lazy' ) ); ?>
                        </a>
                    <?php endif; ?>
                    <h2 class="archive-card__title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                    <div class="archive-card__excerpt"><?php the_excerpt(); ?></div>
                </article>
                <?php
            endwhile;
            ?>
        </div>
        <?php the_posts_pagination( array( 'mid_size' => 2 ) ); ?>
    <?php else : ?>
        <p><?php esc_html_e( '暂无内容。', 'mimangfei' ); ?></p>
    <?php endif; ?>
</main>
<?php
get_footer();
<?php
// 主循环参数集中管理
add_action( 'pre_get_posts', function( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    $uniform_types = array( 'case', 'faq', 'download' );
    if ( $query->is_post_type_archive( $uniform_types ) ) {
        $query->set( 'posts_per_page', 12 );
        $query->set( 'orderby', 'date' );
        $query->set( 'order', 'DESC' );
    }
} );

常见踩坑

  • has_archive 写成字符串却没刷链接:has_archive => 'cases' 改完后必须刷新固定链接,否则新旧别名冲突导致 404。
  • pre_get_posts 漏判 is_main_query():侧边栏的「最新案例」小工具也会跟着变成 12 条,页面错乱。
  • 在模板里用 query_posts() 覆盖主循环:这是老写法,会重置查询对象并引发分页异常,应当改用 pre_get_posts。
  • 每次请求都 flush_rewrite_rules():重写规则存在数据库里,频繁刷新会造成明显写入开销,还可能引发间歇性 404。
  • 统一模板里写死某个类型的字段名:FAQ 没有缩略图、下载有额外字段,直接复用同一段渲染逻辑会出现空标签。用 post_type_exists() 或 get_post_type() 做分支。
  • 案例:一个 B2B 站点有产品、案例、白皮书三个类型,三个归档页各用一个复制出来的模板。改版时设计师要求统一卡片圆角,运营改了两个文件就上线,结果白皮书页仍是旧样式,被客户指出「网站没做完」。改用 template_include 统一到 archive-uniform.php 后,改一处即全站生效,后续新增「视频」类型只加了一行数组项。
  • 分页第二页 404:posts_per_page 与后台阅读设置不一致,且归档页总数少于预期的翻页数。可结合 WordPress 固定链接设置 检查重写规则。

总结

判定是否需要统一归档模板,看两点:一是主题根目录有没有 archive.php(没有就必须自建统一模板),二是各类型的每页条数与排序是否一致(不一致就必须用 pre_get_posts 收敛)。落地顺序是「注册参数确认 → 统一模板 → 集中改查询 → 刷链接 → 验证分页」。统一之后,新增一个内容类型的边际成本就只剩一行数组项,这是长期维护里最划算的一次投入。若列表页还需要筛选交互,可再参考 WordPress 分类筛选功能实现 的做法。

U
UFO_PF作者

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

留下你的想法

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