摘要:這篇教學說明如何在 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。
🧭 使用方式
- 將上述檔案放入主題或子主題根目錄。
- 在 WordPress 後台 → 建立新頁面。
- 右側「模板」選擇:文章清單(標題+日期時間+分類下拉+兩行分頁)。
- 發佈即可使用。
🏷️ 授權建議
Author: 林易增
License: MIT
自由使用、修改、轉載,但請保留作者註記。
✅ 本教學程式碼無個資風險,可自由發佈於 blog 或 GitHub。