[TOC] # 模型创建 假如现在需要开发一个CMS,文档以创建一个叫Menu栏目的模型为例。 ![](https://img.kancloud.cn/25/97/25979bca18557c41dc72a253afcc8007_803x454.png) 1、(必须)字段中勾选上parent_id字段,父级ID`parent_id`是**必须的,也只能叫它**; 2、(必须)无极限数字大于0,代表最多支持到的层级,确实要无限级就填写大点 3、建议排序设置为正序,想这种模型一般都希望是按照添加的先后顺序,先添加的优先显示 4、建议勾选上`list_order`,用于模型可以自定义顺序 无限级中,系统默认还可以帮你维护3个字段: `family`:家族族谱,可有可无,如果需要字段名必须是它 `level`:当前行的级别,可有可无,如果需要字段名必须是它 `children_count`:当前行下级数量,可有可无,如果需要字段名必须是它 现在基本上一个无限级功能的模型都做好了(就多了一个parent_id字段,然后无极限数量>0,其他的交给系统了)... 为了演示后面的功能,让栏目的功能更加合理,Menu我再自定义一个字段: ![](https://img.kancloud.cn/e7/e1/e7e1a5723fc13f8c8d3b48f4252f25e7_448x203.png) `is_nav`:表示栏目是否显示(实际开发中有的栏目可能会不希望前台显示出来,到时候前台的栏目查询is_nav=1的数据即可;该字段和无级限功能无关,只是你自己的业务而已) 随便添加一些数据,看下数据表结构: ![](https://img.kancloud.cn/de/31/de31c39bfb9aa405bc1cabd4041f874b_788x286.png) # 列表改造 这个时候,其实无限极功能已经开发好了,但是列表页并不太友好。可以简单的改造下: 找到对应的后台控制器,比如这里的Menu:`app\admin\controller\Menu` ## 方式一:(不支持ajax) ~~~ // 引入一个trait use \woo\common\controller\traits\Tree; public function index() { // 列表页稍微改下 return $this->showTree(); } ~~~ 还可以根据情况设置一些参数: ~~~ return $this->showTree('', 每页数量 默认5000, 是否显示翻页 默认 false, 是否自动折叠 默认true); ~~~ 这样就默认全部展开,不折叠了: ~~~ return $this->showTree('', 5000, false, false); ~~~ 自行测试显示效果... ***** ## 方式二:(支持ajax,数据多可以采用该方式) ~~~ // 引入trait use \woo\common\controller\traits\Tree; public function index() { // 开启ajax加载下级 如果数据量比较多 可以开启 // $this->assign->options['is_ajax'] = true; // 关闭 添加一级分类 按钮 //$this->local['tool_bar']['create'] = false; // 关闭 排序一级 分类 按钮 //$this->local['tool_bar']['sortable'] = false; // 关闭 添加子分类 按钮 //$this->local['item_tool_bar']['create_child'] = false; // 关闭 排序子分类 按钮 //$this->local['item_tool_bar']['sort_child'] = false; // 关闭 编辑 按钮 //$this->local['item_tool_bar']['modify'] = false; // 关闭删除 子分类 按钮 //$this->local['item_tool_bar']['delete'] = false; // 添加一个字段显示 默认只显示 id和标题(主显字段) $this->local['fields'] = [ 'children_count' => [ 'title' => '子' . $this->mdl->cname . '数', 'templet' => '{{# if (d.children_count> 0){ }}{{d.children_count}}{{#} }}', 'style' => 'color:#009688;' ] ]; return $this->showList(); } ~~~ ![](https://img.kancloud.cn/63/e9/63e90dafbf8d15601ab21bcae9ee7795_1338x254.png) >V2.0.2已经支持无限级模型列表自动生成"方式二"代码,但是有个“麻烦”就是需要你删除默认生成的控制器文件,然后在后台重新创建该控制器。 # 缓存结构 如果数据量大的表,不建议调用接下来会自动生成缓存的相关函数,列表使用默认的或第二种方式的ajax加载都不会大批量的生成缓存。 接下来,我们在任意可以执行到的地方,比如就在index方法测试代码: ~~~ pr(tree('Menu')); ~~~ `tree`函数本身的作用和用法,请查看后面的内容。通过tree函数可以获取到指定无限级模型的所有数据。数据结构打印出来大概如下: ~~~ Array ( [threaded] => Array ( [1] => Array ( [4] => Array ( ) [5] => Array ( ) ) [2] => Array ( [6] => Array ( ) [7] => Array ( ) ) [3] => Array ( [8] => Array ( ) [9] => Array ( ) ) ) [children] => Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [1] => Array ( [0] => 4 [1] => 5 ) [4] => Array ( ) [5] => Array ( ) [2] => Array ( [0] => 6 [1] => 7 ) [6] => Array ( ) [7] => Array ( ) [3] => Array ( [0] => 8 [1] => 9 ) [8] => Array ( ) [9] => Array ( ) ) [list] => Array ( [1] => Array ( [id] => 1 [parent_id] => 0 [title] => 关于我们 [image] => [list_order] => 1 [family] => ,1, [level] => 1 [children_count] => 2 [create_time] => 2020-09-27 17:11:35 [update_time] => 2020-09-27 17:23:58 [delete_time] => 0 [is_nav] => 1 ) [2] => Array ( [id] => 2 //... [is_nav] => 1 ) //... 3 4 5 6... 结构一致的内容已经删除 [9] => Array ( [id] => 9 [parent_id] => 3 [title] => 夏季产品 [image] => [list_order] => 9 [family] => ,3,9, [level] => 2 [children_count] => 0 [create_time] => 2020-09-27 17:13:17 [update_time] => 2020-09-27 17:13:17 [delete_time] => 0 [is_nav] => 1 ) ) ) ~~~ 我们会发现有3个主要的键(多观察下数据结构): `threaded`:整表数据螺旋结构;一个N维数组,里面只有主键id值,根据层级关系从一级分类开始一直包含下去(一般使用比较少) `children`:一个二维数组,里面只有主键id值;给定任意一个id,包含它的子级分类id `list`:一个二维数组,以主键id值为键,以整行数据为值;给定任意一个id,包含该id对应的所有字段数据 灵活的运用上面三大缓存键,就可以帮你解决很多场景的数据取值了,具体怎么取值查看后面章节。 虽然数据是存在缓存中,不需要每次读取数据库提升效率,但数据发送改变以后系统会自动清除对应缓存,下次获取的时候会再生成缓存,不用担心数据的实时问题。 # 自定义缓存 上面的缓存三大键是查询的所有数据,但我们做CMS应该只显示`is_nav=1`的数据吧,难道我前台遍历栏目的时候还需要自己判断? 如果我可以再缓存中只查询到`is_nav=1`的数据那不就可以了吗? 接下来,我们在`/app/commom/model/trait/MenuTrait.php`文件中(也就是模型中),加入以下方法和代码: ~~~ public function getCustomCache() { return [ // nav 是你自定义的缓存键,不要和默认的3大键一致 'nav' => [ // 通过where 自定义缓存的查询条件 order自定义缓存的排序方式 'where' => [ ['is_nav', '=', 1] ] ] ]; } ~~~ 每当生成缓存的时候,系统都会判断是否有`getCustomCache`方法,用于定义开发者自定义的缓存键 和 查询条件和排序方式等。 然后,随便找一条栏目数据编辑提交一次或点击首页的清除缓存,再刷新首页以后,查看下现在的缓存数据结构: ~~~ Array ( [threaded] => Array ( [1] => Array ( [4] => Array ( ) [5] => Array ( ) ) [2] => Array ( [6] => Array ( ) [7] => Array ( ) ) [3] => Array ( [8] => Array ( ) [9] => Array ( ) ) ) [children] => Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [1] => Array ( [0] => 4 [1] => 5 ) [4] => Array ( ) [5] => Array ( ) [2] => Array ( [0] => 6 [1] => 7 ) [6] => Array ( ) [7] => Array ( ) [3] => Array ( [0] => 8 [1] => 9 ) [8] => Array ( ) [9] => Array ( ) ) [nav] => Array ( [1] => Array ( [4] => Array ( ) ) [2] => Array ( [6] => Array ( ) [7] => Array ( ) ) ) [nav_children] => Array ( [0] => Array ( [0] => 1 [1] => 2 ) [1] => Array ( [0] => 4 ) [4] => Array ( ) [2] => Array ( [0] => 6 [1] => 7 ) [6] => Array ( ) [7] => Array ( ) ) [list] => Array ( [1] => Array ( [id] => 1 [parent_id] => 0 [title] => 关于我们 [image] => [list_order] => 1 [family] => ,1, [level] => 1 [children_count] => 2 [create_time] => 2020-09-27 17:11:35 [update_time] => 2020-09-28 17:10:43 [delete_time] => 0 [is_nav] => 1 ) [2] => Array ( [id] => 2 //... [is_nav] => 1 ) // 还有 3-8 假设在这里 ... [9] => Array ( [id] => 9 [parent_id] => 3 [title] => 夏季产品 [image] => [list_order] => 9 [family] => ,3,9, [level] => 2 [children_count] => 0 [create_time] => 2020-09-27 17:13:17 [update_time] => 2020-09-27 17:13:17 [delete_time] => 0 [is_nav] => 1 ) ) ) ~~~ 你发发型多了2大键 `nav`: 满足你自定义条件的数据螺旋结构;一个N维数组,里面只有主键id值,根据层级关系从一级分类开始一直包含下去 `nav_children`:一个二维数组,里面只有主键id值;给定任意一个id,包含满足你自定义条件下的子级分类id 每个自定义的键最终都会生成2大缓存分别是N维满足你自定义条件和排序的螺旋数据结构 和_children 一个2维数组 用于存储满足你自定义条件下 给定一个父id 获取满足条件的下级数据。 再看下数据表结构: ![](https://img.kancloud.cn/3d/16/3d16b95229ae871a9d0983d43614ef21_796x292.png) 只有id:3和5栏目隐藏了,为什么nav中没有 8 9?因为他们的父级被隐藏了,子级自然不会有了 为什么nav_children中如果给定3,没有办法获取到8 9这2个可见的导航?还是因为3父级被隐藏了,如果确实需要前台获取3的下级 ,你可以通过children中去拿3的下级。 # 数据获取 为了方便各种情况下的数据获取,系统默认给大家准备了一个函数叫`tree` ~~~ tree('无限极模型名'); // 获取到整个缓存数据 接下来以Menu模型为例 tree('Menu');// 获取到所有缓存数据 tree('Menu', 'threaded');// 获取到整个数据表的 螺旋结构数据 tree('Menu', 'children');// 一个二维数字 获取到所有数据的父子关系 tree('Menu', 'list');// 获取到整个数据的详细字段数据 tree('Menu', 'nav');// 你自定义的键也可以 tree('Menu', 'nav_chidren'); tree('Menu', 'children', 2);// 获取到指定id的下级 tree('Menu', 'nav_children', 2);// 获取到你自定义条件下指定id的下级 tree('Menu', 1);// 获取到指定ID的整行数据 一位数组 字段 => 值 tree('Menu', 1, 'title');// 获取到指定ID的指定字段值 ~~~ 利用上面的数据获取方式,大致写了一个前台遍历一、二级主导航栏目的示例模板代码: ~~~ <ul> {foreach :tree('Menu', 'nav') as $level1_id => $level1} <div>{:tree('Menu', $level1_id, 'title')}</div> {if $level1} <ul> {foreach $level1 as $level2_id => $level2} <li>{:tree('Menu', $level2_id, 'title')}</li> {/foreach} </ul> {/if} {/foreach} </ul> ~~~ 如果你感觉还不爽,复制下面代码到你的`app/commom.php`函数库中: ~~~ if (!function_exists('menu')) { function menu($key = null, $index = null, $default = []) { return tree('Menu', $key, $index, $default); } } ~~~ 每个无限级模型,开发者都可以自行定义一个该函数,把函数名换下,把tree函数中的`模型名`换下,然后: ~~~ menu(); // 获取到整个缓存数据 接下来以Menu模型为例 menu();// 获取到所有缓存数据 menu('threaded');;// 获取到整个数据表的 螺旋结构数据 menu('children');// 一个二维数字 获取到所有数据的父子关系 menu('list');// 获取到整个数据的详细字段数据 menu('nav');// 你自定义的键也可以 menu('nav_chidren'); menu('children', 2);// 获取到指定id的下级 menu('nav_children', 2);// 获取到你自定义条件下指定id的下级 menu(1);// 获取到指定ID的整行数据 一维数组 字段 => 值 menu(1, 'title');// 获取到指定ID的指定字段值 ~~~ 前台模板示例代码: ~~~ <ul> {foreach :menu('nav') as $level1_id => $level1} <div>{:menu($level1_id, 'title')}</div> {if $level1} <ul> {foreach $level1 as $level2_id => $level2} <li>{:menu($level2_id, 'title')}</li> {/foreach} </ul> {/if} {/foreach} </ul> ~~~ 还有其他的一些可能会用上的获取数据的方法: ~~~ $tree = new \woo\common\helper\Tree(model('Menu'));// v2.0.3以后可以改为tree('Menu', true) $data = $tree->getDeepLevel(4);//获取指定id是第多少代 $data = $tree->getDeepParents(4);//获取指定ID的所有 父辈ID 返回一个数组 $data = $tree->getDeepChildren(0);//获取指定ID下的 所有后代ID $data = $tree->getOptions();// 获取树形选项 自己打印看下 $data = $tree->getXmOptions();// 获取xmselect结构树形选项 自己打印看下 $data = $tree->getLayuiTree();// 获取layui结构的树形选项 ~~~