無限分類(多級分類)是電商、CMS、權限系統核心模型。PHP 遞歸構建樹形結構是經典方案,本文從數據庫設計到前端渲染完整演示。
CREATE TABLE categories (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
pid INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父級 ID,0 為頂級',
name VARCHAR(50) NOT NULL,
sort INT UNSIGNED DEFAULT 0,
status TINYINT DEFAULT 1,
INDEX idx_pid (pid)
);
$items = [
['id'=>1, 'pid'=>0, 'name'=>'電子產品'],
['id'=>2, 'pid'=>0, 'name'=>'圖書音像'],
['id'=>3, 'pid'=>1, 'name'=>'手機通訊'],
['id'=>4, 'pid'=>1, 'name'=>'電腦辦公'],
['id'=>5, 'pid'=>3, 'name'=>'智能手機'],
['id'=>6, 'pid'=>3, 'name'=>'功能機'],
['id'=>7, 'pid'=>4, 'name'=>'筆記本'],
['id'=>8, 'pid'=>4, 'name'=>'台式機'],
['id'=>9, 'pid'=>5, 'name'=>'Android 手機'],
['id'=>10, 'pid'=>5, 'name'=>'iOS 手機'],
];
function buildTree(array $items, int $pid = 0): array {
$tree = [];
foreach ($items as $item) {
if ($item['pid'] == $pid) {
// 遞歸獲取子節點
$children = buildTree($items, $item['id']);
if ($children) {
$item['children'] = $children;
}
$tree[] = $item;
}
}
return $tree;
}
$tree = buildTree($items);
// 結果:包含 children 遞歸嵌套的樹形數組
function buildTreeOptimized(array $items, int $pid = 0): array {
// 1. 建立 id => item 索引(引用)
$indexed = [];
foreach ($items as &$item) {
$indexed[$item['id']] = &$item;
$item['children'] = []; // 初始化
}
unset($item); // 打破引用
// 2. 掛載子節點到父節點
foreach ($indexed as $id => &$item) {
if ($item['pid'] != 0 && isset($indexed[$item['pid']])) {
$indexed[$item['pid']]['children'][] = &$item;
}
}
unset($item);
// 3. 篩選頂級節點
$tree = [];
foreach ($indexed as $item) {
if ($item['pid'] == $pid) {
$tree[] = $item;
}
}
return $tree;
}
// 避免重複遍歷,大數據量 (萬級+) 顯著提速
<!-- TreeNode.vue -->
<script setup>
defineProps(['node']);
</script>
<template>
<div class="tree-node">
<span @click="expanded = !expanded">
{{ node.name }} ({{ node.id }})
</span>
<ul v-if="expanded && node.children?.length">
<TreeNode v-for="child in node.children" :key="child.id" :node="child" />
</ul>
</div>
</template>
<style scoped>
.tree-node > span { cursor: pointer; user-select: none; }
.tree-node ul { margin-left: 20px; border-left: 1px solid #ddd; padding-left: 10px; }
</style>
| 需求 | 實現要點 |
|---|---|
| 獲取某節點所有子孫 ID | 遞歸收集 array_column($tree, 'id') 或 CTE 查詢 |
| 獲取節點完整路徑 (麵包屑) | 遞歸向上查找父節點,或存儲 path 字段 (物化路徑) |
| 移動節點 (修改 pid) | 檢查目標非自身子孫,事務更新 pid + 所有子孫 path |
| 軟刪除連帶子孫 | 遞歸更新 status=0,或定時任務清理 |
| 排序調整 | 拖拽前端傳新順序,批量 UPDATE sort |
path = '1/3/5/',LIKE 查詢子樹,移動需更新全子樹 pathlft/rgt 值,查詢極快,移動極慢(需重算全表)WITH RECURSIVE$visited = [] 檢測foreach (... as &$v) 後必須 unset($v)選擇合適模型:簡單業務用邻接表+遞歸,高頻讀寫大樹用閉包表/CTE,前端組件化渲染體驗最佳。