WordPress 教學:文章分類列表設計

摘要:這篇教學說明如何在 WordPress 中建立一個可依分類切換的文章清單頁,並加入雙行分頁功能。無需外掛即可實現,支援所有主題,具備高安全性與良好相容性,適合前端展示多分類內容的網站。

💡 WordPress 教學:可切換分類的文章清單頁(雙行分頁+下拉分類)

這篇教學示範如何建立一個 可依分類切換的文章列表頁面,並在下方顯示「兩行分頁」按鈕。
本範例適用於任何傳統 WordPress 主題(建議放在子主題),不需要外掛即可使用。


🧩 功能特色

  • 支援下拉選擇分類(slug / ID 皆可)
  • 支援父分類自動包含子分類
  • 每頁可自訂筆數
  • 分頁採「兩行樣式」:第 1 行:上一頁、頁碼、下一頁;第 2 行:第一頁、前 10 頁、後 10 頁、最後一頁
  • 支援所有主題(Classic / Block)
  • 無需外掛,安全、無個資風險

📂 檔案名稱

page-article-list.php
放在目前主題或子主題的根目錄(與 page.php 同層)。


🧱 程式碼完整範例

👉 點擊下方可展開完整程式碼,採用 Prism Smart Lite 高亮顯示:

<?php
/* 
Template Name: 文章清單(標題+日期時間+分類下拉+兩行分頁)
Description: 可切換分類的文章列表(每頁 20 筆;第1行:上一頁/頁碼/下一頁,第2行:第一頁/前10/後10/最後一頁;分頁為自然寬度膠囊且有間距)
*/

get_header();

/* ---------------------- 可調整區 ---------------------- */
// ★ 改每頁筆數
$posts_per_page_default = 20;            // ← 想要 30、50 直接改這裡
// ★ 改日期格式
$date_format            = 'Y-m-d H:i';   // 例:'Y/m/d H:i'
// ★ 改整體字型大小(px 或 rem)
$font_size              = '16px';
// ★ 前/後跳頁步長(前10/後10)
$jump_step              = 10;
/* ------------------------------------------------------ */

// 目前分頁(支援 pretty 與 query 參數)
$paged = max(1, get_query_var('paged') ?: get_query_var('page') ?: 1);

/* ---------------------- 分類參數解析(slug / id 皆可) ---------------------- */
/**
 * 相容策略:
 * 1. 主要參數仍是 ?category=(可放 slug 或 id)
 * 2. 若無,回退接受 ?cat=ID(WP 常見舊連結)
 * 3. 再回退 ?category_name=slug
 * 4. 若收到舊的 ID(例如把 1 當未分類),且該 term 不存在,回退目前實際的未分類 ID
 */
$default_cat_id = (int) get_option('default_category'); // 目前真正的「未分類」ID

$selected_raw = '';
if (isset($_GET['category']) && $_GET['category'] !== '') {
  $selected_raw = wp_unslash($_GET['category']);
} elseif (isset($_GET['cat']) && $_GET['cat'] !== '') {           // 舊連結相容
  $selected_raw = wp_unslash($_GET['cat']);
} elseif (isset($_GET['category_name']) && $_GET['category_name'] !== '') { // 更舊習慣
  $selected_raw = wp_unslash($_GET['category_name']);
}

$selected_cat  = sanitize_text_field($selected_raw); // 原樣保留到網址(slug 或 id)
$selected_term = null;

if ($selected_cat !== '' && $selected_cat !== 'all') {
  if (ctype_digit($selected_cat)) {
    // 參數是數字 → 以 ID 查;若舊 ID 已失效,回退目前的未分類 ID
    $maybe_id = (int) $selected_cat;
    $selected_term = get_term($maybe_id, 'category');
    if (!$selected_term || is_wp_error($selected_term)) {
      // 舊 ID 失效,且常見的是把 1 當作未分類 → 改抓現在的未分類
      $selected_term = get_term($default_cat_id, 'category');
    }
  } else {
    // 參數是字串 → 先當 slug,再嘗試名稱(中文站有時名稱與 slug 混用)
    $selected_term = get_term_by('slug', $selected_cat, 'category');
    if (!$selected_term) {
      $selected_term = get_term_by('name', $selected_cat, 'category');
    }
  }
}

/* ---------------------- 主查詢參數 ---------------------- */
$query_args = [
  'post_type'      => 'post',
  'post_status'    => 'publish',
  'orderby'        => 'date',
  'order'          => 'DESC',
  'posts_per_page' => $posts_per_page_default,
  'paged'          => $paged,
  'no_found_rows'  => false, // 需要分頁
];

