返回

WordPress首页显示多个文章归档?3种方法解决

php

WordPress 首页显示多个文章归档?看这篇就够了

咱们直接点,你是不是碰到了这个头疼的问题:想在 WordPress 网站的首页(就是访客一进来看到的那个页面)同时展示来自不同自定义文章类型(Custom Post Types)的归档内容?比如说,你搞了个叫 gemer 的文章类型,又搞了个叫 spis 的,都有各自的归档模板(archive-gemer.phparchive-spis.php),现在就想让首页把这两个归档里的帖子都列出来。

你可能尝试改了 functions.php,用了 pre_get_posts 钩子,或者在 front-page.php 文件里直接用 get_template_part 来加载那两个归档模板,结果发现,首页要么只显示些 HTML 骨架,要么内容根本不对,WordPress 的循环(Loop)好像没起作用。

别急,这事儿能搞定。咱们来分析下为啥你之前的尝试可能没成功,然后给出几种靠谱的解决方案。

问题出在哪儿?

你遇到的情况,根源通常在这几个地方:

  1. pre_get_posts 的误用 : 这个钩子非常强大,它允许你在 WordPress 执行主查询(Main Query)之前修改查询参数。但它主要是用来调整 查询条件 的(比如查哪些文章类型、按什么排序),而不是用来 直接输出 HTML 内容 的。你在 pre_get_posts 的回调函数里用 include 或者 ob_start/ob_get_clean 来加载模板文件并 echo 输出,这完全用错了地方。这时候主查询还没真正获取数据呢,更别说模板文件里的 have_posts() / the_post() 循环了。
  2. front-page.phpget_template_part 的理解 : front-page.php 文件确实是 WordPress 用来显示网站首页的最高优先级模板。在里面使用 get_template_part('archive', 'gemer')get_template_part('archive', 'spis') 会包含 archive-gemer.phparchive-spis.php 文件里的代码。问题是,如果这两个模板文件里都用了标准的 if (have_posts()) : while (have_posts()) : ... 循环,它们默认会尝试显示 主查询 的结果。如果你没有正确地为它们准备独立的查询数据,它们要么显示空(如果主查询没结果或不符合条件),要么会重复显示主查询的结果。单纯地包含模板文件,并不会自动为每个模板启动一个它“应该”对应的查询。
  3. 首页设置与模板文件 : WordPress 的“设置” -> “阅读”里的“您的主页显示”选项会影响首页用哪个模板。
    • 如果选的是“您的最新文章 ”,WordPress 会优先查找 home.php,其次是 index.php 来显示。这种情况下,is_home() 这个条件标签会返回 true,而 is_front_page() 返回 false
    • 如果选的是“一个静态页面 ”,并指定了一个页面作为“主页”,WordPress 会优先查找 front-page.php,其次是根据页面模板层级去查找(比如 page-slug.php, page-id.php, page.php)。这种情况下,is_front_page() 返回 trueis_home() 返回 false(除非你把“文章页”也设置成了同一个静态页面,这不常见)。
      你之前的代码里似乎混淆了 is_front_page() 的检查,而且试图在“最新文章”设置下,通过修改查询类型来强行模拟归档页行为,路子就走偏了。

了解了这些,解决起来就思路清晰了。

解决方案

下面提供几种解决这个问题的实用方法,你可以根据自己的具体情况和偏好来选择。

方案一:使用静态首页 + 多个自定义查询 (推荐)

这是最常用、最灵活也最符合 WordPress 设计思路的方法。简单说,就是专门创建一个页面用作首页,然后在这个页面的模板文件里,手动发起多个查询来分别获取 gemerspis 的文章列表。

原理与作用:

  • 将首页内容与标准的博客文章流解耦,给你完全的控制权。
  • 通过创建独立的 WP_Query 对象,可以在一个页面上运行多个互不干扰的文章查询循环。

