搜索全站
主题 18 分钟阅读

category-{slug}.php 用法:按分类定制列表页的完整方案

想给某个分类做独立布局却总是不生效?讲清 category-{slug}.php 的命名与优先级、别名的正确查法、分类专属字段的读取,以及不用堆文件的条件判断写法。

U
UFO_PF 2022-02-21 · 阅读 18 分钟
ca 主题
阅读提示
本文约 7,198 字,预计阅读 18 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:想给”客户案例”做个单独的列表页,改了半天没反应

网站有个”客户案例”分类,希望它的列表页显示大图网格,其他分类保持普通列表。照着教程新建 category-cases.php,刷新后台、清缓存、改文件名大小写,折腾一小时毫无变化。

最后在数据库里一查:这个分类的别名是 cases-2。因为早期建过一个同名分类删掉了,WordPress 为了防止 URL 冲突,自动给新分类的别名加了数字后缀。

模板层级的规则不复杂,真正的坑在于”你以为的标识”和”WordPress 实际用的标识”不一致。

原理分析:分类模板的四级查找链

访问 /category/news/ 时,核心调用 get_category_template(),它交给 get_query_template() 按这个候选顺序找:

// wp-includes/template.php(简化)
function get_category_template() {
    $category = get_queried_object();
    $templates = array();

    if ( ! empty( $category->slug ) ) {
        $templates[] = "category-{$category->slug}.php";    // ① 按别名
        $templates[] = "category-{$category->term_id}.php"; // ② 按 ID
    }
    $templates[] = 'category.php';   // ③ 所有分类通用
    $templates[] = 'archive.php';    // ④ 所有归档通用

    return get_query_template( 'category', $templates );
}

四个要点:

要点 说明
标识是 slug 或 term_id 用分类名称(如”客户案例”)命名无效
顺序固定 别名 → ID → category.php → archive.php → index.php
子主题优先 每个候选都先在子主题目录找,再找父主题
最终兜底 index.php 找不到就走兜底,不会报错

get_queried_object() 返回当前查询的 WP_Term 对象,是分类模板里获取分类信息最可靠的方式:

$term = get_queried_object();
// $term->term_id / ->name / ->slug / ->description / ->count / ->parent / ->taxonomy

另一个高频混淆点:category.php 与 archive.php 的关系。archive.php 是”所有归档的兜底”,包括分类、标签、作者、日期、自定义类型归档;category.php 只管分类,且优先级更高。很多主题只有 archive.php,那分类页就会走它。

保姆级实操

第 1 步:确认分类的真实标识

# 列出所有分类的 ID 与别名(这才是文件名的依据)
wp term list category --fields=term_id,name,slug,count

后台也能看:「文章 → 分类目录 → 编辑」页面上的「别名(slug)」字段。注意:编辑框里如果显示的是老的别名,修改后要点”更新”才会生效,且改别名会让老 URL 失效(需要设置 301)。

确认当前页面真实使用的模板,加一段调试代码:

add_filter( 'template_include', function ( $template ) {
    if ( current_user_can( 'manage_options' ) && ! is_admin() ) {
        printf( '<!-- 模板:%s -->', esc_html( basename( $template ) ) );
    }
    return $template;
}, 99 );

第 2 步:创建分类专属模板

<?php
/** category-cases.php —— 客户案例分类:大图网格布局 */
get_header();

$term = get_queried_object();
?>
<main id="primary" class="site-main archive-cases">
    <header class="page-header page-header--cases">
        <h1 class="page-title"><?php echo esc_html( $term->name ); ?></h1>
        <?php if ( $term->description ) : ?>
            <div class="archive-description"><?php echo wp_kses_post( $term->description ); ?></div>
        <?php endif; ?>
        <p class="term-count">
            <?php
            printf(
                esc_html__( '共 %d 个案例', 'mimangfei' ),
                (int) $term->count
            );
            ?>
        </p>
    </header>

    <?php if ( have_posts() ) : ?>
        <div class="cases-grid">
            <?php
            while ( have_posts() ) :
                the_post();
                get_template_part( 'template-parts/content/content', 'case' );
            endwhile;
            ?>
        </div>
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p><?php esc_html_e( '该分类下暂无内容。', 'mimangfei' ); ?></p>
    <?php endif; ?>
</main>
<?php
get_footer();

第 3 步:给分类加专属字段

分类默认只有名称、别名、描述三个字段。需要”分类封面图””SEO 标题”这类额外字段时,用 term meta:

// 保存 term meta(挂在插件或主题里均可,建议插件)
add_action( 'created_category', 'mmf_save_cat_meta' );
add_action( 'edited_category', 'mmf_save_cat_meta' );

function mmf_save_cat_meta( $term_id ) {
    if ( ! isset( $_POST['mmf_cat_image_nonce'] )
        || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['mmf_cat_image_nonce'] ) ), 'mmf_cat_image' ) ) {
        return;
    }
    if ( isset( $_POST['mmf_cat_image'] ) ) {
        update_term_meta( $term_id, 'mmf_cat_image', absint( $_POST['mmf_cat_image'] ) );
    }
}

