採用鄰接表模型在 MySQL 儲存選單結構,欄位包含:menu_id、menu(名稱)、menu_grade(層級 1=主選單)、menu_superior(父選單 ID)。透過遞迴函數 my_menu() 動態生成可摺疊的樹狀導航,支援無限層級。
CREATE TABLE menu (
menu_id INT(11) NOT NULL AUTO_INCREMENT,
menu VARCHAR(20) NOT NULL,
menu_grade INT(11) NOT NULL,
menu_superior INT(11) NOT NULL,
PRIMARY KEY (menu_id)
);
INSERT INTO menu VALUES
(1, '計算機', 1, 0),
(2, '編程', 2, 1),
(3, '網絡', 2, 1),
(4, 'PHP與MySql', 3, 2),
(5, 'C語言', 3, 2),
(6, '網頁制作', 3, 3),
(7, 'TCP、IP協議', 3, 3),
(8, '數學', 1, 0),
(9, '高等數學', 2, 8),
(10, '線性代數', 3, 9),
(11, '離散數學', 3, 9),
(12, '初等數學', 2, 8),
(13, '文學', 1, 0),
(14, '中國文學', 2, 13),
(15, 'php', 4, 4),
(16, 'mysql', 4, 4);
my_menu()function my_menu($menu_content, $i, $menu_grade_temp, $menu_superior_temp) {
global $PHP_SELF;
$temp1 = $menu_grade_temp + 1;
$menu_superior_temp_array = explode("/", $menu_superior_temp);
for ($t = 0; $t < $i; $t++) {
$menu_array = explode("/", $menu_content[$t]);
// 條件:層級符合 且 父ID符合當前路徑對應層級的父ID
if (($menu_array[2] == $menu_grade_temp) &&
($menu_array[3] == $menu_superior_temp_array[$menu_grade_temp - 1])) {
// 顯示層級縮排箭頭
for ($p = 1; $p <= $menu_grade_temp; $p++) { echo ">"; }
$temp3 = $menu_superior_temp_array;
$temp3[$menu_grade_temp] = $menu_array[0];
$temp2 = implode("/", $temp3);
// 判斷是否為當前展開的分支
if ($menu_array[0] == $menu_superior_temp_array[$temp1 - 1]) {
$temp5 = $temp1 - 1;
$temp3[$menu_grade_temp] = "";
$temp6 = implode("/", $temp3);
// 已展開:顯示「收合」連結,遞迴輸出子選單
echo "<a href=\"" . $PHP_SELF . "?menu_grade_temp=" . $temp5 . "&menu_superior_temp=" . $temp6 . "\">" . htmlspecialchars($menu_array[1]) . "</a><br>";
my_menu($menu_content, $i, $temp1, $temp2);
} else {
// 未展開:顯示「展開」連結
$temp3[$menu_grade_temp + 1] = "";
$temp6 = implode("/", $temp3);
echo "<a href=\"" . $PHP_SELF . "?menu_grade_temp=" . $temp1 . "&menu_superior_temp=" . $temp6 . "\">" . htmlspecialchars($menu_array[1]) . "</a></br>";
}
}
}
}
// 1. 連線資料庫
mysql_connect("localhost", "root", "zmxj");
mysql_select_db("joss");
// 2. 讀取所有選單,依層級排序
$query = "SELECT * FROM menu ORDER BY menu_grade";
$db_data = mysql_query($query);
$i = 0;
while (list($menu_id, $menu, $menu_grade, $menu_superior) = mysql_fetch_row($db_data)) {
$menu_content[$i] = "$menu_id/$menu/$menu_grade/$menu_superior";
$i++;
}
// 3. 初始化參數
if ($menu_grade_temp == "") { $menu_superior_temp = 0; }
// 4. 從第 1 層開始渲染
my_menu($menu_content, $i, 1, $menu_superior_temp);
menu_grade_temp(目前層級)、menu_superior_temp(各層級對應的父 ID 串列,以 / 分隔)維持展開/收合狀態,無需 Session。> 字元重複次數表示層級深度。| 維度 | 現狀 | 建議改進 |
|---|---|---|
| 資料結構 | 鄰接表 | 改用 Closure Table 或 Nested Set 支援高效率子樹查詢、移動、排序 |
| 前端呈現 | PHP 直輸 HTML + 超連結換頁 | 改為 JSON API + 前端 Vue/React 組件,支援 AJAX 懶加載、動畫、鍵盤導航 |
| 狀態管理 | URL 參數 | 前端狀態或 localStorage 記住展開狀態,刷新不重置 |
| 權限控制 | 無 | 加入 role_id 欄位,查詢時 JOIN 使用者權限表 |
| 效能 | 全表撈入記憶體遞迴 | 大型選單採用遞迴 CTE 或 Closure Table 分頁載入 |
// GET /api/menu?parent=0
// 回傳 JSON
[
{"id":1,"label":"計算機","grade":1,"parent":0,"children":[
{"id":2,"label":"編程","grade":2,"parent":1,"children":[...]},
{"id":3,"label":"網絡","grade":2,"parent":1,"children":[...]}
]},
{"id":8,"label":"數學","grade":1,"parent":0,"children":[...]}
]
這個經典範例展示了 PHP 早期「資料庫+遞迴函數+URL 狀態」構建動態導航的完整思路。雖然渲染方式屬於伺服器端直出時代,但樹狀資料的遞迴處理、狀態編碼、層級關聯邏輯,在現代前端組件庫的 Tree/Menu 實作中依然是核心演算法。