操作步骤:

  1. 设置静态首页:

    • 登录 WordPress后台。
    • 创建一个新的“页面”(不是文章),标题可以叫“首页”或者其他你喜欢的名字,内容暂时留空。
    • 进入“设置” -> “阅读”。
    • 在“您的主页显示”选项中,选择“一个静态页面”。
    • 在“主页”下拉菜单中,选择你刚刚创建的那个“首页”页面。
    • (可选)如果你还想保留一个显示最新博客文章的地方,可以再创建一个“博客”页面,并在“文章页”下拉菜单中选择它。
    • 保存更改。
  2. 创建 front-page.php 模板:

    • 在你的主题文件夹下(比如 /wp-content/themes/your-theme/),创建一个名为 front-page.php 的文件。如果它已存在,就编辑它。这个文件会自动被用作你指定的静态首页的模板。
  3. front-page.php 中编写代码:

    <?php get_header(); // 加载头部 ?>
    
    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">
    
            <div class="gemer-archive-section">
                <h1>Gemer Content</h1>
                <?php
                // 第一个自定义查询:获取 'gemer' 类型的文章
                $args_gemer = array(
                    'post_type'      => 'gemer', // 指定文章类型
                    'posts_per_page' => 5,       // 每页显示多少篇,-1 表示全部
                    // 你还可以加其他参数,比如排序 'orderby' => 'date', 'order' => 'DESC'
                );
                $query_gemer = new WP_Query( $args_gemer );
    
                // 'gemer' 的循环
                if ( $query_gemer->have_posts() ) :
                    while ( $query_gemer->have_posts() ) : $query_gemer->the_post();
                        // 这里是你展示 'gemer' 文章的 HTML 结构
                        ?>
                        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                            <header class="entry-header">
                                <?php the_title( sprintf( '<h2 class="entry-title"><a href="%s" rel="bookmark">', esc_url( get_permalink() ) ), '</a></h2>' ); ?>
                            </header><!-- .entry-header -->
    
                            <div class="entry-content">
                                <?php the_excerpt(); // 或者用 the_content() 显示全文 ?>
                            </div><!-- .entry-content -->
                        </article><!-- #post-## -->
                        <?php
                    endwhile;
                    wp_reset_postdata(); // !!! 非常重要:重置主查询数据,以免影响后续查询或主循环
                else :
                    // 如果 'gemer' 类型没有文章
                    echo '<p>暂时没有 Gemer 内容。</p>';
                endif;
                ?>
            </div><!-- .gemer-archive-section -->
    
            <hr> <!-- 加个分隔线,方便看 -->
    
            <div class="spis-archive-section">
                <h1>Spis Content</h1>
                <?php
                // 第二个自定义查询:获取 'spis' 类型的文章
                $args_spis = array(
                    'post_type'      => 'spis',
                    'posts_per_page' => 5,
                );
                $query_spis = new WP_Query( $args_spis );
    
                // 'spis' 的循环
                if ( $query_spis->have_posts() ) :
                    while ( $query_spis->have_posts() ) : $query_spis->the_post();
                        // 这里是你展示 'spis' 文章的 HTML 结构 (可以和 gemer 一样,也可以不同)
                        ?>
                        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                            <header class="entry-header">
                                <?php the_title( sprintf( '<h2 class="entry-title"><a href="%s" rel="bookmark">', esc_url( get_permalink() ) ), '</a></h2>' ); ?>
                            </header><!-- .entry-header -->
    
                            <div class="entry-content">
                                <?php the_excerpt(); ?>
                            </div><!-- .entry-content -->
                        </article><!-- #post-## -->
                        <?php
                    endwhile;
                    wp_reset_postdata(); // !!! 同样重要:重置查询数据
                else :
                    // 如果 'spis' 类型没有文章
                    echo '<p>暂时没有 Spis 内容。</p>';
                endif;
                ?>
            </div><!-- .spis-archive-section -->
    
        </main><!-- #main -->
    </div><!-- #primary -->
    
    <?php get_sidebar(); // 如果需要,加载侧边栏 ?>
    <?php get_footer(); // 加载尾部 ?>
    

