计算机网络/计算机科学与应用/系统/运维/开发

ThinkPHP5.1 无限极分类

ThinkPHP5.1 中实现无限极分类的核心思路是数据库表设计父子关联字段 + PHP 递归 / 迭代处理平级数据为树形结构,以下提供最常用的递归实现方案(代码简洁、易理解,适配 99% 业务场景),包含数据库设计、模型封装、控制器调用、前端渲染全流程,可直接复用。
一、数据库表设计(核心:pid 关联父级 ID)
设计分类表(示例表名 category),核心字段 pid 用于标识当前分类的父级分类 ID,规则:
顶级分类的 pid = 0;
子分类的 pid = 父级分类的 id;
支持无限级嵌套(子分类可作为新的父级,下挂子子分类)。
建表 SQL(MySQL)
sql
CREATE TABLE `category` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类ID',
  `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父级ID,0为顶级分类',
  `name` varchar(50) NOT NULL COMMENT '分类名称',
  `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序值(升序)',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态 1-启用 0-禁用',
  `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`),
  KEY `sort` (`sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='无限极分类表';
测试数据(快速验证)
插入 3 级分类测试数据,直观体现父子关联:
sql
INSERT INTO `category` (`pid`, `name`, `sort`) VALUES
(0, '电子产品', 1),  -- 顶级分类1
(0, '生活用品', 2),  -- 顶级分类2
(1, '手机', 1),      -- 二级分类(父级:电子产品)
(1, '电脑', 2),      -- 二级分类(父级:电子产品)
(3, '苹果手机', 1),  -- 三级分类(父级:手机)
(3, '华为手机', 2),  -- 三级分类(父级:手机)
(2, '洗漱用品', 1);  -- 二级分类(父级:生活用品)
二、模型封装(核心:递归方法,通用可复用)
在 ThinkPHP5.1 的模型层(application/index/model/ 目录,无则新建)创建 Category.php 模型,封装获取平级分类和递归生成树形结构的方法,实现业务与数据解耦。
模型代码(application/index/model/Category.php)
php
运行
<?php
namespace app\index\model;
use think\Model;
class Category extends Model
{
    // 开启自动时间戳(对应create_time/update_time)
    protected $autoWriteTimestamp = true;
    // 时间字段类型(int 时间戳)
    protected $type = [
        'create_time' => 'integer',
        'update_time' => 'integer'
    ];
    /**
     * 获取所有启用的平级分类(按排序+ID升序)
     * @return array 平级分类数组
     */
    public static function getList()
    {
        return self::where('status', 1)
            ->order('sort ASC, id ASC')
            ->field('id, pid, name')  // 按需查询字段,减少数据量
            ->select()
            ->toArray();  // 转换为纯数组,方便后续处理
    }
    /**
     * 递归生成无限极分类树形结构
     * @param array $list 平级分类数组(getList返回的结果)
     * @param int $pid 父级ID,默认0(查询顶级分类)
     * @param int $level 分类层级,默认0(用于前端缩进)
     * @return array 树形分类数组
     */
    public static function buildTree($list, $pid = 0, $level = 0)
    {
        // 定义空数组,存储当前层级的分类
        $tree = [];
        foreach ($list as $v) {
            // 匹配当前父级ID的分类
            if ($v['pid'] == $pid) {
                $v['level'] = $level;  // 追加层级字段,前端可根据level做缩进
                // 递归查询当前分类的子分类,层级+1
                $v['children'] = self::buildTree($list, $v['id'], $level + 1);
                // 将当前分类加入树形数组
                $tree[] = $v;
            }
        }
        return $tree;
    }
}
方法说明
getList():仅查询启用状态的分类,按排序和 ID 升序,返回纯数组(避免 ThinkPHP 对象干扰);
buildTree():核心递归方法,通过遍历平级数组、匹配pid实现父子关联,自动追加level(层级)和children(子分类数组)字段,无嵌套时 children 为空数组,不影响前端遍历。
三、控制器调用(简洁易用,直接获取树形数据)
在控制器中调用模型的方法,一行代码即可获取树形分类数据,支持直接返回给前端(接口开发)或赋值给模板(后端渲染)。
控制器代码(示例:application/index/controller/Index.php)
php
运行
<?php
namespace app\index\controller;
use app\index\model\Category;
use think\Controller;
class Index extends Controller
{
    /**
     * 获取无限极分类树形数据(示例:后端渲染模板)
     */
    public function category()
    {
        // 1. 获取平级分类列表
        $cateList = Category::getList();
        // 2. 生成树形结构(核心一行代码)
        $cateTree = Category::buildTree($cateList);
        
        // 3. 赋值给模板,供前端渲染
        $this->assign('cateTree', $cateTree);
        // 4. 加载模板
        return $this->fetch('category');
    }
    /**
     * 接口返回树形分类数据(示例:前后端分离,返回JSON)
     */
    public function cateApi()
    {
        $cateList = Category::getList();
        $cateTree = Category::buildTree($cateList);
        // 返回JSON格式,包含状态码和数据
        return json([
            'code' => 200,
            'msg'  => '获取成功',
            'data' => $cateTree
        ]);
    }
}
调用结果示例(树形数组)
执行Category::buildTree(Category::getList())后,返回的树形数据结构如下(清晰的父子嵌套):
php
运行
[
    [
        "id" => 1,
        "pid" => 0,
        "name" => "电子产品",
        "level" => 0,
        "children" => [
            [
                "id" => 3,
                "pid" => 1,
                "name" => "手机",
                "level" => 1,
                "children" => [
                    ["id"=>5,"pid"=>3,"name"=>"苹果手机","level"=>2,"children"=>[]],
                    ["id"=>6,"pid"=>3,"name"=>"华为手机","level"=>2,"children"=>[]]
                ]
            ],
            ["id"=>4,"pid"=>1,"name"=>"电脑","level"=>1,"children"=>[]]
        ]
    ],
    [
        "id" => 2,
        "pid" => 0,
        "name" => "生活用品",
        "level" => 0,
        "children" => [
            ["id"=>7,"pid"=>2,"name"=>"洗漱用品","level"=>1,"children"=>[]]
        ]
    ]
]
四、前端渲染(2 种常用场景,直接套用)
场景 1:后端模板渲染(ThinkPHP 内置模板引擎,推荐后台管理)
利用volist标签递归遍历树形数组,通过level字段实现分类名称缩进(直观体现层级),示例模板文件application/index/view/index/category.html:
html
预览
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>无限极分类</title>
    <style>
        /* 按层级缩进,每级缩进20px */
        .cate-item { margin-left: {$vo.level}*20px; line-height: 30px; }
    </style>
</head>
<body>
    <div>
        {volist name="cateTree" id="vo"}
            <!-- 一级分类 -->
            <div>{$vo.name}</div>
            <!-- 递归渲染子分类 -->
            {volist name="vo.children" id="child"}
                <div>{$child.name}</div>
                <!-- 三级及以上分类,继续递归volist即可 -->
                {volist name="child.children" id="grandson"}
                    <div>{$grandson.name}</div>
                {/volist}
            {/volist}
        {/volist}
    </div>
</body>
</html>
场景 2:前后端分离(JSON 接口 + JS 递归渲染,推荐前台展示)
前端通过 AJAX 请求控制器cateApi方法获取 JSON 数据,用JS 递归函数渲染(适配任意层级,无需手动写多层循环),示例原生 JS 代码:
html
预览
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>无限极分类-接口版</title>
</head>
<body>
    <ul id="cateUl"></ul>
    <script>
        // 发起AJAX请求获取分类数据
        fetch('/index/index/cateApi')  // 替换为你的实际接口地址
            .then(res => res.json())
            .then(data => {
                if (data.code === 200) {
                    renderCate(data.data, document.getElementById('cateUl'));
                }
            });
        /**
         * JS递归渲染树形分类
         * @param {Array} tree 树形分类数组
         * @param {Element} parent 父级DOM节点
         */
        function renderCate(tree, parent) {
            tree.forEach(item => {
                // 创建li节点,添加缩进样式
                const li = document.createElement('li');
                li.style.marginLeft = item.level * 20 + 'px';
                li.innerText = item.name;
                parent.appendChild(li);
                // 若有子分类,创建ul并递归渲染
                if (item.children.length > 0) {
                    const subUl = document.createElement('ul');
                    li.appendChild(subUl);
                    renderCate(item.children, subUl);
                }
            });
        }
    </script>
</body>
</html>
五、关键优化与注意事项
1. 性能优化(适用于大数据量)
本文递归方案基于一次性查询所有数据(SELECT *),适用于分类数量≤1000 的场景(99% 业务满足);若分类数量极大(万级),可优化为分步查询(子分类单独查) 或缓存树形数据(用 ThinkPHP 缓存机制,避免每次请求都递归)。
2. 字段按需查询
模型getList()中仅查询id, pid, name核心字段,避免查询冗余字段(如create_time、update_time),减少数据传输和内存占用。
3. 递归终止条件
本方案中,当分类无下一级子分类时,$v['children']会被赋值为空数组,递归自动终止,无死循环风险,无需额外判断。
4. 排序优先级
数据库表中增加sort字段,支持手动调整分类顺序,查询时按sort ASC, id ASC排序,满足业务的自定义排序需求。
六、扩展:迭代法实现(非递归,适用于超大数据量)
若分类数量极多(万级),递归法可能出现栈溢出,可使用迭代法(循环) 实现,核心思路是通过ID作为键名构建映射,再遍历匹配父级,以下是模型中替换的buildTree迭代方法,调用方式与递归法完全一致:
php
运行
/**
 * 迭代法生成无限极分类树形结构(无栈溢出风险,适用于超大数据量)
 * @param array $list 平级分类数组
 * @param int $pid 父级ID,默认0
 * @return array 树形分类数组
 */
public static function buildTree($list, $pid = 0)
{
    // 1. 构建ID=>分类的映射,方便快速查找
    $map = [];
    $tree = [];
    foreach ($list as $v) {
        $v['children'] = [];
        $map[$v['id']] = $v;
    }
    // 2. 遍历匹配父级,组装树形
    foreach ($list as $v) {
        if (isset($map[$v['pid']])) {
            // 子分类加入父分类的children
            $map[$v['pid']]['children'][] = &$map[$v['id']];
        } else {
            // 顶级分类直接加入树形
            if ($v['pid'] == $pid) {
                $tree[] = &$map[$v['id']];
            }
        }
    }
    unset($map); // 释放内存
    return $tree;
}
总结
ThinkPHP5.1 实现无限极分类的核心要点:
数据库必须包含pid字段,顶级分类pid=0,建立父子关联;
模型封装平级查询和树形构建方法,实现代码复用,推荐递归法(简洁),超大数据量用迭代法;
核心递归方法buildTree通过匹配pid实现嵌套,自动追加level(层级)和children(子分类)字段;
控制器一行代码调用,支持模板渲染和JSON 接口两种场景,前端通过递归遍历即可实现任意层级展示。


读书和赚钱都是一个人最好的修行,前者使人不惑,后者使人不屈,二者结合,便可不困于世,不流于俗

评论

^