首页菜单必须在控制器中一次查全并PHP组装树结构,禁止在视图中查库或调用Auth::check(),因view层仅为纯渲染上下文,否则将引发报错、静默失败或N次SQL查询导致性能骤降。

首页直接调用导航菜单,必须走「一次查全 + PHP 组装树」流程,不能在模板里用 foreach 嵌套查库,也不能依赖前端递归渲染。
为什么不能在 view 里查数据库或调用 Auth::check()
TP6 的视图层(view)是纯渲染上下文,不支持数据库查询、权限校验等业务逻辑。若在 index.html 里写 {: Db::name('menu')->where('pid', 0)->select()},会报错或静默失败;若用 {: Auth::check('api.menu.list')},每次渲染一个菜单项都触发一次 RBAC 规则匹配,N 个菜单就是 N 次 SQL 查询——首页加载直接变慢 3 倍以上。
- 所有菜单数据必须在控制器中查出并组装成树结构,再以数组形式传入视图
- 权限过滤也必须在控制器完成,不是“每个菜单项判断一次”,而是先拿到用户全部可访问的
rule_id集合,再对整棵树做白名单裁剪 - 视图只负责
{: $menuList }循环输出,不参与逻辑
如何在 Index 控制器中正确组装菜单树
假设菜单表为 menu,字段含 id、pid、title、path、sort,且已按权限过滤过(见下一点),组装代码应类似:
$menus = Db::name('menu')
->where('status', 1)
->order('pid ASC, sort ASC')
->select();
$map = [];
$tree = [];
foreach ($menus as $item) {
$item['children'] = [];
$map[$item['id']] = &$item;
if ($item['pid'] == 0) {
$tree[] = &$item;
} else {
$map[$item['pid']]['children'][] = &$item;
}
}
$this->assign('menuList', $tree);
return view();
-
order('pid ASC, sort ASC')是关键:保证父节点一定出现在子节点之前,否则$map[$item['pid']]可能尚未定义 - 必须用
&$item引用赋值,否则$map[$item['pid']]['children'][]追加的是副本,树结构断开 - 不要用递归函数(如
getTree($pid = 0)),TP6 默认禁用深度递归,容易触发max_execution_time超时
首页菜单必须过滤权限,但不能每个节点都 Auth::check()
TP6 原生 Auth 类的 check() 方法底层会查 auth_rule 和 auth_group_access 表,单次调用约 2–3 毫秒。如果首页有 12 个菜单项,就是 24–36 毫秒纯等待——还没算缓存穿透。
- 正确做法:登录后,用
Auth::getRuleList($userId)一次性获取该用户所有生效的rule_id(建议缓存键为auth_rule_list_user_{$uid}) - 在首页控制器中,先查出全部启用菜单,再用
array_filter()按rule_id匹配,保留命中的节点及其所有祖先路径(避免出现“子菜单可见但父菜单不可见”的空悬状态) - 若你扩展了
menu表的name字段(如admin.menu.dashboard),就直接拿这个字段去比对权限规则名,不用查关联表
view 中渲染多级菜单的最小可用写法
别追求“自动递归模板”,TP6 的 ThinkTemplate 不支持原生递归标签。首页模板里用两层 {: foreach} 即可覆盖绝大多数后台菜单结构:
- 第三层及以上菜单(如三级菜单)极少出现在首页主导航,硬加递归逻辑反而让模板难维护、易出错
- 若真需要三级,把
$menu.children再拆一层传入,不要在模板里嵌套三层{: foreach} -
{$menu.path}必须确保是完整 URL(如/admin/dashboard),不是相对路径或前端路由别名,否则首页链接点不开
最常被忽略的一点:菜单数据缓存没分用户。很多人用 Cache::get('menu_tree') 全局缓存一棵树,结果 A 用户看不到的菜单,B 用户登录后也跟着看不见——因为缓存键没带 $userId。哪怕只是首页,只要涉及权限,缓存就必须按用户隔离。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











