下面我将从基本结构、常用函数、高级技巧完整示例,为你详细讲解 WordPress 文章页模板。


WordPress 模板加载机制(如何找到文章页模板)

你需要理解 WordPress 是如何决定使用哪个文件来显示文章的,WordPress 会按照一个特定的优先级顺序来查找模板文件:

  1. single-{post_type}.php (最高优先级)

    • 这是最精确的模板,如果你有一个自定义文章类型(product),WordPress 会优先查找 single-product.php,如果不存在,它会继续往下找。
    • 对于默认的“文章”(Post),它会查找 single-post.php,如果这个文件存在,它会直接使用这个文件来显示所有文章。
  2. single.php

    • single-{post_type}.php 不存在,WordPress 就会查找通用的 single.php 文件,这是最常见、最基础的文章页模板。
  3. singular.php

    • single.php 也不存在,WordPress 会查找 singular.php,这个模板不仅用于文章,也用于页面,是所有“单一”内容(single content)的回退模板。
  4. index.php

    • 如果以上所有文件都不存在,WordPress 就会使用最基础的 index.php 来显示内容。

要自定义文章页,你只需要在主题根目录下创建一个 single.php 文件(或者 single-post.php)即可。


single.php 的基本结构

一个典型的 single.php 文件结构如下,它包含了 HTML 骨架和 WordPress 的主循环:

<?php
/**
 * Template Name: 文章页模板 (可以省略,但加上是个好习惯)
 * @package WordPress
 * @subpackage Your_Theme_Name
 */
// get_header() 加载主题的 header.php 文件
get_header(); 
?>
    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">
        <?php
        // WordPress 主循环
        // 如果有文章,则循环显示
        if ( have_posts() ) :
            // 循环开始
            while ( have_posts() ) : the_post();
                // 加载文章内容模板部分
                // 这是一个最佳实践,将单个文章的HTML结构放在一个单独的文件中
                get_template_part( 'template-parts/content', get_post_format() );
            // 循环结束
            endwhile;
            // 文章分页导航(上一篇文章/下一篇文章)
            the_post_navigation();
        else :
            // 如果没有找到文章,加载没有内容的模板
            get_template_part( 'template-parts/content', 'none' );
        endif;
        ?>
        </main><!-- #main -->
    </div><!-- #primary -->
<?php
// get_sidebar() 加载主题的 sidebar.php 文件(如果需要侧边栏)
get_sidebar(); 
// get_footer() 加载主题的 footer.php 文件
get_footer(); 
?>

核心函数详解

single.php 的主循环中,我们使用了很多函数来获取和显示文章内容,以下是最常用的一些函数:

A. 文章标题和内容

  • the_title(): 显示文章的标题。
    • the_title( '<h1 class="entry-title">', '</h1>' ); // 可以给标题包裹上 HTML 标签和类名,这是最佳实践。
  • the_permalink(): 显示文章的永久链接(URL)。
  • the_content(): 显示文章的完整内容(在后台编辑器中输入的正文)。
    • the_content( '继续阅读 <span class="meta-nav">&rarr;</span>' ); // 可以添加“阅读更多”链接的文本。
  • the_excerpt(): 显示文章的摘要,如果文章设置了摘要,则显示摘要;否则,会自动从内容中截取一部分。
  • get_the_content(): 获取,但不直接显示,它返回一个字符串,你可以对这个字符串进行处理后再输出。

B. 元数据(文章信息)

元数据通常显示在标题下方,包含作者、发布日期、分类、标签等信息。

  • the_author(): 显示文章作者。
  • the_time( get_option('date_format') ): 显示文章发布时间。
    • get_option('date_format') 会获取你在 WordPress 后台设置的时间格式。
  • the_category(', '): 显示文章所属的分类,多个分类用逗号隔开。
  • the_tags(): 显示文章的标签。
  • edit_post_link(): 显示“编辑”链接,只有登录的管理员才能看到。

显示元数据的最佳实践(通常放在一个 <div class="entry-meta"> 容器里):

