返回博客

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>,',末尾逗号必须有 |

正解:新增菜单 = 改前端路由文件 + INSERT sys_menu,缺一不可

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 旁路会让你在测试阶段永远发现不了这个漏洞。