代码解释:

  • get_header(), get_sidebar(), get_footer(): 标准的模板函数,加载你主题的通用部分。
  • new WP_Query( $args ): 这是核心。我们创建了两个独立的 WP_Query 实例 ($query_gemer$query_spis)。每个实例都有自己的参数 $args,最重要的是 'post_type' 指定了要查询的文章类型。
  • $query->have_posts(), $query->the_post(): 注意,在自定义查询的循环里,我们调用的是 $query_gemer->have_posts()$query_gemer->the_post()(以及 $query_spis 的对应方法),而不是直接用 have_posts()the_post()。后者操作的是全局的主查询对象。
  • the_title(), the_permalink(), the_excerpt(), post_class(), the_ID(): 这些是标准的模板标签,它们在 the_post() 被调用后,会自动引用当前循环中的文章数据。
  • wp_reset_postdata(): 极其关键! 在每个自定义 WP_Query 循环结束后,必须调用这个函数。它会恢复全局的 $post 对象到主查询的当前状态。如果不这样做,后续的查询,或者页面其他部分依赖主查询的函数(甚至包括某些插件或 get_footer() 里的内容)可能会出问题。

进阶使用技巧:

  • 复用模板部分: 如果 gemerspis 的展示样式完全一样,你可以把循环内部的 HTML 结构(比如从 <article></article> 的部分)放到一个单独的文件里,例如 template-parts/content-custom-archive.php。然后在两个循环内部都使用 get_template_part('template-parts/content', 'custom-archive') 来加载它。这能让你的 front-page.php 更简洁。不过要注意,标准的 get_template_part 不方便直接传递 $query_gemer$query_spis。你可以:
    • 仍然在 front-page.php 里写循环,只把 单篇文章的HTML结构 放到 template-part 里。
    • 或者稍微 hack 一下,在调用 get_template_part 前设置一个全局变量,然后在 template-part 里使用那个全局变量里的 post 对象(不推荐,容易混淆)。
    • 更现代的方法是使用主题开发框架提供的、或者自己实现的能传递参数的模板包含函数。
  • 查询参数: WP_Query 支持非常多的参数,比如按分类、标签、自定义字段过滤,设置分页 (paged),排除某些文章 (post__not_in) 等等。你可以根据需要定制 $args_gemer$args_spis

安全建议:

  • 这种方法本身比较安全,主要关注点在于确保 front-page.php 和你可能包含的 template-parts 文件中的代码是安全的,特别是如果你在里面处理用户输入或者输出自定义字段时,要做好数据清理和转义(如使用 esc_html(), esc_url(), esc_attr(), wp_kses_post() 等)。

方案二:修改主查询 (pre_get_posts) 以包含多种类型 (适用场景有限)

如果你的首页设置是“显示最新文章”,并且你强求 gemerspis 的内容分成独立的两块,而是希望它们和普通文章(post 类型)混合在一起,按发布时间统一排序显示,那么可以用 pre_get_posts 来修改主查询。

原理与作用:

  • 在 WordPress 构建首页文章列表的主查询时介入,告诉它:“嘿,除了默认的 post 类型,也把 gemerspis 类型的文章一起查出来吧。”

操作步骤:

  1. 确认首页设置: 确保“设置” -> “阅读” -> “您的主页显示”选的是“您的最新文章”。

  2. 编辑 functions.php: 在你的主题的 functions.php 文件里,或者一个自定义的功能插件里,添加以下代码:

    <?php
    function my_include_custom_post_types_on_home( $query ) {
        // 只针对前台的主查询,并且是首页(最新文章列表页)
        if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) {
            // 获取当前已设置的文章类型 (通常只有 'post')
            $post_types = $query->get( 'post_type' );
    
            // 如果当前只查询 'post' 或者没指定 (默认就是 'post')
            // 把它扩展成包含 'post', 'gemer', 'spis'
            if ( empty( $post_types ) || $post_types == 'post' ) {
                 $query->set( 'post_type', array( 'post', 'gemer', 'spis' ) );
            }
            // 如果已经查询多个类型了 (可能由其他插件或代码修改过),
            // 确保 'gemer' 和 'spis' 包含在内
            elseif ( is_array( $post_types ) ) {
                if ( ! in_array( 'gemer', $post_types ) ) {
                    $post_types[] = 'gemer';
                }
                if ( ! in_array( 'spis', $post_types ) ) {
                    $post_types[] = 'spis';
                }
                $query->set( 'post_type', $post_types );
            }
        }
    }
    add_action( 'pre_get_posts', 'my_include_custom_post_types_on_home' );
    
  3. 检查模板文件: 你的主题应该有一个 home.php 文件(优先)或 index.php 文件。里面的标准 WordPress 循环现在会自然地输出包含 post, gemer, spis 三种类型的文章,按日期混合排序。