<div class="entry-meta">
    <?php
    printf(
        esc_html__( '发表于 %s', 'your-theme-textdomain' ),
        '<a href="' . esc_url( get_permalink() ) . '" rel="bookmark">' . get_the_date() . '</a>'
    );
    ?>
    <?php
    printf(
        esc_html__( '作者 %s', 'your-theme-textdomain' ),
        '<span class="author vcard"><a class="url fn n" href="' . esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ) . '">' . get_the_author() . '</a></span>'
    );
    ?>
    <?php
    /* translators: used between list items, there is a space after the comma */
    $categories_list = get_the_category_list( esc_html__( ', ', 'your-theme-textdomain' ) );
    if ( $categories_list ) {
        printf( /* translators: 1: list of categories. */
            '<span class="cat-links">' . esc_html__( '分类: %1$s', 'your-theme-textdomain' ) . '</span>',
            $categories_list
        ); // WPCS: XSS OK.
    }
    ?>
</div>

C. 互动功能

  • comments_template(): 加载评论模板,这会引入 comments.php 文件来显示评论列表和评论表单。
  • the_post_navigation(): 显示“上一篇/下一篇”文章的链接。

完整的 single.php 示例

这是一个功能完整、结构清晰的 single.php 示例,你可以直接基于它进行修改。

<?php
/**
 * 文章页模板
 *
 * @package WordPress
 * @subpackage Your_Theme_Name
 */
get_header(); ?>
    <div id="primary" class="content-area">
        <main id="main" class="site-main">
        <?php if ( have_posts() ) : ?>
            <?php /* 开始主循环 */ ?>
            <?php while ( have_posts() ) : the_post(); ?>
                <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                    <!-- 文章标题 -->
                    <header class="entry-header">
                        <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
                        <!-- 文章元数据(发布时间、作者、分类等) -->
                        <div class="entry-meta">
                            <?php
                                your_theme_posted_on();
                                your_theme_posted_by();
                            ?>
                        </div><!-- .entry-meta -->
                    </header><!-- .entry-header -->
                    <!-- 特色图片 -->
                    <?php if ( has_post_thumbnail() ) : ?>
                        <div class="post-thumbnail">
                            <a href="<?php the_permalink(); ?>">
                                <?php the_post_thumbnail( 'large' ); ?>
                            </a>
                        </div>
                    <?php endif; ?>
                    <!-- 文章内容 -->
                    <div class="entry-content">
                        <?php
                            the_content(
                                sprintf(
                                    wp_kses(
                                        /* translators: %s: Name of current post. */
                                        __( '继续阅读<span class="screen-reader-text"> "%s"</span>', 'your-theme-textdomain' ),
                                        array(
                                            'span' => array(
                                                'class' => array(),
                                            ),
                                        )
                                    ),
                                    get_the_title()
                                )
                            );
                            wp_link_pages( array(
                                'before' => '<div class="page-links">' . esc_html__( '页面:', 'your-theme-textdomain' ),
                                'after'  => '</div>',
                            ) );
                        ?>
                    </div><!-- .entry-content -->
                    <!-- 文章底部元数据(标签等) -->
                    <footer class="entry-footer">
                        <?php your_theme_entry_footer(); ?>
                    </footer><!-- .entry-footer -->
                </article><!-- #post-<?php the_ID(); ?> -->
                <?php // 文章导航 ?>
                <?php the_post_navigation( array(
                    'prev_text' => '<span class="nav-subtitle">' . esc_html__( '上一篇:', 'your-theme-textdomain' ) . '</span> <span class="nav-title">%title</span>',
                    'next_text' => '<span class="nav-subtitle">' . esc_html__( '下一篇:', 'your-theme-textdomain' ) . '</span> <span class="nav-title">%title</span>',
                ) ); ?>
                <?php // 如果评论是开启的,或者至少有一篇评论,则加载评论模板。 ?>
                <?php if ( comments_open() || get_comments_number() ) : ?>
                    <?php comments_template(); ?>
                <?php endif; ?>
            <?php endwhile; // 循环结束 ?>
        <?php else : // 如果没有文章 ?>
            <?php get_template_part( 'template-parts/content', 'none' ); ?>
        <?php endif; ?>
        </main><!-- #main -->
    </div><!-- #primary -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>

注意:上面的示例中用到了 your_theme_posted_on()your_theme_entry_footer() 这两个函数,这些函数通常不会在 single.php 中直接写,而是放在 functions.php 中,以便在多个地方(如归档页)复用。

functions.php 中添加这两个函数:

