菜单管理
一句话:菜单 = 侧边栏里能看到的那个入口,同时也是「路由地址 + 权限标识」的登记表。本页维护整棵菜单树:目录、菜单、按钮三种节点各管一件事。
先建立一个心智模型
- 目录:只负责分组(如「系统管理」),自己不对应页面;
- 菜单:对应一个真实页面(如「用户管理」),要填组件路径;
- 按钮:不对应页面,只产出一个权限标识(如
system:user:create),供角色资源授权按按钮粒度授权、前端按它隐藏按钮。
所以「页面上看不到某个按钮」十有八九是按钮级菜单没授给你的角色,去权限诊断一查便知。
功能在哪
- 菜单路径:系统管理 → 菜单管理
- 地址栏路由:
/system/menu - 主要权限:
system:menu:query(看)、system:menu:create(新增)、system:menu:update(改/状态开关)、system:menu:delete(删)

界面认识
| 区域 | 作用 | 常用操作 |
|---|---|---|
| 查询区 | 按 菜单名称 / 状态 过滤(过滤后是命中项的子树,不是整棵树) | 「搜索」「重置」 |
| 工具条 | 整页级动作 | 「新增」建顶级节点;勾选后「批量删除(N)」;「展开/折叠」一键切换整棵树;「刷新菜单缓存」 |
| 列表 | 虚拟滚动的树表:勾选 / 菜单名称 / 图标 / 排序 / 权限标识 / 组件路径 / 组件名称 / 状态 / 操作 | 点行首三角展开子级;行内「修改」「新增」(在它下面建子节点)「删除」 |
| 状态列 | 开关直接启停菜单 | 需要 system:menu:update;没权限时开关可见但点不动 |
列表是虚拟滚动,没有分页
菜单全量一次加载,靠虚拟滚动只渲染可视区那几十行(节点上千也不卡)。因此:没有分页条;「展开/折叠」和滚动位置只影响你眼前看到的行;勾选框勾的是数据行,跟展开与否无关。
三种节点在列表里的样子
| 节点 | 识别特征 | 例子(见上图) |
|---|---|---|
| 目录 | 有子节点、组件路径为空 | 系统管理 / 租户管理 |
| 菜单 | 组件路径有值(如 system/tenant/index) | 租户列表 |
| 按钮 | 只有权限标识有值,排在父菜单下面 | 租户查询 system:tenant:query |
常用操作
新建菜单(最常用的一条链路)
- 找到父节点:想挂在哪个目录下面就先展开它,点该行的「新增」(行内新增 = 自动把「上级菜单」填好);建顶级节点用工具条的「新增」;
- 填「菜单名称」;
- 选「菜单类型」——选菜单才会出现「组件地址 / 组件名字 / 缓存状态」三个字段(下图即这一档);
- 填「路由地址」:顶级菜单必须以
/开头(如/system),子菜单不能以/开头(如user); - 填「组件地址」(前端
src/views/下的路径,如system/user/index)与「组件名字」(如SystemUser,要缓存必填); - 填「权限标识」,惯例
模块:对象:动作,如system:user:query; - 「显示排序」决定同级先后;「显示状态」选隐藏则侧边栏不出现但路由仍可直达;
- 点「确定」。

