WP DEVELOP

EP210-211. “批量搭建自定义文章类型模板(archive / single / page)”

首页 WordPress 开发课程 BLOCK THEME(2024 最佳实践) · EP210-211
约 25 分钟· #EP210-211#BLOCK THEME(2024 最佳实践)
🔒 登录后可标记已读

📌 并入 EP210 的提醒:跟着这一讲新建各个 Block 的 PHP 文件时,凡是「单篇详情页」场景(singleprogram.php/singleprofessor.php),按 EP208/EP203 学到的规则,不要手写 while (have_posts()) { the_post(); ... }、也不要调用 the_post(),WordPress 6.4+ 的 Block Theme 已经自动处理好了这层循环。如果不确定,随时可以下载这一章任意一讲的参考代码压缩包核对。


按 EP199-211 反复验证过的固定套路,批量补齐自定义文章类型(Program、Professor)和特殊页面(My Notes)各自需要的专属模板——每一个都是「新建 templates/*.html 三段式文件 → functions.php 加一行 new PlaceholderBlock(...) → 复制最简单的占位 JS 改名字 → 新建 PHP 文件、从传统主题对应文件搬运既有代码」。这一讲同时用三个例子巩固了 EP208 的规则:「列表」类模板programarchive.php,展示全部 Program)保留 while 循环;「单篇详情」类模板singleprogram.php/singleprofessor.php)不需要外层 while 循环(内部各自独立的关联查询循环——比如相关教授、近期活动——这些不是「主查询循环」,本来就该保留自己的 while);带自定义权限逻辑的特殊页面mynotes.php,未登录用户重定向)同样不需要主查询循环,但完全保留了自己原有的判断和独立查询逻辑。作者明确表示这类模板会越写越重复,后续没有转换完的模板直接放进参考代码压缩包里,不再逐个录制讲解。


涉及文件(这一讲新增的 4 组 Block + 对应模板)

Block 名模板文件场景
programarchivetemplates/archive-program.htmlProgram 列表页(保留 while 循环)
singleprogramtemplates/single-program.html单个 Program 详情页(无主循环,内含独立关联查询)
singleprofessortemplates/single-professor.html单个 Professor 详情页(无主循环)
mynotestemplates/page-my-notes.htmlMy Notes 功能页(无主循环,保留登录校验 + 独立笔记查询)
  • wp-content/themes/fictional-university-block-theme/functions.php (修改,新增 4 个 PlaceholderBlock 实例)
  • 对应的 our-blocks/{name}.js / {name}.php 各 4 组(新建)
  • templates/archive-{文章类型}.html:命名规则完全沿用传统主题的 archive-{文章类型}.php,只是换成 .html
  • templates/single-{文章类型}.html:同理对应传统主题的 single-{文章类型}.php
  • templates/page-{页面slug}.html:同理对应传统主题的 page-{slug}.php(比如 page-my-notes.php

代码实现

模板文件三段式(以 archive-program.html 为例,其余同构)

<!-- wp:ourblocktheme/header /-->

<!-- wp:ourblocktheme/programarchive /-->

<!-- wp:ourblocktheme/footer /-->

functions.php:批量追加占位 Block

new PlaceholderBlock("programarchive");
new PlaceholderBlock("singleprogram");
new PlaceholderBlock("singleprofessor");
new PlaceholderBlock("mynotes");

our-blocks/programarchive.php(列表页,保留 while 循环)

<?php

pageBanner(array(
  'title' => 'All Programs',
  'subtitle' => 'There is something for everyone. Have a look around.'
));
 ?>

<div class="container container--narrow page-section">

<ul class="link-list min-list">

<?php
  while(have_posts()) {
    the_post(); ?>
    <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
  <?php }
  echo paginate_links();
?>
</ul>

</div>

our-blocks/singleprogram.php(单篇详情页,不需要主循环,但保留原有的关联教授/近期活动/关联校区三段独立查询)

<?php

    pageBanner();
     ?>

    <div class="container container--narrow page-section">
          <div class="metabox metabox--position-up metabox--with-home-link">
        <p><a class="metabox__blog-home-link" href="<?php echo get_post_type_archive_link('program'); ?>"><i class="fa fa-home" aria-hidden="true"></i> All Programs</a> <span class="metabox__main"><?php the_title(); ?></span></p>
      </div>

      <div class="generic-content"><?php the_field('main_body_content'); ?></div>

      <?php 
        $relatedProfessors = new WP_Query(array(
          'posts_per_page' => -1,
          'post_type' => 'professor',
          'orderby' => 'title',
          'order' => 'ASC',
          'meta_query' => array(
            array(
              'key' => 'related_programs',
              'compare' => 'LIKE',
              'value' => '"' . get_the_ID() . '"'
            )
          )
        ));

        if ($relatedProfessors->have_posts()) {
        echo '<hr class="section-break">';
        echo '<h2 class="headline headline--medium">' . get_the_title() . ' Professors</h2>';

        echo '<ul class="professor-cards">';
        while($relatedProfessors->have_posts()) {
          $relatedProfessors->the_post(); ?>
          <li class="professor-card__list-item">
            <a class="professor-card" href="<?php the_permalink(); ?>">
              <img class="professor-card__image" src="<?php the_post_thumbnail_url('professorLandscape') ?>">
              <span class="professor-card__name"><?php the_title(); ?></span>
            </a>
          </li>
        <?php }
        echo '</ul>';
        }

        wp_reset_postdata();
        // ...后面还有近期活动、关联校区两段类似的独立查询,逻辑与传统主题一致,此处从略...
      ?>

    </div>

our-blocks/mynotes.php(保留登录校验 + 自己的笔记列表查询,同样不需要主循环)

<?php

  if (!is_user_logged_in()) {
    wp_redirect(esc_url(site_url('/')));
    exit;
  }

    pageBanner();
     ?>

    <div class="container container--narrow page-section">
      
      <div class="create-note">
        <h2 class="headline headline--medium">Create New Note</h2>
        <input class="new-note-title" placeholder="Title">
        <textarea class="new-note-body" placeholder="Your note here..."></textarea>
        <span class="submit-note">Create Note</span>
        <span class="note-limit-message">Note limit reached: delete an existing note to make room for a new one.</span>
      </div>

      <ul class="min-list link-list" id="my-notes">
        <?php 
          $userNotes = new WP_Query(array(
            'post_type' => 'note',
            'posts_per_page' => -1,
            'author' => get_current_user_id()
          ));

          while($userNotes->have_posts()) {
            $userNotes->the_post(); ?>
            <li data-id="<?php the_ID(); ?>">
              <input readonly class="note-title-field" value="<?php echo str_replace('Private: ', '', esc_attr(get_the_title())); ?>">
              <span class="edit-note"><i class="fa fa-pencil" aria-hidden="true"></i> Edit</span>
              <span class="delete-note"><i class="fa fa-trash-o" aria-hidden="true"></i> Delete</span>
              <textarea readonly class="note-body-field"><?php echo esc_textarea(get_the_content()); ?></textarea>
              <span class="update-note btn btn--blue btn--small"><i class="fa fa-arrow-right" aria-hidden="true"></i> Save</span>
            </li>
          <?php }
        ?>
      </ul>
    </div>

关键改动点:

  • 判断「要不要保留 while 循环」的关键不是「文件长不长」,而是「这段循环是不是 WordPress 的主查询循环」programarchive.phpwhile(have_posts()) 是主查询(列出全部 Program),必须保留;singleprogram.php 里的 $relatedProfessors/$homepageEvents 各自都是开发者自己另外发起的 new WP_Query() 独立查询,跟 EP208 讲的「主查询循环」完全是两回事,这些独立查询自己的 while 循环永远都要保留(不管是不是单篇页面)——EP208 的规则只针对页面本身的主查询
  • mynotes.php 保留了完整的原有逻辑is_user_logged_in() 判断 + wp_redirect() 未登录跳转、$userNotes = new WP_Query(...) 查询当前登录用户自己的笔记——这些都是跟「单篇内容主循环」无关的自定义逻辑,全部原样保留,只是因为整个页面本身是「单篇」性质(一个固定 slug 的页面),所以没有主查询循环需要处理
  • 文件命名规则完全沿用传统主题的既有约定,只是扩展名换成 .htmlarchive-{文章类型}single-{文章类型}page-{slug}——这三种模式覆盖了 WordPress 模板层级里最常用的几种「更具体」模板类型,跟传统主题的经验高度重合,不需要重新学一套新规则
  • 作者明确表示不会再逐个演示剩余模板:这一讲之后如果还需要活动(event)、校区(campus)等自定义文章类型各自的模板,做法完全一样(复制套路即可),课程配套的参考代码压缩包会包含转换好的全部模板文件,不再占用视频时间逐一演示

常见坑

  • 单篇详情页的模板 PHP 文件顶部继续保留主查询的 while (have_posts()) { the_post(); ... }——按 WordPress 6.4+ 的新行为会导致页面空白/报错
  • 误把「页面内部所有的 while 循环」都当成要删除的对象——只有主查询的循环需要去掉,任何开发者自己发起的独立 WP_Query 查询循环都要正常保留
  • 带权限校验/自定义查询的特殊页面(比如 My Notes),生搬硬套「转换成 Block Theme 就要用核心 Block」的思路——这类页面本身没有让终端用户自定义排版的价值,直接原样保留已经调试好的 PHP 逻辑即可

[截图:前台 Program 详情页正确渲染出正文内容和下方"XX Professors"关联教授卡片列表]


延伸 / 后续讲座会用到

下一讲要讲如何限制「全站编辑器」和「普通文章/页面编辑器」里各自能使用哪些 Block 类型。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 28, EP210, EP211