Vue Router 与 RBAC
路由不仅是“URL 对应组件”。在中后台里,它还连接登录态、菜单、权限、数据预取、代码分割和页面恢复。安全边界仍在后端;前端负责体验与可见性。
把权限写成路由元数据
ts
declare module 'vue-router' {
interface RouteMeta {
requiresAuth: boolean
permissions?: string[]
title: string
}
}
const routes = [
{
path: '/activities/publish',
component: () => import('@/views/activity/PublishView.vue'),
meta: {
requiresAuth: true,
permissions: ['activity:publish'],
title: '活动发布',
},
},
]类型化 meta 能让新增页面时缺失权限声明直接变成编译问题。页面、菜单和按钮使用同一套权限码,避免三套规则漂移。
导航守卫只做编排
ts
router.beforeEach(async (to) => {
const auth = useAuthStore()
if (!to.meta.requiresAuth) return true
if (!auth.initialized) await auth.restoreSession()
if (!auth.loggedIn) return { name: 'login', query: { redirect: to.fullPath } }
if (!auth.hasAny(to.meta.permissions)) return { name: 'forbidden' }
})推荐返回值风格,而不是依赖容易重复调用或漏调用的 next。守卫不应塞入大段业务逻辑:登录恢复放 Store,权限计算放领域函数,路由层只编排顺序。
动态路由的生命周期
后端菜单驱动路由时,需要稳定的 route name/id 去重,并保存 addRoute 返回的移除函数。登出或切换角色时先移除旧路由,再拉取新权限。否则会出现重复注册、旧权限残留和首屏死循环。
ts
const disposers = serverRoutes.map((route) => router.addRoute(toRouteRecord(route)))
function resetDynamicRoutes() {
disposers.splice(0).forEach((dispose) => dispose())
}URL 也是状态容器
列表筛选、分页和排序如果需要分享、刷新恢复或浏览器前进后退,应进入 route.query。弹窗开关、输入中的临时值仍放组件本地。不要把整个 route 深度 watch,监听 route.params.id 或具体 query。
数据请求与竞态
数据可在导航前请求(进入即完整,但等待更久),也可导航后请求(更快展示骨架)。快速切换详情页时使用 AbortController 或请求序号,防止旧请求回写新页面。router.push 后如需改变 UI 状态,要检查 navigation failure。
权限的四层表达
| 层级 | 前端职责 | 最终校验 |
|---|---|---|
| 菜单 | 是否展示入口 | 后端返回授权菜单/权限 |
| 路由 | 是否允许进入 | 接口仍需鉴权 |
| 页面 | 是否展示区域与数据 | 数据接口按用户过滤 |
| 操作 | 按钮可见/可用 | 写接口二次鉴权与审计 |