本文约 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 的归档页模板查找顺序是「从具体到通用」的一条回退链:
archive-{post_type}.php(例如archive-case.php)archive.phpindex.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 模板层级详解 是同一套逻辑,理解一次可通用于分类页、标签页和作者页。
保姆级实操
- 确认归档页存在:检查 CPT 注册参数里
has_archive是否为 true。设为 true 时归档链接默认是文章类型名,也可指定字符串自定义别名。 - 建一个统一模板:在子主题里新建
archive-uniform.php,结构直接照搬主循环写法,只保留一份 HTML 骨架和样式类。 - 用
template_include挂载:判断is_post_type_archive()且属于目标类型数组时,返回统一模板。新增类型只需往数组里加一项。 - 统一主循环参数:在
pre_get_posts里集中设置posts_per_page、orderby、order,用一份配置数组管理,避免每个模板各写一套。 - 刷新固定链接:后台「设置 → 固定链接」直接点一次保存,或代码里调用
flush_rewrite_rules()。不要在每次请求都调用,只在主题启用或类型注册变更时执行一次。 - 验证:逐个访问三个归档页,确认 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 分类筛选功能实现 的做法。
