Vue登錄后添加動(dòng)態(tài)路由并跳轉(zhuǎn)的實(shí)踐分享
前言
項(xiàng)目框架:Vue3 + TypeScript
有這樣一個(gè)需求,系統(tǒng)默認(rèn)只有最基礎(chǔ)的幾個(gè)路由,如登錄、404等,其它路由需要在登錄后動(dòng)態(tài)添加。系統(tǒng)沒有固定首頁,登錄完成后跳轉(zhuǎn)至動(dòng)態(tài)菜單的第一個(gè)菜單頁。
分析
這一邏輯乍一看很簡(jiǎn)單,其實(shí)有很多小坑在里面。其中最容易踩的的坑是動(dòng)態(tài)路由尚未渲染完成就已經(jīng)觸發(fā)路由跳轉(zhuǎn)了,這時(shí)候肯定是404,因?yàn)槁酚刹⒉淮嬖?;另一個(gè)容易踩的坑是路由重復(fù)加載,此時(shí)頁面會(huì)顯示空白,需要手動(dòng)刷新才能正常顯示。
首先想到的就是使用 Promise 函數(shù)解決,結(jié)果行不通。addRoute 是一個(gè)宏任務(wù) 和 resolve 是微任務(wù),所以 Promise 結(jié)束的時(shí)候并不能代表動(dòng)態(tài)路由已經(jīng)添加完成。
其次又想到使用 async 函數(shù)來確保獲取到登錄成功結(jié)果的時(shí)候,路由已經(jīng)添加完成,結(jié)果一番嘗試后依然行不通。因?yàn)樘砑勇酚傻牟僮鞑皇钱惒降?,沒有返回 Promise 對(duì)象,因此這里的 await 是不會(huì)產(chǎn)生效果的。(PS:事后使用 Promise.all 解決了這一問題,后面的具體方法上會(huì)說。)
最后,想到了一個(gè)很笨的解決方法,輪詢。實(shí)驗(yàn)過后,確定可以實(shí)現(xiàn),但就如開頭說的,這會(huì)顯得很 low ,雖然它最終解決了問題。
實(shí)踐
登錄的操作都是一樣的,所以單獨(dú)拿出來只寫一遍。表單就不做介紹了,就從點(diǎn)擊登錄表單校驗(yàn)通過后說起。
所有登錄的代碼放到一個(gè)頁面會(huì)顯得臃腫,所以具體登錄的操作邏輯我把它抽離了出來。在 src/utils 目錄下創(chuàng)建一個(gè) 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 中,后面會(huì)說
await routeListStore.updateRouteList()
return res
} catch (err) {
return err
}
}接下來要寫添加路由的具體邏輯。在 src/store 目錄下創(chuàng)建一個(gè) router.ts 文件,添加內(nèi)容如下:(PS:具體文件路徑要結(jié)合具體的項(xiàng)目?jī)?nèi)容,以下路徑及菜單格式僅作為示例)。
根據(jù)處理方式不同,有兩種方案。
方案一:使用 async 函數(shù)
src/store/router.ts
export const useRouteListStore = defineStore('routeList', {
state: () => ({
routeList: [],
breadcrumb: [],
getRouter: true // 是否需要重新加載路由
}),
actions: {
// 更新菜單并追加路由
async updateRouteList() {
const modules = import.meta.glob('../views/**/*.vue')
// 此為接口請(qǐng)求獲取的菜單
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
},
}
})接下來寫動(dòng)態(tài)添加路由的邏輯,使用 Promise.all 來確保 Pinia 中返回結(jié)果時(shí),動(dòng)態(tài)路由已經(jīng)加載完成。在 src/router 創(chuàng)建 index.ts 文件,添加內(nèi)容如下:
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 函數(shù)之后,登錄頁的操作將會(huì)變得很簡(jiǎn)單。
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 函數(shù)要簡(jiǎn)單很多,因?yàn)樗恍枰_保登錄后拿到結(jié)果的那一刻,路由是加載完成的。具體實(shí)現(xiàn)代碼如下:
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)
})
}輪詢的好處是邏輯簡(jiǎn)單,唯一麻煩的一點(diǎn)就是在登錄后添加一個(gè)定時(shí)器去定期獲取路由是否加載完成。之所以要加定時(shí)器是因?yàn)楂@取菜單是異步請(qǐng)求,而程序執(zhí)行時(shí)很快的,所以要確保執(zhí)行路由跳轉(zhuǎn)命令時(shí)菜單是加載完成的。
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次,超過則說明網(wǎng)絡(luò)環(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('當(dāng)前網(wǎng)絡(luò)環(huán)境較差!')
spinning.value = false
}
}, 500)
})
})
}
// 不要忘記清除定時(shí)器
onBeforeUnmount(() => {
clearInterval(timer.value)
timer.value = null
})補(bǔ)充
以上代碼只能保證系統(tǒng)初次登錄后可以正常跳轉(zhuǎn)頁面,如果退出當(dāng)前賬號(hào),重新登錄或者更換賬號(hào)登錄,會(huì)出現(xiàn)路由重復(fù)加載的問題,也就是文章開頭所說的另一個(gè)容易踩的坑。這個(gè)坑解決起來并不困難,只要注意到了,很容易就可以解決。
解決思路是添加路由前置守衛(wèi),同時(shí)在 Pinia 中添加一個(gè)字段判斷當(dāng)前路由是否需要重新加載即可。具體代碼如下:
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()
}
}
})如對(duì)本文有疑問或不同看法,歡迎在評(píng)論區(qū)指出。
以上就是Vue登錄后添加動(dòng)態(tài)路由并跳轉(zhuǎn)的實(shí)踐分享的詳細(xì)內(nèi)容,更多關(guān)于Vue添加動(dòng)態(tài)路由并跳轉(zhuǎn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- vue?+elementui?項(xiàng)目登錄通過不同賬號(hào)切換側(cè)邊欄菜單的顏色
- 登錄頁面的實(shí)現(xiàn)及跳轉(zhuǎn)代碼實(shí)例(vue-router)
- VUE登錄注冊(cè)頁面完整代碼(直接復(fù)制)
- vue前端實(shí)現(xiàn)驗(yàn)證碼登錄功能
- vue實(shí)現(xiàn)登錄數(shù)據(jù)的持久化的使用示例
- Vue實(shí)現(xiàn)驗(yàn)證碼登錄的超詳細(xì)步驟
- 通用vue組件化登錄頁面實(shí)例代碼
- vue實(shí)現(xiàn)未登錄訪問其他頁面自動(dòng)跳轉(zhuǎn)登錄頁功能(實(shí)現(xiàn)步驟)
- Vue精美簡(jiǎn)潔登錄頁完整代碼實(shí)例
- vue限制實(shí)現(xiàn)不登錄無法進(jìn)入其他頁面
相關(guān)文章
vue自適應(yīng)布局postcss-px2rem詳解
這篇文章主要介紹了vue自適應(yīng)布局(postcss-px2rem)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-05-05
vue3+ts+EsLint+Prettier規(guī)范代碼的方法實(shí)現(xiàn)
本文主要介紹在Vue3中使用TypeScript做開發(fā)時(shí),如何安裝與配置EsLint和Prettier,以提高編碼規(guī)范。感興趣的可以了解一下2021-10-10
vue 中 get / delete 傳遞數(shù)組參數(shù)方法
這篇文章主要介紹了vue 中 get / delete 傳遞數(shù)組參數(shù)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
vue3.x使用swiper實(shí)現(xiàn)卡片輪播
這篇文章主要為大家詳細(xì)介紹了vue3.x使用swiper實(shí)現(xiàn)卡片輪播,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
Monaco-editor 的 JSON Schema 配置及使用介紹
這篇文章主要為大家介紹了Monaco-editor 的 JSON Schema 配置及使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
antd vue表格可編輯單元格以及求和實(shí)現(xiàn)方式
這篇文章主要介紹了antd vue表格可編輯單元格以及求和實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
利用Vue與D3.js創(chuàng)建交互式數(shù)據(jù)可視化
在現(xiàn)代Web開發(fā)中,數(shù)據(jù)可視化是一個(gè)引人矚目的熱點(diǎn)領(lǐng)域,從簡(jiǎn)單的圖表到復(fù)雜的交互式儀表盤,數(shù)據(jù)可視化能夠幫助用戶更好地理解數(shù)據(jù),而Vue與D3.js的結(jié)合則為我們提供了構(gòu)建這些可視化效果的強(qiáng)大工具,本文將向您展示如何利用Vue與D3.js創(chuàng)建一個(gè)基本的交互式數(shù)據(jù)可視化項(xiàng)目2025-02-02

