Vue.js_Vue Router 4.x 動態路由解決重新整理空白
阿新 • • 發佈:2022-12-02
問題描述:
基於對 Vue Router 3.x
沒有改變前,我們常規的實現一定,在 store
中根據獲取的使用者許可權,對路由進行過濾並返回,然後到路由守衛的地方,使用 addRoutes
動態新增路由。但是在 Vue Router 4.x
以後對這部分進行了修改。
修改點:
- 刪除API
addRoutes
- 改用API
addRoute
,新增APIremoveRoute
,下附官方該 API 的說明:
也就是說這兩個API是我們實現動態路由的關鍵,但是按照官方的說明及以往的開發經驗,最終我出錯了,動態路由頁面重新整理空白。分析下問題原因:
動態路由實現常規思路
路由
- 建立
./src/router
- 新建
index.ts
檔案用於書寫通用的路由routes
和createRouter
物件
// ./src/router/index.ts import { createWebHistory, createRouter } from 'vue-router' import GuardEach from './guard' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/*', redirect: '/'}, { path: '/404', name: '404', component: () => import('@/views/common/404.vue') }, { path: '/login', name: 'Login', component: () => import('@/views/common/login.vue') }, { path: '/', name: 'Home', redirect: '/home', component: () => import('@/views/layout/index.vue'), children: [ { path: '/home', name: 'Home-Index', component: () => import('@/views/home/index.vue') } ] } ] }) GuardEach(router) export default router
- 新建
async
目錄,用於按業務模組存放你的非同步路由routes
,當然瞭如果的的動態路由實在少,你可以可以寫在index.ts
中匯出,或者一個檔案搞定,這裡演示我們就比如他是一個ts
檔案
// ./src/router/async.ts import type { RouteRecordRaw } from 'vue-router' export const Member: Readonly<RouteRecordRaw[]> = [ { path: '/member', name: 'Member', redirect: '/member/list', meta: { code: 1 }, component: () => import('@/views/layout/index.vue'), children: [ { path: 'list', name: 'Member-List', meta: { code: 2 } component: () => import('@/views/member/list.vue') }, { path: 'item', name: 'Member-Item', meta: { code: 3 } component: () => import('@/views/member/item.vue') } ] } ]
- 新建
guard.ts
檔案,用於書寫路由守衛邏輯(大部分程式設計師習慣將其放至在./src/permission.ts
中,這個看個人喜好,無關痛癢)
// ./src/router/guard.ts
import { userStore } from '@/stores'
import type { Router } from 'vue-router'
let hasInitAuth = true
export default function (router: Router) {
router.beforeEach((to, from, next) => {
const user = userStore()
const authRoutes = user.getAuthRoutes(router.options.routes) || []
if (hasInitAuth) {
authRoutes.forEach((item: any) => router.addRoute(item))
hasInitAuth = false
router.push({ ...to, replace: true })
}
next()
})
}
- 這裡為了處理路由和選單,我新增了一個檔案
extend.ts
,用於存放一些擴充套件的函式(僅供參考)
// ./src/router/extend.ts
import type { RouteRecordRaw } from 'vue-router'
// 生成有許可權的路由表
export function createAuthRoutes(asyncRoutes: Readonly<RouteRecordRaw[]>, authCode: Number) {
return asyncRoutes.filter((s) => {
const code = s.meta?.code
const child = s.children
if (child && child.length > 0) createAuthRoutes(child, authCode)
return !code || (code && authCode)
})
}
// 生成有許可權的導航選單
export function createNavMenus(allRoutes: Readonly<RouteRecordRaw[]>) {
function mapItem(data: Readonly<RouteRecordRaw[]>): any[] {
return data.map((s) => {
let children = []
if (s.children && s.children.length > 0) {
children = mapItem(s.children) || []
}
return {
title: s.meta?.name as string,
children: children.length === 0 ? undefined : children
}
})
}
function filterItem(data: Readonly<RouteRecordRaw[]>): any[] {
return data.filter((s) => {
if (s.children && s.children.length > 0) {
s.children = filterItem(s.children)
}
return true
})
}
return filterItem(mapItem(allRoutes))
}
stores
- 建立一個模組或一個檔案,用來實現對
./src/router/async/**/*.ts
的所有routes
們進行許可權的過濾,以及右側選單的生成,最終你需要在你的state
中儲存兩個值authAsyncRoutes
(有許可權的動態路由列表),authNavMenus
(有許可權的導航選單列表) - 根據你的業務場景需要,對這兩個值做相關的持久化儲存
// ./src/stores/index.ts
import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router'
import { createAuthRoutes, createNavMenus } from '@/router/extend'
import asyncRoutes from '@/router/async'
export const userStore = defineStore('USER_STORES', {
state: () => ({
authNavMenus: [] as any[]
}),
actions: {
getAuthRoutes(syncRoutes: Readonly<RouteRecordRaw[]>) {
/**
* 1. 獲取快取使用者許可權路由
* 2. 獲取快取使用者許可權選單
* 3. 如果存在快取,則給導航選單賦值並返回有許可權的路由
*/
const authAsyncRoutes = sessionStorage.get('UserRoutes')
const authNavMenus = sessionStorage.get('UserMenus')
if (authAsyncRoutes && authNavMenus) {
this.authNavMenus = authNavMenus
return authAsyncRoutes
}
/**
* 4. 如果不存在快取,則獲取當前使用者的許可權配置(我的業務場景是二進位制許可權配置,因此是一個最大許可權值)
* 5. 根據許可權配置,生成有許可權的路由
* 6. 根據有許可權的路由,生成導航選單,並賦值
* 7. 將有許可權的路由和導航選單進行快取
* 9. 返回有許可權的路由
*/
const authCode = sessionStorage.get('UserAuthCode')
if (authCode) {
const authAsyncRoutes = createAuthRoutes(asyncRoutes, authCode)
const allRoutes = syncRoutes.concat(authAsyncRoutes)
this.authNavMenus = createNavMenus(allRoutes)
sessionStorage.set('UserRoutes', authAsyncRoutes)
sessionStorage.set('UserMenus', this.authNavMenus)
return authAsyncRoutes
}
}
}
})
main.ts
- 引入路由和
stores
,並use
,ok!感受報錯和重新整理白屏的洗禮!
// ./src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
是不是發現沒有沒有看出什麼問題,其實這其中有兩個問題
- 控制檯報錯問題,具體是因為 next() 這個發生了一些修改,看了官方文件其實還是一知半解,經過不斷實驗,不斷探索,終於我發現了問題:
// ./src/router/guard.ts 中以下程式碼錯誤,這段程式碼等同於執行了一個 next()
router.push({ ...to, replace: true })
// ,,因此這應該修改為:
if (hasInitAuth) {
authRoutes.forEach((item: any) => router.addRoute(item))
hasInitAuth = false
router.push({ ...to, replace: true })
+ } else {
+ next()
+ }
- next()
- 解決報錯問題,發現重新整理空白,經檢視報錯提示及查看了快取的有許可權的路由發現,快取中是不存在路由元件關聯的,因此這裡我們需要手動將檢視元件匯入並關聯,需要在新增如下程式碼:
// ./src/router/extend.ts 新增如下程式碼:
/**
* 動態新增路由當快取時只會儲存其路由清單樹,不會儲存其關聯的檢視元件
* 故而當重新重新整理或進入頁面時,需要重新將檢視元件與路由清單樹關聯
* 否則會導致頁面空白,無法正常顯示
*/
+ export function authRouteTreePlug(
+ authRoutesTree: Readonly<RouteRecordRaw[]>,
+ parentPath: string = ''
+ ) {
+ // 查閱資料得出,在這裡不支援 () => import() 的寫法,需要使用 import.meta.glob匯入元件,而後在使用,這裡不要使用 @ 哦,否則會找不到,具體原因不明
+ const modules = import.meta.glob('../views/**/*.vue')
+
+ return authRoutesTree.map((item) => {
+ const itemPath = item.path.slice(0, 1) === '/' ? item.path : `/${item.path}`
+ const hasChild = item.children && item.children.length > 0
+
+ // 由於這裡是管理後臺,因此一級路由需要使用layout佈局元件,故增加判斷,各位看官可以根據需求修改
+ const compPath = item.redirect && hasChild ? '/layout/index' : parentPath + itemPath
+ item.component = modules[`../views${compPath}.vue`]
+
+ if (hasChild) {
+ item.children = authRouteTreePlug(item.children as Readonly<RouteRecordRaw[]>, itemPath)
+ }
+ return item
+ })
+ }
// ./src/stores/index.ts 修改如下這段程式碼:
/**
* 1. 獲取快取使用者許可權路由
* 2. 獲取快取使用者許可權選單
* 3. 如果存在快取,則給導航選單賦值並返回有許可權的路由
*/
+ const authAsyncRoutesTree = sessionStorage.get('UserRoutes')
const authNavMenus = sessionStorage.get('UserMenus')
+ if (authAsyncRoutesTree && authNavMenus) {
+ const authAsyncRoutes = authRouteTreePlug(authRoutesTree)
this.authNavMenus = authNavMenus
return authAsyncRoutes
}
這下終於解決了,完事!