// 在 functions.php 中添加
if ( ! function_exists( 'your_theme_posted_on' ) ) :
    /**
     * 显示文章的发布时间
     */
    function your_theme_posted_on() {
        $time_string = '<time class="entry-date published updated" datetime="%1$s">%2$s</time>';
        if ( get_the_time( 'U' ) !== get_the_modified_time( 'U' ) ) {
            $time_string = '<time class="entry-date published" datetime="%1$s">%2$s</time><time class="updated" datetime="%3$s">%4$s</time>';
        }
        $time_string = sprintf( $time_string,
            esc_attr( get_the_date( DATE_W3C ) ),
            esc_html( get_the_date() ),
            esc_attr( get_the_modified_date( DATE_W3C ) ),
            esc_html( get_the_modified_date() )
        );
        $posted_on = sprintf(
            /* translators: %s: post date. */
            esc_html_x( '发布于 %s', 'post date', 'your-theme-textdomain' ),
            '<a href="' . esc_url( get_permalink() ) . '" rel="bookmark">' . $time_string . '</a>'
        );
        echo '<span class="posted-on">' . $posted_on . '</span>'; // WPCS: XSS OK.
    }
endif;
if ( ! function_exists( 'your_theme_entry_footer' ) ) :
    /**
     * 显示文章的底部元数据,如分类和标签
     */
    function your_theme_entry_footer() {
        // 隐藏分类和标签
        if ( 'post' === get_post_type() ) {
            /* translators: used between list items, there is a space after the comma */
            $categories_list = get_the_category_list( esc_html__( ', ', 'your-theme-textdomain' ) );
            if ( $categories_list ) {
                /* translators: 1: list of categories. */
                printf( '<span class="cat-links">' . esc_html__( '分类: %1$s', 'your-theme-textdomain' ) . '</span>', $categories_list ); // WPCS: XSS OK.
            }
            /* translators: used between list items, there is a space after the comma */
            $tags_list = get_the_tag_list( '', esc_html_x( ', ', 'list item separator', 'your-theme-textdomain' ) );
            if ( $tags_list ) {
                /* translators: 1: list of tags. */
                printf( '<span class="tags-links">' . esc_html__( '标签: %1$s', 'your-theme-textdomain' ) . '</span>', $tags_list ); // WPCS: XSS OK.
            }
        }
        if ( ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) {
            echo '<span class="comments-link">';
            comments_popup_link(
                esc_html__( '发表评论', 'your-theme-textdomain' ),
                esc_html__( '1 条评论', 'your-theme-textdomain' ),
                esc_html__( '% 条评论', 'your-theme-textdomain' )
            );
            echo '</span>';
        }
        // 编辑文章链接
        edit_post_link(
            sprintf(
                wp_kses(
                    /* translators: %s: Name of current post. */
                    __( '编辑 <span class="screen-reader-text">%s</span>', 'your-theme-textdomain' ),
                    array(
                        'span' => array(
                            'class' => array(),
                        ),
                    )
                ),
                get_the_title()
            ),
            '<span class="edit-link">',
            '</span>'
        );
    }
endif;

高级技巧

A. 使用 get_template_part() 进行模块化开发

正如基本结构中提到的,最佳实践是将单个文章的 HTML 结构(<article> 及其内部)放在一个单独的文件中。

  1. 在主题目录下创建 template-parts/content.php 文件。
  2. single.php<article> 及其内部的所有代码剪切并粘贴到 content.php 中。
  3. single.php 的循环中,用 get_template_part( 'template-parts/content' ); 来调用它。

这样做的好处是:

  • 代码复用:归档页(archive.php)也可以使用这个 content.php 模板来显示文章列表项。
  • 结构清晰single.php 只负责页面的整体布局(header, main, footer),而 content.php 只负责文章内容的展示,职责分明。

B. 根据文章格式显示不同内容

WordPress 支持文章格式(如“ aside ”、“ gallery ”、“ video ”等),你可以为不同格式的文章创建不同的模板文件,

  • template-parts/content-aside.php:用于“日志”格式的文章。
  • template-parts/content-gallery.php:用于“图库”格式的文章。
  • template-parts/content.php:默认模板。

然后在 single.php 的循环中使用 get_template_part( 'template-parts/content', get_post_format() );,WordPress 会自动根据文章的格式选择正确的模板文件。

希望这份详细的指南能帮助你完全掌握 WordPress 文章页模板的开发!