结果:列表多出一行。但侧边栏不会立刻变——见下一条。
改完之后:刷新菜单缓存
前端把「我能看到哪些菜单」缓存在本地(登录时拉一次)。改完菜单想立刻看到效果:
- 点工具条「刷新菜单缓存」;
- 确认框「即将更新缓存刷新浏览器!」点确定;
- 浏览器整页重载,重新拉菜单。
不刷会怎样
改了自己却看不到,误以为没保存成功;更隐蔽的是删掉的菜单还能点进去(路由表还是旧的)。所以「改了菜单但界面没变」的第一反应就是刷这个缓存,而不是重启后端。
新增按钮级权限
- 在目标菜单行点「新增」,「菜单类型」选按钮;
- 只填「菜单名称」(写人话,如「用户导出」)与「权限标识」(如
system:user:export); - 保存后去角色资源授权把它勾给角色,前端带
v-hasPermi的按钮才会出现。
修改 / 删除
- 行内「修改」打开同一张表单;改「路由地址 / 组件地址」后记得刷菜单缓存。
- 行内「删除」或勾选后「批量删除」。批量删除的确认框会明说「包含其勾选的子菜单」:勾中一个目录,它下面整棵子树一起删。
删除前想清楚两件事
- 菜单被角色引用时删除,角色的授权里会留下悬空项;
- 按钮级菜单一删,依赖它权限标识的授权与前端按钮显隐同时失效。 演示环境请只动自己建的测试菜单。
字段说明
| 字段 | 何时出现 | 必填 | 取值 / 校验 | 说明 |
|---|---|---|---|---|
| 上级菜单 | 总是 | 否 | 树选择,根为「主类目」 | 不选 = 顶级 |
| 菜单名称 | 总是 | 是 | 「菜单名称不能为空」 | 侧边栏显示名 |
| 菜单类型 | 总是 | 是 | 目录 / 菜单 / 按钮 | 决定其余字段的显隐 |
| 菜单图标 | 类型 ≠ 按钮 | 否 | 图标选择器 | 侧边栏图标 |
| 路由地址 | 类型 ≠ 按钮 | 是 | 「路由地址不能为空」;顶级须以 / 开头否则报「路径必须以 / 开头」,子级反之报「路径不能以 / 开头」;http(s)://、mailto:、tel: 视为外链不校验 | 外链会新开标签页 |
| 组件地址 | 类型 = 菜单 | 是 | 如 system/user/index | 对应前端 src/views/ 下文件 |
| 组件名字 | 类型 = 菜单 | 否 | 如 SystemUser | 要开缓存则必填 |
| 权限标识 | 类型 ≠ 目录 | 否 | 如 system:user:list | 后端接口鉴权与前端按钮显隐的依据 |
| 显示排序 | 总是 | 是 | 「菜单顺序不能为空」 | 同级排序 |
| 菜单状态 | 总是 | 是 | 开启 / 关闭 | 关闭后该菜单对所有人不可见 |
| 显示状态 | 类型 ≠ 按钮 | 是 | 显示 / 隐藏 | 隐藏 = 侧边栏不出现、路由仍可直达 |
| 总是显示 | 类型 ≠ 按钮 | 是 | 总是 / 不是 | 「不是」= 只有一个子菜单时不显示自己、直接显示子菜单 |
| 缓存状态 | 类型 = 菜单 | 是 | 缓存 / 不缓存 | 缓存走 keep-alive,需填组件名字 |
视角对照
| 视角 | 看到什么 |
|---|---|
超管 admin / 租户管理员 docadmin | 整棵菜单树(含按钮级节点) |
普通员工 salesman01 | 无此菜单:菜单管理本身也是菜单,没授权就看不见 |
菜单 ≠ 套餐
菜单树定义「系统里有哪些功能」;租户套餐决定「这个租户能用其中哪些」,见租户套餐。套餐没勾的菜单,租户管理员在菜单管理里也看不到。
进阶
常见问题
Q:新增的菜单侧边栏没有? A:三步排查——① 没点「刷新菜单缓存」;② 菜单状态是关闭或显示状态是隐藏;③ 当前角色没被授权这个菜单(角色资源授权),或租户套餐没勾(租户套餐)。
Q:报「路径必须以 / 开头」? A:你在建顶级菜单(上级 = 主类目)。顶级路由地址要写 /system 这种带斜杠开头的;挂在别的目录下面时则不能带开头斜杠。
Q:组件填了但页面空白 / 404? A:「组件地址」必须与前端 src/views/ 下的真实文件路径一致(不含 .vue 后缀)。路径写错时路由能进但组件加载失败。
Q:按钮级节点要不要填路由地址? A:不要。按钮类型只填名称与权限标识,其余字段界面上也不出现。
Q:「展开/折叠」点了没反应? A:它切换的是整棵树的展开态;如果当前是搜索过滤结果,树本来就只展示命中子树,观感变化小。点「重置」回到全量再看。
