答案是“先保底、再补权”:启动时仅注册/login、/404等静态公共路由;登录后异步获取菜单数据,转换为RouteRecordRaw格式并用router.addRoute动态注入,同时存入localStorage,配合全局守卫实现刷新恢复与越权拦截。

核心是“先保底、再补权”:启动时只挂载登录页和 404 等无需权限的路由;登录成功后,拉取菜单数据、转成合法路由配置、动态注入,并配合守卫防止刷新丢失或越权访问。
静态路由打底,确保未登录可访问
项目初始化时,不加载任何需权限的页面。只在 router/index.ts 中硬编码基础路由:
-
/login:登录页,hidden: true不出现在菜单中 -
/404或/not-found:兜底页面 -
/重定向到 dashboard(用 layout 包裹,但 dashboard 组件本身也属于静态路由)
这些路由使用动态导入,如 () => import('@/views/login/index.vue'),保证按需加载且不依赖用户状态。
登录后拉取并转换菜单数据
后端返回的菜单通常是扁平数组或树形结构,例如:
立即学习“前端免费学习笔记(深入)”;
使用 `@ainative/vue-sdk` 为 Vue 3 应用添加 AI 聊天功能。适用于以下场景:(1) 安装 `@ainative/vue-sdk`,(2) 在 Vue 组件中使用 `useChat` 组合式函数,(3) 提供……
[{"path":"/user","name":"UserList","component":"user/List","meta":{"title":"用户列表"}}]
前端必须做三件事:
- 把
component: "user/List"转为合法异步组件:() => import('@/views/user/List.vue') - 确保
path是完整路径(避免嵌套时重复拼接),name全局唯一 - 补全
meta字段,比如icon、hidden、roles,用于后续菜单渲染和守卫判断
转换后的对象才符合 RouteRecordRaw 格式,能被 router.addRoute() 正确识别。
注入路由 + 守卫兜底,防止白屏和越权
调用 router.addRoute(route)(Vue 3)或循环添加后,新路由立即注册,但需注意:
- 若用户刚跳转到
/user这类尚未注入的路径,需手动next({ path: to.path })或router.replace()触发重匹配 - 全局前置守卫
router.beforeEach中要判断:!router.hasRoute(to.name) && 已登录→ 先拉菜单、注入路由、再next() - 刷新页面时动态路由会丢失,需将菜单存入
localStorage;守卫中检测到无路由且有 token,就先读缓存、注入,再放行
退出时清理,避免权限残留
用户登出必须清除动态路由,否则下个用户可能看到上一个用户的菜单甚至访问其页面:
- 遍历之前注入的路由 name,逐个调用
router.removeRoute(name) - 清空
localStorage中缓存的菜单 - 重置侧边栏菜单状态(如 Pinia store 中的
menuList)
不复杂但容易忽略


















