菜单权限必须由后端控制并返回,前端仅渲染;需通过接口获取动态菜单数据,用框架条件渲染而非隐藏DOM;hasPermission应支持细粒度匹配;路由守卫与API校验缺一不可。

HTML 本身不能做菜单权限控制——它只是静态结构,没有判断能力。真正的菜单权限必须由后端决定数据范围,前端只负责按后端返回的权限列表渲染对应菜单项。
菜单数据必须从后端接口获取
前端不能靠写死 ['用户管理', '订单管理'] 来生成菜单,否则任何人改一下 HTML 或 JS 就能绕过。正确做法是:登录后请求类似 /api/user/menu 的接口,后端根据用户角色、部门、数据范围等策略,返回当前用户「真正被允许看到的菜单项数组」。
- 返回结构建议是扁平或树形对象,包含
path、name、icon、permission(如'menu:user')等字段 - 后端必须对每个菜单项做校验:比如“财务报表”菜单只返回给财务角色,且该角色还需有
report:finance权限才生效 - 不要在前端用
if (role === 'admin')硬编码显示逻辑——角色可能动态变更,且 JWT 中的 role 字段也可能被篡改
前端渲染时避免直接操作 display: none
拿到菜单数据后,常见错误是遍历 DOM 元素再手动 el.style.display = 'none' 隐藏无权菜单。这会导致可访问性问题(屏幕阅读器仍能读到)、SEO 干扰、测试路径遗漏,而且无法响应多标签页间权限变更。
- 推荐用框架级条件渲染:Vue 用
v-if="hasPermission(item.permission)",React 用{hasPermission(item.permission) && } - 纯 JS 场景下,应基于菜单数据数组生成完整 DOM 结构,而不是先渲染全部再隐藏——即「只创建有权菜单的元素」
- 禁用菜单项时,优先用
aria-disabled="true"+tabindex="-1",而非单纯视觉隐藏
hasPermission 函数必须支持细粒度比对
菜单权限不是只有“看不看得到”,还涉及「能否跳转」「是否高亮当前路由」「是否显示子菜单」。所以 hasPermission 不能只判断字符串相等,得支持前缀匹配、通配符、组合权限等场景。
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
- 例如:权限码
'menu:order:detail'应能被hasPermission('menu:order:*')或hasPermission('menu:order')匹配到 - 函数内部建议用
permissions.includes(code)做精确匹配,用permissions.some(p => p.startsWith(prefix))做前缀匹配 - 别把权限数组存在
localStorage后就不管了——用户切换角色后,必须重新拉取并更新本地缓存,否则菜单不会实时变化
路由跳转前必须做守卫校验
即使菜单没显示某个入口,用户仍可能手动输入 URL 访问。所以单页应用中,所有受控路由都得加守卫(如 Vue Router 的 beforeEach、React Router 的 useNavigate + useEffect 检查)。
- 守卫里调用同一套
hasPermission,传入路由配置中的meta.permission字段 - 未通过时,跳转到
/403页面,而不是首页或登录页——避免掩盖权限问题 - 注意:守卫检查的是「目标路由是否在用户权限范围内」,不是「菜单是否渲染过」;两者独立校验,不可互相替代
最容易被忽略的是菜单与路由权限的分离管理:菜单控制「可见性」,路由守卫控制「可访问性」,而 API 接口控制「数据真实性」——三者缺一不可,且都依赖后端返回的同一份权限声明。前端任何一处省略校验,都会让整个权限体系出现缺口。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










