Discuz! 經典的 multi() 函數(位於 global.func.php)用於生成論壇列表、帖子列表等頁面的分頁導航 HTML。以下為核心邏輯重點:
function multi($num, $perpage, $curpage, $mpurl, $maxpages = 0) {
$multipage = '';
$mpurl .= strpos($mpurl, '?') ? '&' : '?';
if ($num > $perpage) {
$page = 10; // 顯示頁碼數量
$offset = 2; // 當前頁左右偏移
$realpages = ceil($num / $perpage);
$pages = ($maxpages && $maxpages < $realpages) ? $maxpages : $realpages;
// 計算顯示範圍 $from ~ $to
if ($page > $pages) { $from = 1; $to = $pages; }
else {
$from = $curpage - $offset;
$to = $from + $page - 1;
if ($from < 1) { $to = $curpage + 1 - $from; $from = 1; if ($to - $from < $page) $to = $page; }
elseif ($to > $pages) { $from = $pages - $page + 1; $to = $pages; }
}
// 組裝 HTML:首頁、上一頁、頁碼、下一頁、尾頁、跳轉輸入框
$multipage = ($curpage - $offset > 1 && $pages > $page)
? '<a class="p_redirect" href="'.$mpurl.'page=1">|<</a>' : '';
$multipage .= ($curpage > 1)
? '<a class="p_redirect" href="'.$mpurl.'page='.($curpage-1).'">«</a>' : '';
for ($i = $from; $i <= $to; $i++) {
$multipage .= ($i == $curpage)
? '<a class="p_curpage">'.$i.'</a>'
: '<a href="'.$mpurl.'page='.$i.'" class="p_num">'.$i.'</a>';
}
$multipage .= ($curpage < $pages)
? '<a class="p_redirect" href="'.$mpurl.'page='.($curpage+1).'">»</a>' : '';
$multipage .= ($to < $pages)
? '<a class="p_redirect" href="'.$mpurl.'page='.$pages.'">>|</a>' : '';
// 最大頁限制提示與跳轉輸入框
$multipage .= ($curpage == $maxpages)
? '<a class="p_redirect" href="misc.php?action=maxpages&pages='.$maxpages.'">»</a>' : '';
$multipage .= ($pages > $page)
? '<a class="p_pages" style="padding:0"><input class="p_input" type="text" name="custompage" onKeyDown="if(event.keyCode==13){window.location=\''.$mpurl.'page=\'+this.value;return false;}"></a>' : '';
$multipage = $multipage
? '<div class="p_bar"><a class="p_total"> '.$num.' </a><a class="p_pages"> '.$curpage.'/'.$realpages.' </a>'.$multipage.'</div>'
: '';
}
return $multipage;
}
$page=10),當前頁置中($offset=2),首尾邊界自動調整。p_redirect(首/尾/上/下)、p_curpage(當前)、p_num(普通頁碼)、p_bar(容器),便於樣式客製。$mpurl 是否含 ?,決定用 ? 或 & 串接 page=。| 問題 | 建議改進 |
|---|---|
| 直接輸出 HTML,難以複用/測試 | 改為回傳結構化陣列(['pages'=>[...], 'current'=>N, 'total'=>M, 'url_template'=>'...']),由視圖層渲染 |
內聯 JavaScript (onKeyDown) | 採用 addEventListener 綁定,支援 CSP |
| 硬編碼樣式類別 | 允許傳入類別名稱前綴或模板回呼 |
| 不支援 AJAX 分頁 | 提供 data-page 屬性,前端自行攔截 |
使用舊式 ereg 等棄用函數(其他處) | 全面遷移至 preg_* / filter_var |
function pager(array $opts = []) {
$defaults = [
'total' => 0, // 總筆數
'per_page' => 20, // 每頁筆數
'current' => 1, // 當前頁
'url_tpl' => '?page=%d', // URL 模板,%d 會被頁碼替換
'window' => 10, // 顯示頁碼數
'max_pages' => 0, // 最大頁數限制
];
$o = array_merge($defaults, $opts);
$real_pages = (int)ceil($o['total'] / $o['per_page']);
$pages = $o['max_pages'] && $o['max_pages'] < $real_pages ? $o['max_pages'] : $real_pages;
if ($pages <= 1) return ['pages' => [], 'current' => 1, 'total' => $real_pages, 'html' => ''];
$half = (int)($o['window'] / 2);
$from = max(1, $o['current'] - $half);
$to = min($pages, $from + $o['window'] - 1);
if ($to - $from + 1 < $o['window']) $from = max(1, $to - $o['window'] + 1);
$items = [];
if ($o['current'] > 1) $items[] = ['page' => 1, 'text' => '««', 'rel' => 'first'];
if ($o['current'] > 1) $items[] = ['page' => $o['current']-1, 'text' => '«', 'rel' => 'prev'];
for ($i = $from; $i <= $to; $i++) {
$items[] = [
'page' => $i,
'text' => (string)$i,
'current'=> ($i == $o['current']),
'url' => sprintf($o['url_tpl'], $i),
];
}
if ($o['current'] < $pages) $items[] = ['page' => $o['current']+1, 'text' => '»', 'rel' => 'next'];
if ($o['current'] < $pages) $items[] = ['page' => $pages, 'text' => '»»', 'rel' => 'last'];
return [
'items' => $items,
'current' => $o['current'],
'total' => $real_pages,
'per_page'=> $o['per_page'],
'count' => $o['total'],
];
}
// 視圖層渲染(Twig / Blade / 原生 PHP)
/*
<nav aria-label="Pagination">
<ul class="pagination">
<?php foreach ($pager['items'] as $it): ?>
<li class="page-item <?= $it['current'] ? 'active' : '' ?>">
<?php if ($it['current']): ?>
<span class="page-link"><?= $it['text'] ?></span>
<?php else: ?>
<a class="page-link" href="<?= $it['url'] ?>" <?= isset($it['rel']) ? 'rel="'.$it['rel'].'"' : '' ?>><?= $it['text'] ?></a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
*/
Discuz! 的 multi() 在當年是極佳的實用主義作品,但現代架構強調關注點分離與可測試性。將分頁邏輯拆分為「計算資料結構」與「渲染 HTML」兩階段,並配合語義化 HTML5 與無障礙屬性(aria-label、rel),能更好地適應響應式設計、前端框架整合與自動化測試需求。