WordPress首页显示多个文章归档?3种方法解决
2025-03-30 12:35:25
WordPress 首页显示多个文章归档?看这篇就够了
咱们直接点,你是不是碰到了这个头疼的问题:想在 WordPress 网站的首页(就是访客一进来看到的那个页面)同时展示来自不同自定义文章类型(Custom Post Types)的归档内容?比如说,你搞了个叫 gemer
的文章类型,又搞了个叫 spis
的,都有各自的归档模板(archive-gemer.php
和 archive-spis.php
),现在就想让首页把这两个归档里的帖子都列出来。
你可能尝试改了 functions.php
,用了 pre_get_posts
钩子,或者在 front-page.php
文件里直接用 get_template_part
来加载那两个归档模板,结果发现,首页要么只显示些 HTML 骨架,要么内容根本不对,WordPress 的循环(Loop)好像没起作用。
别急,这事儿能搞定。咱们来分析下为啥你之前的尝试可能没成功,然后给出几种靠谱的解决方案。
问题出在哪儿?
你遇到的情况,根源通常在这几个地方:
pre_get_posts
的误用 : 这个钩子非常强大,它允许你在 WordPress 执行主查询(Main Query)之前修改查询参数。但它主要是用来调整 查询条件 的(比如查哪些文章类型、按什么排序),而不是用来 直接输出 HTML 内容 的。你在pre_get_posts
的回调函数里用include
或者ob_start
/ob_get_clean
来加载模板文件并echo
输出,这完全用错了地方。这时候主查询还没真正获取数据呢,更别说模板文件里的have_posts()
/the_post()
循环了。front-page.php
和get_template_part
的理解 :front-page.php
文件确实是 WordPress 用来显示网站首页的最高优先级模板。在里面使用get_template_part('archive', 'gemer')
和get_template_part('archive', 'spis')
会包含archive-gemer.php
和archive-spis.php
文件里的代码。问题是,如果这两个模板文件里都用了标准的if (have_posts()) : while (have_posts()) : ...
循环,它们默认会尝试显示 主查询 的结果。如果你没有正确地为它们准备独立的查询数据,它们要么显示空(如果主查询没结果或不符合条件),要么会重复显示主查询的结果。单纯地包含模板文件,并不会自动为每个模板启动一个它“应该”对应的查询。- 首页设置与模板文件 : 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()
返回true
,is_home()
返回false
(除非你把“文章页”也设置成了同一个静态页面,这不常见)。
你之前的代码里似乎混淆了is_front_page()
的检查,而且试图在“最新文章”设置下,通过修改查询类型来强行模拟归档页行为,路子就走偏了。
- 如果选的是“您的最新文章 ”,WordPress 会优先查找
了解了这些,解决起来就思路清晰了。
解决方案
下面提供几种解决这个问题的实用方法,你可以根据自己的具体情况和偏好来选择。
方案一:使用静态首页 + 多个自定义查询 (推荐)
这是最常用、最灵活也最符合 WordPress 设计思路的方法。简单说,就是专门创建一个页面用作首页,然后在这个页面的模板文件里,手动发起多个查询来分别获取 gemer
和 spis
的文章列表。
原理与作用:
- 将首页内容与标准的博客文章流解耦,给你完全的控制权。
- 通过创建独立的
WP_Query
对象,可以在一个页面上运行多个互不干扰的文章查询循环。
操作步骤:
-
设置静态首页:
- 登录 WordPress后台。
- 创建一个新的“页面”(不是文章),标题可以叫“首页”或者其他你喜欢的名字,内容暂时留空。
- 进入“设置” -> “阅读”。
- 在“您的主页显示”选项中,选择“一个静态页面”。
- 在“主页”下拉菜单中,选择你刚刚创建的那个“首页”页面。
- (可选)如果你还想保留一个显示最新博客文章的地方,可以再创建一个“博客”页面,并在“文章页”下拉菜单中选择它。
- 保存更改。
-
创建
front-page.php
模板:- 在你的主题文件夹下(比如
/wp-content/themes/your-theme/
),创建一个名为front-page.php
的文件。如果它已存在,就编辑它。这个文件会自动被用作你指定的静态首页的模板。
- 在你的主题文件夹下(比如
-
在
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()
里的内容)可能会出问题。
进阶使用技巧:
- 复用模板部分: 如果
gemer
和spis
的展示样式完全一样,你可以把循环内部的 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
) 以包含多种类型 (适用场景有限)
如果你的首页设置是“显示最新文章”,并且你不强求 gemer
和 spis
的内容分成独立的两块,而是希望它们和普通文章(post
类型)混合在一起,按发布时间统一排序显示,那么可以用 pre_get_posts
来修改主查询。
原理与作用:
- 在 WordPress 构建首页文章列表的主查询时介入,告诉它:“嘿,除了默认的
post
类型,也把gemer
和spis
类型的文章一起查出来吧。”
操作步骤:
-
确认首页设置: 确保“设置” -> “阅读” -> “您的主页显示”选的是“您的最新文章”。
-
编辑
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' );
-
检查模板文件: 你的主题应该有一个
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 输出。 - 你可以在一个静态页面(设置为首页)的内容编辑器里,像插入普通文本一样插入这些短代码。
操作步骤:
-
设置静态首页: 同方案一的第一步。
-
在
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' );
-
在页面编辑器中使用短代码:
- 编辑你设置为静态首页的那个页面。
- 在内容编辑器(经典编辑器或区块编辑器)中,在你想要显示 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
)通常是最佳实践 ,它提供了最大的控制力和清晰度。如果只是想把自定义类型的文章混入主博客流,方案二也可用。方案三(短代码)则在易用性和灵活性之间提供了一个平衡点。
选一个最适合你项目的方式动手试试吧!