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 接口两种场景,前端通过递归遍历即可实现任意层级展示。