if ($selected_term && !is_wp_error($selected_term)) {
  // 更穩定的 taxonomy 查詢(含子分類)
  $query_args['tax_query'] = [[
    'taxonomy'         => 'category',
    'field'            => 'term_id',
    'terms'            => (int)$selected_term->term_id,
    'include_children' => true,
    'operator'         => 'IN',
  ]];
}

$q = new WP_Query($query_args);

/* ---------------------- 下拉選單資料 ---------------------- */
$categories = get_terms([
  'taxonomy'   => 'category',
  'hide_empty' => true,
  'orderby'    => 'name',
  'order'      => 'ASC',
]);

/* ---------------------- 分頁連結工具(保留參數) ---------------------- */
function wpz_build_page_link($page, $extra_query = []) {
  $url = get_pagenum_link(max(1, (int)$page));
  if (!empty($extra_query)) $url = add_query_arg($extra_query, $url);
  return esc_url($url);
}
$persist_args = [];
if ($selected_cat !== '' && $selected_cat !== 'all') {
  $persist_args['category'] = $selected_cat; // 保留 slug 或 id(相容外部連結)
}
?>

<style>
  /* === 版面與字體(整體) === */
  .wpz-article-wrap { font-size: <?= esc_attr($font_size); ?>; }
  .wpz-article-wrap .page-header { margin-bottom: .5rem; }
  .wpz-article-wrap .page-title { margin: 0 0 .25rem; }

  /* === 下拉篩選 === */
  .wpz-article-wrap .article-filter { margin: .5rem 0 1rem; }

  /* === 文章清單 === */
  .wpz-article-wrap .simple-article-list { margin: .5rem 0 1rem; padding: 0; }
  .wpz-article-wrap .simple-article-list li { list-style: none; margin: .25rem 0; }
  .wpz-article-wrap .simple-article-list .date { display: inline-block; min-width: 10.5em; opacity: .8; }
  .wpz-article-wrap .simple-article-list a { text-decoration: none; }
  .wpz-article-wrap .simple-article-list a:hover { text-decoration: underline; }

  /* === 分頁(兩行):自然寬度的膠囊按鈕,彼此分開 === */
  .wpz-article-wrap .article-pagination { margin: 1rem 0; }

  /* 每一行用 flex,項目不撐滿、不等分,保持彼此間距 */
  .wpz-article-wrap .pagination-row{
    display: flex;
    flex-wrap: wrap;                 
    justify-content: center;         
    gap: .6rem;                      
    list-style: none;
    padding: 0;
    margin: .6rem 0;
  }

  .wpz-article-wrap .pagination-row--main{ gap: .6rem; }
  .wpz-article-wrap .pagination-row--aux { gap: .8rem; }

  .wpz-article-wrap .pagination-row li{
    flex: 0 0 auto;                  
  }

  .wpz-article-wrap .pagination-row li a,
  .wpz-article-wrap .pagination-row li span{
    display: inline-block;
    min-width: 2.6rem;               
    padding: .48rem .7rem;
    text-align: center;
    border: 1px solid #d0d7de;
    border-radius: .6rem;
    text-decoration: none;
    color: #1f2328;
    background: #fff;
    line-height: 1;
    width: auto;                     
  }

  .wpz-article-wrap .pagination-row li a:hover{ background:#f3f4f6; }
  .wpz-article-wrap .pagination-row li .current,
  .wpz-article-wrap .pagination-row li span.current{
    background:#0073aa; border-color:#0073aa; color:#fff;
  }
  .wpz-article-wrap .pagination-row li.disabled a{
    pointer-events:none; opacity:.45;
  }

  @media (max-width: 480px){
    .wpz-article-wrap .pagination-row{ gap:.45rem; }
    .wpz-article-wrap .pagination-row li a,
    .wpz-article-wrap .pagination-row li span{ min-width: 2.3rem; padding:.44rem .6rem; }
  }
</style>

<main class="site-main container wpz-article-wrap">
  <header class="page-header">
    <h1 class="page-title"><?php echo esc_html(get_the_title()); ?></h1>

    <!-- 分類下拉(GET 提交保留在同一頁) -->
    <form method="get" class="article-filter">
      <label for="category" style="margin-right:.5rem;">分類:</label>
      <select id="category" name="category" onchange="this.form.submit()">
        <option value="all"<?php selected($selected_cat, 'all'); ?>>全部</option>
        <?php foreach ($categories as $cat): ?>
          <?php
            // 預設 value 用 slug(相容既有網址);如想最穩可改成 ID:$opt_value = (string)$cat->term_id;
            $opt_value = $cat->slug;
          ?>
          <option value="<?php echo esc_attr($opt_value); ?>"
            <?php selected($selected_cat, (string)$opt_value); ?>>
            <?php echo esc_html($cat->name); ?>
          </option>
        <?php endforeach; ?>
      </select>
      <noscript><button type="submit">篩選</button></noscript>
    </form>
  </header>

  <?php if ($q->have_posts()): ?>
    <ul class="simple-article-list">
      <?php while ($q->have_posts()): $q->the_post(); ?>
        <li>
          <span class="date"><?php echo esc_html(get_the_date($date_format)); ?></span>
          <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
        </li>
      <?php endwhile; ?>
    </ul>

    <?php
      // === 自訂分頁(兩行:第1行 上/數字/下;第2行 首/前10/後10/末) ===
      $total   = max(1, (int) $q->max_num_pages);
      $current = max(1, (int) $paged);
      $jump    = max(1, (int) $jump_step);

      if ($total > 1):
        // 連結(都保留分類等參數)
        $link_first = wpz_build_page_link(1,                       $persist_args);
        $link_last  = wpz_build_page_link($total,                  $persist_args);
        $link_prev  = wpz_build_page_link(max(1, $current - 1),    $persist_args);
        $link_next  = wpz_build_page_link(min($total, $current+1), $persist_args);
        $link_prevJ = wpz_build_page_link(max(1, $current - $jump),$persist_args);
        $link_nextJ = wpz_build_page_link(min($total,$current+$jump),$persist_args);

        echo '<nav class="article-pagination" aria-label="文章分頁">';

        // 第1行:上一頁 + 數字 + 下一頁
        echo '<ul class="pagination-row pagination-row--main">';
          // 上一頁
          echo '<li class="prev'.($current===1?' disabled':'').'"><a href="'.$link_prev.'" aria-label="上一頁">‹ 上一頁</a></li>';

          // 數字群
          $number_links = paginate_links([
            'current'   => $current,
            'total'     => $total,
            'mid_size'  => 2,
            'end_size'  => 1,
            'prev_next' => false,
            'type'      => 'array',
            'add_args'  => $persist_args,
          ]);
          if (!empty($number_links)) {
            foreach ($number_links as $html) {
              $html = str_replace('page-numbers current', 'page current', $html);
              $html = str_replace('page-numbers', 'page', $html);
              echo '<li>'.$html.'</li>';
            }
          }

          // 下一頁
          echo '<li class="next'.($current===$total?' disabled':'').'"><a href="'.$link_next.'" aria-label="下一頁">下一頁 ›</a></li>';
        echo '</ul>';

        // 第2行:首末+前後10
        echo '<ul class="pagination-row pagination-row--aux">';
          echo '<li class="first'.($current===1?' disabled':'').'"><a href="'.$link_first.'" aria-label="到第一頁">«« 第一頁</a></li>';
          echo '<li class="prev10'.($current===1?' disabled':'').'"><a href="'.$link_prevJ.'" aria-label="前'.$jump.'頁">前'.$jump.'頁</a></li>';
          echo '<li class="next10'.($current===$total?' disabled':'').'"><a href="'.$link_nextJ.'" aria-label="後'.$jump.'頁">後'.$jump.'頁</a></li>';
          echo '<li class="last'.($current===$total?' disabled':'').'"><a href="'.$link_last.'" aria-label="到最後一頁">最後一頁 »»</a></li>';
        echo '</ul>';

        echo '</nav>';
      endif;
    ?>

  <?php else: ?>
    <p>目前沒有符合條件的文章。</p>
  <?php endif; wp_reset_postdata(); ?>
</main>

<?php get_footer(); ?>

🛡️ 安全與相容性說明

  • 僅讀取公開文章(post_status=publish),不會顯示草稿或私密文章。
  • 所有 GET 參數都經過 sanitize_text_field() 清理。
  • 不含任何帳號、密碼、API key 或個資。
  • 可安全公開於教學或 GitHub。

🧭 使用方式

  1. 將上述檔案放入主題或子主題根目錄。
  2. 在 WordPress 後台 → 建立新頁面。
  3. 右側「模板」選擇:文章清單(標題+日期時間+分類下拉+兩行分頁)
  4. 發佈即可使用。

🏷️ 授權建議

Author: 林易增
License: MIT
自由使用、修改、轉載,但請保留作者註記。

✅ 本教學程式碼無個資風險,可自由發佈於 blog 或 GitHub。

分類: 部落格架設,標籤: 。這篇內容的永久連結

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *