vue-element-admin 加菜单别只改路由:meta.roles 与 sys_menu 必须双写
某天给某后台管理系统新增一个功能模块,在 src/router/xtmodules/ 里加了路由、配好了 meta.roles,本地用 admin 账号测试一切正常,自信地提了 PR。上线后运营反馈:"为什么我这个角色在权限管理里看不到这个菜单,没法分配给其他人?"
这是一个在 vue-element-admin + Java 后端架构下会反复踩的隐性 bug,admin 开发者自测永远发现不了它。
现象
- admin 账号进入新页面:正常
- 非 admin 账号:菜单不可见,但这很正常,毕竟还没分配权限
- 打开角色管理后台(
/settings/roleManagement/addRole/:roleId),展开对应模块的权限树:找不到刚加的菜单 - 无法勾选,无法分配,非 admin 角色永远无法获得这个权限
根因
vue-element-admin 的 meta.roles 是纯前端路由守卫,工作机制是:进入路由时检查 store.user.roles 数组是否包含 route.meta.roles 中的某个字符串,不包含则重定向 403。它和权限树毫无关系。
而角色管理后台的"菜单权限树"是从后端 API(通常是 permission/menu/getMenuTree)拉数据,这个 API 的数据来源是数据库里的 sys_menu 表。前端路由文件是代码资产,后端不会扫描它,两者完全解耦。
admin 用户能直接进新页面,是因为 permission.js 里有一个旁路:
// src/store/modules/permission.js
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes // admin 跳过 meta.roles 过滤,直接拿全部路由
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}这个旁路让 admin 成了"永远测不出权限漏洞"的账号。
新增菜单时,只改 src/router/xtmodules/*.js + meta.roles=['xxx'] 不够。后端的 sys_menu 表没有这条记录,角色管理后台就永远拉不到它——这个 bug 用 admin 测试完全无感,只有非 admin 用户上线后才暴露。
正解
新增菜单时,必须同时完成两件事:
1. 前端路由配置(你已经会)
// src/router/xtmodules/manage_xxx.js
{
path: '/xxxModule/newFeature',
name: 'xxxModule_newFeature',
component: emptyRouterView,
meta: {
title: '新功能',
roles: ['newFeatureCode'] // ← 这个字符串就是后续 sys_menu.code
},
redirect: '/xxxModule/newFeature/index',
children: [
makeSubPage('xxxModule/newFeature/index', '新功能', false),
makeSubPage('xxxModule/newFeature/create', '新建', true),
makeSubPage('xxxModule/newFeature/detail', '详情', true, null, '/:id'),
]
}2. 同步插入 sys_menu 一行
INSERT INTO sys_menu (id, parent_id, parent_ids, name, sort, is_show, code, create_at, del_flag)
VALUES (
REPLACE(UUID(), '-', ''), -- 32 位 UUID 无横杠
'<父菜单 id>', -- SELECT id FROM sys_menu WHERE code='xxxModule'
'0,1,<父菜单 id>,', -- 末尾逗号不能漏
'新功能', -- 与 meta.title 一致
5, -- 同级最大 sort + 1
1,
'newFeatureCode', -- 必须等于 meta.roles 数组里的字符串
NOW(),
0
);四个字段对齐是硬约束:
| sys_menu 字段 | 对应前端 | 要求 |
|---|---|---|
| code | meta.roles[0] | 字符串完全一致 |
| name | meta.title | 建议保持一致 |
| parent_id | 父路由所在模块 | 先 SELECT 出来再填 |
| parent_ids | — | 格式 '0,1,<parent_id>,',末尾逗号必须有 |
PR 描述 / commit message 里必须同时包含这两个变更。如果你的 diff 里只有 .js 路由文件,没有 SQL 变更,这个 PR 是不完整的。
排查
如果已经上线发现问题,快速验证:
-- 确认记录是否存在
SELECT id, parent_id, parent_ids, name, code, is_show, del_flag
FROM sys_menu WHERE code = 'newFeatureCode';
-- 期望:1 行,del_flag=0, is_show=1如果记录存在但前端仍看不到:Java 服务可能有内存缓存,让用户退出重登或重启服务,不要怀疑 SQL 写错了。
另一个常见情况:之前曾手动删过同 code 的记录(软删 del_flag=1),此时不要再 INSERT,直接:
UPDATE sys_menu SET del_flag = 0 WHERE code = 'newFeatureCode';一句话外卖
vue-element-admin 的
meta.roles只是路由守卫,不是菜单数据本身——新增菜单必须双写:前端路由文件 + 后端sys_menu表,admin 旁路会让你在测试阶段永远发现不了这个漏洞。