场景引入
大部分后台管理系统都有这类需求:
- 未登录用户跳转任何页面都强制回登录页;
- 不同角色看到的菜单不同、能进的页面不同;
- 路由按需加载,首屏不打包全部页面。
这些需求都靠 Vue Router 的守卫和动态路由解决。
一、路由元信息:给路由打标签
meta 是挂在路由上的自定义数据,权限、标题、缓存标记都放这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const routes = [ { path: '/admin', component: () => import('@/views/Admin.vue'), meta: { requiresAuth: true, roles: ['admin'], title: '管理后台' } }, { path: '/login', component: () => import('@/views/Login.vue'), meta: { title: '登录' } } ]
|
二、全局守卫:登录拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import router from './router' import { useUserStore } from '@/stores/user'
router.beforeEach((to, from, next) => { document.title = to.meta.title ? `${to.meta.title} - 我的后台` : '我的后台'
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.token) { next({ path: '/login', query: { redirect: to.fullPath } }) return }
if (to.meta.roles && !to.meta.roles.includes(userStore.role)) { next('/403') return }
next() })
|
守卫的三参数:
to:目标路由(即将进入)
from:来源路由
next():放行;next(false) 取消;next('/xx') 重定向
三、登录后跳回原页面
登录页拿到 redirect 参数,登录成功后回跳:
1 2 3 4 5 6 7 8
| const router = useRouter() const route = useRoute()
async function handleLogin() { await login() router.push(route.query.redirect || '/') }
|
四、动态路由:按角色添加菜单
静态路由只放公共页面,业务页面登录后根据权限动态添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const menus = [ { path: '/user', name: 'User', component: 'user/UserList' }, { path: '/order', name: 'Order', component: 'order/OrderList' } ]
const modules = import.meta.glob('@/views/**/*.vue')
function buildRoutes(menus) { return menus.map((menu) => ({ path: menu.path, name: menu.name, component: modules[`/src/views/${menu.component}.vue`] })) }
router.addRoute({ path: '/', component: () => import('@/layout/MainLayout.vue'), children: buildRoutes(menus) })
|
重点坑:addRoute 添加的路由刷新页面就没了(内存态),所以刷新后要在入口重新拉菜单、重新 addRoute,否则 404。常见方案是在 beforeEach 里加”是否已初始化”判断:
1 2 3 4 5 6 7 8 9
| router.beforeEach(async (to, from, next) => { const userStore = useUserStore() if (userStore.token && !userStore.menusLoaded) { await userStore.fetchAndRegisterRoutes() next({ ...to, replace: true }) return } next() })
|
五、路由懒加载与打包优化
Vue Router 4 默认推荐路由级懒加载,页面按需打包:
1 2 3 4 5
| component: () => import('@/views/Home.vue')
component: () => import( '@/views/user/UserList.vue')
|
Vite 里用 build.rollupOptions.output.manualChunks 控制分包,路由级懒加载天然就会产生多个 chunk。
六、组件内守卫与路由滚动
组件内守卫适合”离开前确认”:
1 2 3 4 5 6 7 8 9 10 11 12
| onBeforeRouteLeave((to, from, next) => { if (hasUnsavedChanges.value) { if (confirm('有未保存的修改,确定离开吗?')) { next() } else { next(false) } } else { next() } })
|
路由切换保持滚动位置:
1 2 3 4 5 6 7
| const router = createRouter({ history: createWebHistory(), routes, scrollBehavior(to, from, savedPosition) { return savedPosition || { top: 0 } } })
|
常见坑
- 守卫里访问 store 时机:
beforeEach 里用 Pinia 要在 app.use(pinia) 之后,否则拿不到实例;
addRoute 重复添加报错:同名路由重复 add 会警告,初始化前先 router.hasRoute(name) 判断;
- 动态路由刷新 404:几乎所有人都会踩,务必处理”菜单初始化”逻辑;
- 守卫死循环:
next('/login') 但 login 页面也满足拦截条件时,会无限重定向,拦截逻辑要排除登录页。
总结
Vue Router 4 的进阶能力就三块:meta 元信息打标签 → 全局守卫做登录/权限拦截 → 动态 addRoute 按角色挂路由。配合路由懒加载,后台管理系统的权限骨架就完整了。