代码解释:

  • ! is_admin(): 确保只影响前台页面,不干扰后台管理界面。
  • $query->is_main_query(): 确保只修改主查询,不影响侧边栏小工具、菜单等地方可能有的次级查询。
  • $query->is_home(): 关键!这个条件确保只在显示“最新文章”的那个首页(通常是网站根 URL 或者你指定的博客页面)上才执行修改。注意这里用的是 is_home() 而不是 is_front_page()
  • $query->get('post_type') / $query->set('post_type', ...): 获取和设置查询的文章类型参数。我们把 'gemer''spis' 添加进去。代码逻辑稍微复杂一点是为了确保能正确地添加类型,即使 post_type 参数已经被其他代码修改过。

局限性:

  • 这种方法最大的缺点是,所有文章会混在一起。你无法轻易地在页面上先显示一个 gemer 列表,再显示一个 spis 列表。它们是按统一的查询规则(通常是日期)混合排列的。
  • 如果你想在循环内部根据文章类型 (get_post_type()) 来应用不同的 HTML 结构或样式,是可行的,但这会让模板文件变得复杂,而且不易于管理和维护。

安全建议:

  • 此方法主要涉及查询参数修改,本身风险较低。

方案三:使用短代码 (Shortcodes)

这是一种介于方案一和方案二之间的方法,提供了一定的灵活性,同时可能比直接编辑模板文件对非开发者更友好一点。

原理与作用:

  • 创建自定义的短代码,比如 [gemer_archive][spis_archive]
  • 每个短代码的功能就是执行一个 WP_Query 来获取对应文章类型的列表,并返回 HTML 输出。
  • 你可以在一个静态页面(设置为首页)的内容编辑器里,像插入普通文本一样插入这些短代码。

