企业级全栈 RBAC 实战 (11):菜单管理与无限层级树形表格

发布时间:2026-01-21 15:09

摘要:系统如何管理它自己的菜单?这是一个经典的“先有鸡还是先有蛋”的问题。本文将介绍如何通过数据库初始化解决冷启动问题,并深入讲解如何利用 Element Plus 的树形表格展示无限层级数据,以及如何实现“目录、菜单、按钮”三种类型的动态表单交互。

一、 引言:先有鸡还是先有蛋?

在前面的文章中,我们实现了动态路由,前端的菜单是根据数据库里的 sys_menus 表自动生成的。

现在问题来了:我们想要在页面上通过图形化界面来新增、修改菜单,但是数据库里如果没有“菜单管理”这个页面,我们就进不去这个功能。这就是典型的系统**自举(Bootstrapping)**问题。

本篇的核心任务:

数据初始化:手动 SQL 插入第一条数据,打破僵局。 树形表格:不用递归组件,利用 el-table 原生特性展示树数据。 动态表单:根据是“目录”还是“按钮”,动态控制表单项的显示与校验。

二、 数据库与初始化:打破僵局

在开发任何 CRUD 功能之前,先确保数据库结构支持我们的业务需求(如缓存控制、显隐控制)。

. 表结构确认

除了基础的 path 和 component,我们需要重点关注 菜单类型 type:

M (Directory) : 目录。有路由地址,无组件路径(通常是 Layout),不显示在内容区,只展开子菜单。 C (Menu) : 菜单。有路由,有组件,是真正的页面。 F (Button) : 按钮。无路由,无组件,只用于控制页面内的按钮权限(如 system:user:add)。 2. 初始化 SQL (种子数据)

我们需要手动插入“菜单管理”本身,并把它分配给超级管理员。

-- 1. 插入菜单管理页面 (作为系统管理的子菜单) -- 假设系统管理 ID=1 INSERT INTO `sys_menus` (`parent_id`, `menu_name`, `path`, `component`, `perms`, `icon`, `type`, `hidden`, `sort`) VALUES (1, '菜单管理', 'menu', 'system/menu/index', 'system:menu:list', 'list', 'C', 0, 3); -- 2. 给角色分配权限 (Role ID = 1) INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, (SELECT id FROM sys_menus WHERE path='menu' LIMIT 1));

执行完这条 SQL,刷新页面,侧边栏就会神奇地出现“菜单管理”了。

三、 后端实现:逻辑校验与树形组装

后端除了常规的 CRUD,主要有两个逻辑点:列表转树 和 删除保护

1. 列表接口 (GET /menu/list)

// routes/menu.js router.get('/list', authMiddleware, async (req, res, next) => { try { const { menuName, status } = req.query let sql = 'SELECT * FROM sys_menus WHERE 1=1' let params = [] // ... 拼接查询条件 ... const [rows] = await pool.query(sql, params) // 【关键】将扁平数组转换为树形结构 // 这里的 buildTree 与之前动态路由使用的是同一个辅助函数 const list = buildTree(JSON.parse(JSON.stringify(rows))) res.json({ code: 200, data: list }) } catch (err) { next(err) } })

四、 前端实现:树形表格与动态表单

1. 树形表格 (el-table)

Element Plus 的 Table 组件自带树形展示功能,只需配置 row-key 和 tree-props。

<el-table :data="menuList" row-key="id" border :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" > <el-table-column prop="menu_name" label="菜单名称" width="160" /> <!-- 动态图标展示 --> <el-table-column prop="icon" label="图标" align="center" width="60"> <template #default="{ row }"> <el-icon v-if="row.icon"> <component :is="row.icon" /> </el-icon> </template> </el-table-column> <el-table-column prop="perms" label="权限标识" /> <el-table-column prop="component" label="组件路径" /> <!-- 状态展示 --> <el-table-column prop="hidden" label="状态" width="80"> <template #default="{ row }"> <el-tag v-if="row.hidden === 0" type="success">显示</el-tag> <el-tag v-else type="info">隐藏</el-tag> </template> </el-table-column> </el-table> row-key="id" : 告诉表格每行的唯一标识,这是折叠展开的关键。 tree-props: 告诉表格子节点在哪个字段里(默认是 children)。 2. 动态表单交互 (核心亮点)

