首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

ThinkPHP 二級分類循環讀取實戰:遞歸樹構建與模板渲染

     編輯:PHP 相關知識

ThinkPHP 無限級分類:二級循環、遞歸樹構建、模板輸出完整指南

分類管理是 CMS、電商系統的核心功能。本文展示如何在 ThinkPHP 中高效讀取二級/多級分類,構建樹形結構,並在模板中優雅渲染。

數據庫設計

CREATE TABLE `category` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
    `pid` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父級ID,0為頂級',
    `name` VARCHAR(50) NOT NULL,
    `sort` INT UNSIGNED NOT NULL DEFAULT 0,
    `status` TINYINT UNSIGNED NOT NULL DEFAULT 1,
    PRIMARY KEY (`id`),
    KEY `idx_pid_status` (`pid`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

方案一:兩次查詢 + PHP 組裝二級數組(適合固定二級)

<?php
// 控制器
public function index() {
    // 1. 獲取所有一級分類
    $topCates = Db::name('category')
        ->where(['pid' => 0, 'status' => 1])
        ->order('sort ASC, id ASC')
        ->select();
    
    if ($topCates) {
        // 2. 獲取所有二級分類(一次查詢避免 N+1)
        $pids = array_column($topCates, 'id');
        $childCates = Db::name('category')
            ->where(['pid' => ['IN', $pids], 'status' => 1])
            ->order('sort ASC, id ASC')
            ->select();
        
        // 3. 按 pid 分組
        $childGroup = [];
        foreach ($childCates as $cate) {
            $childGroup[$cate['pid']][] = $cate;
        }
        
        // 4. 掛載到一級分類
        foreach ($topCates as &$cate) {
            $cate['children'] = $childGroup[$cate['id']] ?? [];
        }
    }
    
    $this->assign('categories', $topCates);
    return $this->fetch();
}
?>

方案二:遞歸構建無限級樹(通用方案)

<?php
// 工具類或模型方法
function buildTree(array $data, int $pid = 0, string $pk = 'id', string $pidKey = 'pid'): array {
    $tree = [];
    foreach ($data as $item) {
        if ($item[$pidKey] == $pid) {
            $children = buildTree($data, $item[$pk], $pk, $pidKey);
            if ($children) $item['children'] = $children;
            $tree[] = $item;
        }
    }
    return $tree;
}

// 控制器使用
public function tree() {
    $allCates = Db::name('category')
        ->where('status', 1)
        ->order('sort ASC, id ASC')
        ->select()
        ->toArray(); // 轉純數組提高遞歸性能
    
    $tree = buildTree($allCates);
    $this->assign('categories', $tree);
    return $this->fetch();
}
?>

方案三:數據庫層面遞歸查詢(MySQL 8.0+ CTE)

<?php
// 僅獲取某分類下的所有子孫 ID(如刪除分類連帶子分類)
public function getDescendantIds(int $cateId): array {
    $sql = <<column('id');
}
?>

模板渲染:ThinkPHP 模板標籤

<!-- 二級分類渲染 -->
<ul class="cate-list">
    {volist name="categories" id="cate"}
    <li>
        <a href="{:url('list', ['cid' => $cate.id])}">{$cate.name}</a>
        {if !empty($cate.children)}
        <ul class="sub-cate">
            {volist name="cate.children" id="sub"}
            <li><a href="{:url('list', ['cid' => $sub.id])}">{$sub.name}</a></li>
            {/volist}
        </ul>
        {/if}
    </li>
    {/volist}
</ul>

多級遞歸模板(通用)

<!-- 定義遞歸模板文件 widget/cate_tree.html -->
<ul>
    {volist name="categories" id="cate"}
    <li>
        <a href="{:url('list', ['cid' => $cate.id])}">{$cate.name}</a>
        {if !empty($cate.children)}
            {include file="widget/cate_tree" categories="$cate.children" /}
        {/if}
    </li>
    {/volist}
</ul>

<!-- 主模板調用 -->
{include file="widget/cate_tree" categories="$categories" /}

前端交互:無限級下拉選單(Select2 / TreeSelect)

<?php
// API 返回樹形 JSON
public function apiTree() {
    $all = Db::name('category')->where('status', 1)->select()->toArray();
    $tree = buildTree($all);
    return json($tree);
}
?>
<!-- 前端 Vue + Element UI TreeSelect -->
<el-tree-select
    v-model="form.cate_id"
    :data="cateTree"
    :props="{label: 'name', value: 'id', children: 'children'}"
    placeholder="請選擇分類"
    @node-click="handleNodeClick"
></el-tree-select>

<script>
export default {
    data() { return { cateTree: [], form: { cate_id: '' } }; },
    mounted() {
        fetch('/category/apiTree').then(r => r.json()).then(data => this.cateTree = data);
    }
}
</script>

性能優化:緩存分類樹

<?php
public function getCachedTree() {
    return cache('category_tree', function() {
        $all = Db::name('category')->where('status', 1)->order('sort, id')->select()->toArray();
        return buildTree($all);
    }, 3600); // 緩存 1 小時
}

// 後台更新分類後清除緩存
public function clearCateCache() {
    cache('category_tree', null);
    return json(['code' => 1]);
}
?>

總結選型建議

方案適用場景查詢次數代碼複雜度擴展性
兩次查詢組裝固定二級、數據量小2低僅二級
PHP 遞歸構建無限級、數據量 < 50001中全支援
MySQL CTE 遞歸MySQL 8.0+、需單獨查子樹1中全支援
緩存樹 + 異步更新高併發讀取、分類變動少0 (命中緩存)高全支援

建議:中小項目用 PHP 遞歸 + 緩存,大型項目考慮 Closure Table(閉包表) 或 物化路徑 結構實現高效祖先/後代查詢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved