Vue登錄后添加動態(tài)路由并跳轉的實踐分享
前言
項目框架:Vue3 + TypeScript
有這樣一個需求,系統(tǒng)默認只有最基礎的幾個路由,如登錄、404等,其它路由需要在登錄后動態(tài)添加。系統(tǒng)沒有固定首頁,登錄完成后跳轉至動態(tài)菜單的第一個菜單頁。
分析
這一邏輯乍一看很簡單,其實有很多小坑在里面。其中最容易踩的的坑是動態(tài)路由尚未渲染完成
就已經觸發(fā)路由跳轉了,這時候肯定是404,因為路由并不存在;另一個容易踩的坑是路由重復加載
,此時頁面會顯示空白,需要手動刷新才能正常顯示。
首先想到的就是使用 Promise
函數解決,結果行不通。addRoute
是一個宏任務 和 resolve
是微任務,所以 Promise
結束的時候并不能代表動態(tài)路由已經添加完成。
其次又想到使用 async
函數來確保獲取到登錄成功結果的時候,路由已經添加完成,結果一番嘗試后依然行不通。因為添加路由的操作不是異步的,沒有返回 Promise
對象,因此這里的 await
是不會產生效果的。(PS:事后使用 Promise.all
解決了這一問題,后面的具體方法上會說。)
最后,想到了一個很笨的解決方法,輪詢。實驗過后,確定可以實現,但就如開頭說的,這會顯得很 low
,雖然它最終解決了問題。
實踐
登錄的操作都是一樣的,所以單獨拿出來只寫一遍。表單就不做介紹了,就從點擊登錄表單校驗通過后說起。
所有登錄的代碼放到一個頁面會顯得臃腫,所以具體登錄的操作邏輯我把它抽離了出來。在 src/utils
目錄下創(chuàng)建一個 auth.ts
文件。
auth.ts
import { useRouteListStore } from '@/store/router' const routeListStore = useRouteListStore() // 登錄 export async function Login(data: { username: string; password: string; portal: string; corpCode: string }) { const { username, password, portal, corpCode } = data try { // 登錄接口 const res = await getLogin({ username, password, portal, corpCode }) // ... // 這里寫保存用戶信息及 token 的邏輯 // ... // 添加路由操作,寫在 pinia 中,后面會說 await routeListStore.updateRouteList() return res } catch (err) { return err } }
接下來要寫添加路由的具體邏輯。在 src/store
目錄下創(chuàng)建一個 router.ts
文件,添加內容如下:(PS:具體文件路徑要結合具體的項目內容,以下路徑及菜單格式僅作為示例)。
根據處理方式不同,有兩種方案。
方案一:使用 async 函數
src/store/router.ts
export const useRouteListStore = defineStore('routeList', { state: () => ({ routeList: [], breadcrumb: [], getRouter: true // 是否需要重新加載路由 }), actions: { // 更新菜單并追加路由 async updateRouteList() { const modules = import.meta.glob('../views/**/*.vue') // 此為接口請求獲取的菜單 const list = await getMenus() list.forEach((e) => { e.route = e.path e.component = () => import('@/layout/index.vue') e.redirect = `${e.path}/${e.children[0].path}` e.children.forEach((item) => { item.route = `${e.path}/${item.path}` item.component = modules[`../views${item.component}.vue`] }) }) await addRouteList(list) this.getRouter = false this.routeList = list return true }, } })
接下來寫動態(tài)添加路由的邏輯,使用 Promise.all 來確保 Pinia 中返回結果時,動態(tài)路由已經加載完成。在 src/router
創(chuàng)建 index.ts
文件,添加內容如下:
src/router/index.ts
export function addRouteList(data: any[] = []) { return new Promise((resolve) => { const promises = [] data.forEach((e) => promises.push(router.addRoute(e))) Promise.all(promises).then(() => resolve(true)) }) }
使用 async 函數之后,登錄頁的操作將會變得很簡單。
login.vue
import { Login } from '@/utils/auth' const onSubmit = () => { validate().then(() => { Login(formState).then(() => { router.push(routerStore.routeList[0].path) }).catch(err => { message.error(err.message) }) }) }
方案二:使用輪詢
輪詢的方案相比于使用 async
函數要簡單很多,因為它不需要確保登錄后拿到結果的那一刻,路由是加載完成的。具體實現代碼如下:
src/store/router.ts
export const useRouteListStore = defineStore('routeList', { state: () => ({ routeList: [], breadcrumb: [], getRouter: true }), actions: { // 更新菜單并追加路由 updateRouteList() { listMenus().then((res) => { const list = res.data if (list === null) { this.getRouter = false router.push('/404') return } list.forEach((e) => { e.route = e.path e.component = () => import('@/layout/index.vue') e.children.forEach((item) => { item.route = `${e.path}/${item.path}` item.component = modules[`../views${item.component}.vue`] }) }) addRouteList(list) this.getRouter = false this.routeList = list }) } })
src/router/index.ts
export function addRouteList(data: any[] = []) { data.forEach((e) => { router.addRoute(e) }) }
輪詢的好處是邏輯簡單,唯一麻煩的一點就是在登錄后添加一個定時器去定期獲取路由是否加載完成。之所以要加定時器是因為獲取菜單是異步請求,而程序執(zhí)行時很快的,所以要確保執(zhí)行路由跳轉命令時菜單是加載完成的。
login.vue
import { ref, onBeforeUnmount } from 'vue' import { useRouter } from 'vue-router' import { useRouteListStore } from '@/store/router' const routerStore = useRouteListStore() import { Login } from '@/utils/auth' const router = useRouter() // 每0.5s判斷一次菜單是否加載完成,最多判斷30次,超過則說明網絡環(huán)境極差 const timer = ref(null) const onSubmit = () => { validate().then(() => { Login(formState).then(() => { let i = 0 timer.value = setInterval(() => { if (routerStore.routeList[0].path) { router.push(routerStore.routeList[0].path) } i++ if (i > 30) { clearInterval(timer.value) timer.value = null i = null message.error('當前網絡環(huán)境較差!') spinning.value = false } }, 500) }) }) } // 不要忘記清除定時器 onBeforeUnmount(() => { clearInterval(timer.value) timer.value = null })
補充
以上代碼只能保證系統(tǒng)初次登錄后可以正常跳轉頁面,如果退出當前賬號,重新登錄或者更換賬號登錄,會出現路由重復加載的問題,也就是文章開頭所說的另一個容易踩的坑。這個坑解決起來并不困難,只要注意到了,很容易就可以解決。
解決思路是添加路由前置守衛(wèi),同時在 Pinia 中添加一個字段判斷當前路由是否需要重新加載即可。具體代碼如下:
import Cookies from 'js-cookie' import { useRouteListStore } from '@/store/router' // 前置守衛(wèi) router.beforeEach(async (to, from, next) => { const token = Cookies.get('token') if (!token) { next({ path: '/login' }) } else { const routerStore = useRouteListStore() routerStore.addBreadcrumb(to) // 判斷菜單是否存在且是否需要重新加載 if (routerStore.routeList.length === 0 && routerStore.getRouter) { await routerStore.updateRouteList() next({ path: to.path, query: to.query }) } else { next() } } })
如對本文有疑問或不同看法,歡迎在評論區(qū)指出。
以上就是Vue登錄后添加動態(tài)路由并跳轉的實踐分享的詳細內容,更多關于Vue添加動態(tài)路由并跳轉的資料請關注腳本之家其它相關文章!
相關文章
vue3+ts+EsLint+Prettier規(guī)范代碼的方法實現
本文主要介紹在Vue3中使用TypeScript做開發(fā)時,如何安裝與配置EsLint和Prettier,以提高編碼規(guī)范。感興趣的可以了解一下2021-10-10Monaco-editor 的 JSON Schema 配置及使用介紹
這篇文章主要為大家介紹了Monaco-editor 的 JSON Schema 配置及使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10