在“新增/修改”弹窗中,我们需要根据 type 的不同,动态控制表单项的显示/隐藏校验规则

目录 (M) : 需要路由地址,不需要组件路径。 菜单 (C) : 需要路由地址 + 组件路径。 按钮 (F) : 只需要权限字符,不需要路由和组件。

<el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px"> <!-- 上级菜单选择:使用 TreeSelect 组件 --> <el-form-item label="上级菜单"> <el-tree-select v-model="form.parentId" :data="menuOptions" :props="{ value: 'id', label: 'label', children: 'children' }" check-strictly /> </el-form-item> <!-- 类型切换 --> <el-form-item label="菜单类型" prop="menuType"> <el-radio-group v-model="form.menuType"> <el-radio value="M">目录</el-radio> <el-radio value="C">菜单</el-radio> <el-radio value="F">按钮</el-radio> </el-radio-group> </el-form-item> <!-- 动态逻辑 v-if --> <el-form-item label="路由地址" prop="path" v-if="form.menuType !== 'F'"> <el-input v-model="form.path" /> </el-form-item> <el-form-item label="组件路径" prop="component" v-if="form.menuType === 'C'"> <el-input v-model="form.component" placeholder="views下的路径" /> </el-form-item> <el-form-item label="权限字符" v-if="form.menuType !== 'M'"> <el-input v-model="form.perms" placeholder="system:user:add" /> </el-form-item> <!-- ... 其他通用项 ... --> </el-form>

image.png

3. 构建上级菜单下拉树

在选择“上级菜单”时,我们需要展示一个树形下拉框。这里有个小技巧:我们需要在数据顶层手动添加一个“主类目”节点,否则用户无法将菜单创建为一级菜单。

const getTreeselect = async () => { const res = await listMenu({}); // 复用列表接口 // 手动构造根节点 const menu = { id: 0, label: '主类目', children: [] }; // 简单的字段映射 (menu_name -> label) menu.children = res.data.map(node => mapTreeSelect(node)); menuOptions.value = [menu]; };

通过本篇,我们完成了 RBAC 系统管理的最后一块拼图——菜单管理
现在,你可以直接在页面上配置新的路由,设置图标,定义权限,无需修改一行代码,刷新页面即可生效。这标志着我们的系统具备了动态扩展能力

但是,我们在菜单里配置了 system:user:add 这种权限标识,它到底有什么用?前端如何利用它来控制按钮的显示和隐藏?

下一篇:《全栈 RBAC 实战 (12):操作日志与按钮级别权限》 ,我们将深入 Vue 3 自定义指令的实现原理,实现细粒度的按钮级权限控制,并顺带搞定操作日志记录。

具备完整功能后台管理系统的代码仓库:

感谢大家的star

前端:前端

后端:后端

网址:企业级全栈 RBAC 实战 (11):菜单管理与无限层级树形表格 https://mxgxt.com/news/view/1965445

相关内容

企业级全栈 RBAC 实战 (11):菜单管理与无限层级树形表格
2K star!三分钟搭建企业级后台系统,这款开源Java框架绝了!
企业级艺人管理:企业化管理打造艺人明星阵容.docx
企业形象升级版:塑造品牌形象的战略与实践
事业单位改革,管理岗职级并行终于来了,管理八级职员与事业副科有什么区别吗?
铁骨仁心成就行业标杆———中建钢构有限公司企业文化建设和管理四部曲
企业战略管理课程实践
企业战略管理课程实践
加强新时期企业文化建设 以“无形之手”助推企业改革发展-国务院国有资产监督管理委员会
企业战略管理试卷库.doc

随便看看