以下將深入講解 Vue Router 的全局前置守衛 beforeEach
在權限系統中的實現原理和實戰應用,結合企業級項目代碼進行拆解(基于 Vue 3 + TypeScript + Pinia)。
一、前置守衛核心機制
1.1 執行時機與特性
全局前置守衛在路由跳轉前觸發,具有以下特性:
- 攔截所有導航:包括手動 URL 輸入、
router-link
跳轉、編程式導航(router.push
) - 異步解析:支持 Promise 鏈式調用,適用于需要等待接口響應的場景
- 攔截優先級:觸發順序為
組件內 beforeRouteLeave
→全局 beforeEach
→路由獨享 beforeEnter
1.2 參數解析
router.beforeEach((to, from, next) => {// to: 目標路由對象(含 path/params/meta 等)// from: 來源路由對象// next: 導航控制函數
})
參數特性:
to.matched
:獲取嵌套路由的所有匹配記錄(用于檢查深層路由權限)meta
字段擴展性:可在路由配置中自定義權限標識
二、權限校驗實現步驟
2.1 路由元信息配置
在路由配置中聲明權限標識:
// router.ts
const routes = [{path: '/dashboard',component: Dashboard,meta: {requiresAuth: true, // 需要登錄permissions: ['admin'], // 所需權限dynamicMenu: true // 動態菜單標識}},{path: '/login',component: Login,meta: {guestOnly: true // 僅允許未登錄用戶訪問}}
]
2.2 權限校驗主邏輯
// permission.ts
export const permissionGuard: NavigationGuard = async (to, from, next) => {const authStore = useAuthStore() // Pinia 狀態管理// 1. 檢查登錄狀態if (to.meta.requiresAuth && !authStore.isAuthenticated) {return next({ path: '/login',query: { redirect: to.fullPath } // 記錄原始目標路徑})}// 2. 已登錄用戶訪問登錄頁重定向if (to.meta.guestOnly && authStore.isAuthenticated) {return next(from.path || '/')}// 3. 動態權限加載(首次加載或Token刷新后)if (!authStore.permissionsLoaded) {try {await authStore.fetchPermissions()} catch (error) {next('/500') // 接口異常處理return}}// 4. 細粒度權限校驗if (to.meta.permissions) {const hasPermission = authStore.userPermissions.some(perm => to.meta.permissions!.includes(perm))if (!hasPermission) {next('/403') // 無權限頁面return}}// 5. 動態路由注入(異步加載權限路由)if (to.meta.dynamicMenu && !isRouteExists(to)) {const dynamicRoutes = await fetchDynamicRoutes()dynamicRoutes.forEach(route => router.addRoute(route))next(to.fullPath) // 重新觸發導航return}next() // 放行
}
三、登錄狀態檢查進階方案
3.1 狀態持久化方案
// stores/auth.ts (Pinia)
export const useAuthStore = defineStore('auth', {state: () => ({token: localStorage.getItem('token') || null,user: null as User | null,permissions: [] as string[]}),actions: {async login(credentials: LoginForm) {const { data } = await api.login(credentials)this.token = data.tokenthis.user = data.userlocalStorage.setItem('token', data.token)router.push(data.redirect || '/')},logout() {this.token = nullthis.user = nulllocalStorage.removeItem('token')router.replace('/login')}},getters: {isAuthenticated: state => !!state.token}
})
3.2 Token 自動續期
// axios 攔截器
instance.interceptors.response.use(response => response,async error => {const originalRequest = error.configif (error.response.status === 401 && !originalRequest._retry) {originalRequest._retry = truetry {const newToken = await refreshToken()authStore.token = newTokenoriginalRequest.headers.Authorization = `Bearer ${newToken}`return instance(originalRequest)} catch (refreshError) {authStore.logout()return Promise.reject(refreshError)}}return Promise.reject(error)}
)
四、企業級項目整合方案
4.1 路由守衛注冊
// main.ts
import router from './router'
import { permissionGuard } from './permission'router.beforeEach(async (to, from, next) => {// 頁面加載進度條NProgress.start()// 執行權限校驗鏈await permissionGuard(to, from, next)NProgress.done()
})router.afterEach(() => {// 頁面訪問統計trackPageView(to.fullPath)
})
4.2 動態路由加載
// 獲取動態路由
const fetchDynamicRoutes = async () => {const { data } = await api.get('/routes')return data.map(route => ({path: route.path,component: () => import(`@/views/${route.component}.vue`),meta: route.meta}))
}// 處理動態路由的404情況
router.onError((error) => {if (/Loading chunk .+ failed/.test(error.message)) {window.location.reload()}
})
五、最佳實踐與調試技巧
5.1 調試建議
// 開發環境打印導航日志
if (import.meta.env.DEV) {router.beforeEach((to, from) => {console.log('[Route]', from.path, '→', to.path)console.log('[Meta]', to.meta)})
}
5.2 常見問題處理
-
無限重定向問題
檢查重定向條件是否互斥:if (to.path === '/login' && authStore.isAuthenticated) {next(from.path || '/') // 避免循環 }
-
路由切換白屏
使用路由懶加載 + Suspense:<template><Suspense><RouterView /></Suspense> </template>
-
權限變更同步
監聽權限變化事件:watch(() => authStore.permissions, () => {router.replace(currentRoute.value.fullPath) })
六、擴展功能實現
6.1 多租戶權限系統
// 路由配置增加租戶標識
meta: {tenant: 'A',permissions: ['report:view']
}// 守衛中校驗租戶
if (to.meta.tenant !== authStore.currentTenant) {next('/tenant-switch')
}
6.2 頁面水印防護
router.afterEach((to) => {if (to.meta.sensitive) {applyWatermark(authStore.username)}
})
總結
全局前置守衛作為權限系統的核心入口,需要結合以下關鍵點實現:
- 狀態管理:通過 Pinia/Vuex 實現跨組件狀態共享
- 路由分層:靜態路由 + 動態加載實現靈活權限分配
- 錯誤邊界:完善的異常處理流程(401/403/404)
- 性能優化:路由懶加載、接口緩存、防抖處理