
前言:从“假权限”到“真安全”
最近在开发后台管理系统时,我遇到了一个经典需求:不同角色的用户,登录后看到的侧边栏菜单必须不同,且不能通过 URL 偷跑访问。
起初我的想法很简单:把所有路由都写在 router/index.ts 里,然后在侧边栏组件里用 v-if 判断一下用户角色,隐藏掉不该看的菜单不就行了吗?
但朋友问了我一个问题:“如果用户知道了管理员页面的 URL(比如 /admin/settings),直接在浏览器地址栏输入并回车,会发生什么?”
答案是:他能直接进去! 😱
这时候我才意识到,真正的权限控制不能只靠“藏菜单”,必须做到 动态控制路由 ——如果我没有权限,这个路由在 Vue Router 里压根就不应该存在。
本文将分享我是如何从零实现这套 Vue3 + TypeScript + Pinia 的动态路由方案的,以及过程中踩过的“刷新白屏”和“无限循环”大坑。
一、 核心思路拆解
我们采用 “后端控制权限,前端维护映射表” 的方案。
- 静态路由:Login、404 等所有人都能访问的页面,初始化时直接挂载。
- 动态路由:将原本写死的业务路由(Approval, Admin 等)抽离出来,不要放入
createRouter。 - 权限过滤:用户登录 -> 获取 Role -> 拿着 Role 去过滤动态路由表 -> 得到
accessRoutes。 - 动态挂载:使用
router.addRoute()将过滤后的路由添加到 Router 实例中。
二、 代码重构与实现
项目结构如下
1 | src/ |
1. 扩展 TypeScript 类型
在使用 meta 字段存储 requireAdmin 等信息时,TS 可能会报错。我们需要扩展 Vue Router 的类型定义。
注意:
Vue Router将RouteMeta设计为空接口,本质上是留给开发者自定义的“插槽”。但在TypeScript的严格模式下,访问未定义的接口属性是非法的。meta字段默认是空接口(或者基础定义),所以需要加入我们所需的字段,防止报错,并且也能让编辑器自动提示出所有可用字段
文件:src/types/vue-router.d.ts
1 | import "vue-router"; |
2. 路由表拆分(关键)
痛点修正:一定要把业务路由从初始配置中拿走,否则动态添加就没有意义了。
文件:src/router/routes.ts
1 | import type { RouteRecordRaw } from "vue-router"; |
3. 路由初始化
文件:src/router/index.ts
1 | import { createRouter, createWebHistory } from "vue-router"; |
三、 核心难点:全局守卫与“无限循环”陷阱
这是整个功能最容易出 Bug 的地方。我们需要在 beforeEach 中判断:如果用户已登录,但此时路由还没生成,就需要立刻去生成路由。
文件:src/router/guards.ts
1 | import type { Router } from "vue-router"; |
四、 踩坑笔记
坑一:刷新页面后 404
例子:我明明在动态路由里配了 /visitors,正常点击能进去,但只要在那个页面按 F5 刷新,直接跳到了 404 页面。
原因:
Vue Router 初始化的顺序是:
- 浏览器刷新,JS 重新执行。
createRouter创建实例,此时只有constantRoutes(静态路由)。- 如果我把
/:pathMatch(.*)*放在了静态路由里,URL/visitors进来时,发现静态路由里没这个路径,直接匹配到了通配符,跳转 404。 - 此时
beforeEach里的动态添加逻辑甚至还没执行完。
解决方案:
永远不要在 constantRoutes 里写通配符! 必须在动态路由加载完毕后(即 forEach 循环 addRoute 之后),再手动 router.addRoute 把 404 规则加进去。
坑二:死循环
RangeError: Maximum call stack size exceeded
例子:浏览器卡死,控制台报错栈溢出。
原因:
我在 beforeEach 里写了 next({ ...to, replace: true })。
这个操作会再次触发 beforeEach。如果此时 permissionStore.isRoutesGenerated 的状态没有正确更新为 true,或者判断条件写错了,就会无限重复“拦截 -> 添加 -> 重定向 -> 拦截”的过程。
解决方案:
确保 Pinia 中的 isRoutesGenerated 状态变更是在 next({ ...to }) 之前完成的。
五、 总结
通过这次重构,大家应该能感受到 Vue Router “addRoute” 的威力。真正的权限管理不仅仅是前端 UI 的隐藏,更是对路由逻辑的严密控制。
当然还有一点,虽然前端做了拦截,但别忘了:前端是防君子不防小人的。最核心的数据接口权限,依然需要后端同学在 API 层做好验证。
如果你也在做类似的后台管理系统,希望这篇文章能帮你少踩几个坑!