操作步骤:

  1. 设置静态首页: 同方案一的第一步。

  2. functions.php 中定义短代码:

    <?php
    // 短代码 [gemer_archive] 的处理函数
    function display_gemer_archive_shortcode( $atts ) {
        // 设置默认属性,并合并用户传入的属性
        $atts = shortcode_atts( array(
            'posts_per_page' => 5, // 默认显示5'orderby' => 'date',
            'order' => 'DESC',
        ), $atts, 'gemer_archive' ); // 第三个参数是短代码名字,好习惯
    
        $args = array(
            'post_type'      => 'gemer',
            'posts_per_page' => intval( $atts['posts_per_page'] ), // 确保是整数
            'orderby'        => sanitize_key( $atts['orderby'] ), // 清理排序字段
            'order'          => strtoupper( $atts['order'] ) === 'ASC' ? 'ASC' : 'DESC', // 确保是 ASC 或 DESC
        );
        $query = new WP_Query( $args );
    
        ob_start(); // 开始捕获输出
    
        if ( $query->have_posts() ) :
            echo '<div class="gemer-archive-shortcode">'; // 包裹一层方便加样式
            echo '<h1>Gemer Content (from Shortcode)</h1>';
            while ( $query->have_posts() ) : $query->the_post();
                ?>
                <article id="post-<?php the_ID(); ?>" <?php post_class('shortcode-item'); ?>>
                    <header class="entry-header">
                        <?php the_title( sprintf( '<h2 class="entry-title"><a href="%s" rel="bookmark">', esc_url( get_permalink() ) ), '</a></h2>' ); ?>
                    </header>
                    <div class="entry-summary">
                        <?php the_excerpt(); ?>
                    </div>
                </article>
                <?php
            endwhile;
            echo '</div>';
            wp_reset_postdata(); // 别忘了重置
        else :
            echo '<p>暂时没有 Gemer 内容。</p>';
        endif;
    
        return ob_get_clean(); // 返回捕获到的 HTML 内容
    }
    add_shortcode( 'gemer_archive', 'display_gemer_archive_shortcode' ); // 注册短代码
    
    // 短代码 [spis_archive] 的处理函数 (类似地创建)
    function display_spis_archive_shortcode( $atts ) {
        $atts = shortcode_atts( array(
            'posts_per_page' => 5,
            'orderby' => 'date',
            'order' => 'DESC',
        ), $atts, 'spis_archive' );
    
        $args = array(
            'post_type'      => 'spis',
            'posts_per_page' => intval( $atts['posts_per_page'] ),
            'orderby'        => sanitize_key( $atts['orderby'] ),
            'order'          => strtoupper( $atts['order'] ) === 'ASC' ? 'ASC' : 'DESC',
        );
        $query = new WP_Query( $args );
    
        ob_start();
    
        if ( $query->have_posts() ) :
            echo '<div class="spis-archive-shortcode">';
             echo '<h1>Spis Content (from Shortcode)</h1>';
            while ( $query->have_posts() ) : $query->the_post();
                ?>
                <article id="post-<?php the_ID(); ?>" <?php post_class('shortcode-item'); ?>>
                    <header class="entry-header">
                        <?php the_title( sprintf( '<h2 class="entry-title"><a href="%s" rel="bookmark">', esc_url( get_permalink() ) ), '</a></h2>' ); ?>
                    </header>
                    <div class="entry-summary">
                        <?php the_excerpt(); ?>
                    </div>
                </article>
                <?php
            endwhile;
            echo '</div>';
            wp_reset_postdata();
        else :
             echo '<p>暂时没有 Spis 内容。</p>';
        endif;
    
        return ob_get_clean();
    }
    add_shortcode( 'spis_archive', 'display_spis_archive_shortcode' );
    
  3. 在页面编辑器中使用短代码:

    • 编辑你设置为静态首页的那个页面。
    • 在内容编辑器(经典编辑器或区块编辑器)中,在你想要显示 Gemer 列表的地方,输入 [gemer_archive]
    • 在你想要显示 Spis 列表的地方,输入 [spis_archive]
    • 你还可以带上参数来自定义,比如 [gemer_archive posts_per_page="3" orderby="title" order="ASC"] 表示只显示3篇 Gemer 文章,按标题升序排列。
    • 保存页面。

代码解释:

  • add_shortcode( 'shortcode_name', 'callback_function' ): 注册一个短代码及其处理函数。
  • shortcode_atts(): 一个方便的函数,用来处理短代码的属性(用户在方括号里写的 key="value"),提供默认值,并与用户输入合并。
  • 注意 :短代码的处理函数应该 return HTML 字符串,而不是 echo。我们用 ob_start()ob_get_clean() 来捕获本应用 echo 输出的内容,然后返回它。
  • 数据清理 : 在处理用户通过短代码属性传入的值时(比如 posts_per_page, orderby, order),进行适当的清理和验证(如 intval, sanitize_key, 检查是否为允许的值)是很重要的安全实践。

优点:

  • 内容和代码分离,非开发者也能通过编辑页面来调整首页布局和内容块。
  • 短代码可以复用在网站的其他页面或文章中。
  • 可以通过属性(attributes)让短代码更灵活。

缺点:

  • 对于非常复杂的布局或交互,短代码可能不够用。
  • 过多或过于复杂的短代码嵌套可能会影响性能。

安全建议:

  • 务必对短代码属性进行严格的清理和验证,防止用户输入恶意代码或导致查询错误。

总的来说,对于在 WordPress 首页展示多个自定义文章类型归档的需求,方案一(静态首页 + 多个 WP_Query)通常是最佳实践 ,它提供了最大的控制力和清晰度。如果只是想把自定义类型的文章混入主博客流,方案二也可用。方案三(短代码)则在易用性和灵活性之间提供了一个平衡点。

选一个最适合你项目的方式动手试试吧!