前台读取:

$image_id = get_term_meta( $term->term_id, 'mmf_cat_image', true );
if ( $image_id ) {
    echo wp_get_attachment_image( (int) $image_id, 'hero', false, array( 'class' => 'archive-cover' ) );
}

update_term_meta/get_term_meta 自 WordPress 4.4 起可用,替代了旧的”把数据塞进 option”的做法。ACF(Advanced Custom Fields)等插件提供的分类字段,底层也是 term meta,读取方式一致。

第 4 步:不用堆文件——用过滤器按条件选模板

分类很多、但只有几种布局时,不要建十几个文件:

add_filter( 'category_template', function ( $template ) {
    $term = get_queried_object();
    if ( ! $term instanceof WP_Term ) {
        return $template;
    }

    // 这几个分类用大图网格
    $grid = array( 'cases', 'products', 'gallery' );
    if ( in_array( $term->slug, $grid, true ) ) {
        $alt = get_theme_file_path( 'template-parts/archive-grid.php' );
        if ( file_exists( $alt ) ) {
            return $alt;
        }
    }

    // 顶级分类用带子栏目导航的版本
    if ( 0 === (int) $term->parent ) {
        $alt = get_theme_file_path( 'template-parts/archive-with-children.php' );
        if ( file_exists( $alt ) ) {
            return $alt;
        }
    }

    return $template;
} );

get_theme_file_path() 会自动先查子主题、再查父主题,是 WordPress 4.7 起推荐的路径函数,比手写 get_stylesheet_directory() 更省心。

第 5 步:按分类调整每页数量

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_category( 'cases' ) ) {
        $query->set( 'posts_per_page', 9 );      // 网格布局每行 3 个,取 9 的倍数
    }
    if ( $query->is_category( 'news' ) ) {
        $query->set( 'posts_per_page', 20 );
    }
} );

is_category() 的参数可以是别名、ID、名称或数组,判断多个分类时传数组:is_category( array( 'cases', 'products' ) )。

第 6 步:输出子分类导航

分类模板里常需要列出当前分类的子分类:

$children = get_terms( array(
    'taxonomy'   => 'category',
    'parent'     => $term->term_id,
    'hide_empty' => true,
) );

if ( ! is_wp_error( $children ) && $children ) {
    echo '<ul class="subcat-nav">';
    foreach ( $children as $child ) {
        printf(
            '<li><a href="%1$s">%2$s <span class="count">(%3$d)</span></a></li>',
            esc_url( get_term_link( $child ) ),
            esc_html( $child->name ),
            (int) $child->count
        );
    }
    echo '</ul>';
}

get_terms() 返回 WP_Error 的可能不能忽略, taxonomy 不存在或参数错误时都会返回错误对象。

常见踩坑

  1. 用分类名称当文件名。 必须是别名(slug),中文名分类的别名往往是拼音或英文。
  2. 别名带 -2 后缀。 删过同名分类就会出现,用 wp term list category --fields=term_id,slug 确认。
  3. 改了别名没做 301。 老 URL 直接 404,需要在 .htaccess 或重定向插件里加规则。
  4. 文件名大小写不符。 Linux 下 Category-Cases.php 与 category-cases.php 是两个文件,查找会静默失败。页面模板上也有同样的大小写坑,参见自定义页面模板 Page Template。
  5. 子主题放了文件但父主题的 category.php 优先级更高。 不会——子主题的任何模板都优先于父主题同名文件;若发现相反,检查子主题的 style.css 有没有 Template 头。
  6. 案例: 某站给”行业动态”分类做了 category-news.php,本地测试正常,上线后无效。最后发现生产环境数据库里该分类的别名是 news(正确),但生产环境启用的是另一个子主题目录——文件被上传到了 wp-content/themes/parent/ 而非 wp-content/themes/child/。父主题更新时文件还被覆盖丢失了一次。教训是:模板文件一律放子主题,并用 wp option get stylesheet 确认当前启用的主题目录名。模板层级的完整规则见WordPress 模板层级图解。
  7. is_category() 在 archive.php 里判断失效。 在归档页里 is_category() 是可用的,但必须在主查询建立之后(模板阶段没问题,挂在 pre_get_posts 里要用 $query->is_category())。

总结

分类模板的核心就三句话:文件名用别名或 ID(category-{slug}.php / category-{id}.php),别名必须用 wp term list 核实过;模板里用 get_queried_object() 拿分类信息,用 get_term_meta() 读扩展字段;分类数量多时用 category_template 过滤器按条件选模板,别堆文件。每页数量用 pre_get_posts + is_category() 控制。排查”不生效”的固定顺序是:查别名 → 查当前真实模板 → 查文件是否在子主题目录。

U
UFO_PF作者

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

留下你的想法

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