Vue3中簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)添加路由
前言
通過后端接口的返回值,動(dòng)態(tài)添加路由,是作為權(quán)限控制的一種常見方式,本文將簡(jiǎn)單講解如何在Vue3中動(dòng)態(tài)添加路由。
示例數(shù)據(jù)
[ { "id": 1, "pid": 0, "url": "/dashboard", "title": "控制面板板", "component": "/src/views/dashboard/DashboardView.vue", "icon": "SlidersOutlined", "is_show": 0, "level": 1, "sort": 1, "order": "1", "type": "menu", "status": 0, "children": null }, { "id": 2, "pid": 0, "url": "/system", "title": "系統(tǒng)設(shè)置", "component": null, "icon": "ToolOutlined", "is_show": 0, "level": 1, "sort": 7, "order": "2", "type": "menu_dir", "tips": null, "status": 0, "children": [ { "id": 7, "pid": 2, "url": "/system/menu", "title": "菜單管理", "component": "/src/views/system/MenuView.vue", "icon": "BarsOutlined", "is_show": 0, "level": 2, "sort": 3, "order": "2,7", "type": "menu", "tips": null, "status": 0, "children": [ { "id": 8, "pid": 7, "url": "/system/menu/add", "title": "新增菜單", "component": null, "icon": null, "is_show": 1, "level": 3, "sort": 1, "order": "2,7,8", "type": "button", "tips": null, "status": 0, "children": null } ] } ] }, ]
思路分析
動(dòng)態(tài)添加路由的實(shí)質(zhì),就是先將后端返回的json數(shù)據(jù)轉(zhuǎn)化成一個(gè)個(gè)RouteRecordRaw
形式的對(duì)象,然后調(diào)用Vue Router的addRoute
方法,添加進(jìn)路由列表中。由于每個(gè)路由地址都會(huì)對(duì)應(yīng)一個(gè)Vue組件,因此還需要將Vue組件都通過import.meta.glob
讀取到內(nèi)存中。
具體實(shí)現(xiàn)函數(shù)
const viewsComponent: Record<string, any> = import.meta.glob("/src/views/**/*.vue", { eager: true }) const addRouteAll = (menu: RoleMenu[]) => { //RoleMenu就是接口返回的數(shù)據(jù)的類型 menu.forEach(item => { if (item.type === "menu" && viewsComponent[item.component]) { addRouteItem(item) } if (item.children && item.children.length > 0) { addRouteAll(item.children) } }) } const addRouteItem = (route: RoleMenu) => { const path = route.url const component = viewsComponent[route.component] const routeBaseInfo: RouteRecordRaw = { path, name: path.substring(1), component: component.default, meta: { title: route.title, icon: route.icon, keepalive: route.children && route.children.length > 0 ? 0 : path.substring(1), menu_type: "tab", type: route.type, url: route.url, addTab: true } } router.addRoute(routeBaseInfo) }
存在問題
路由何時(shí)處理?
筆者一開始認(rèn)為,登錄成功后立刻調(diào)用獲取菜單的接口,然后處理路由,因此路由的處理應(yīng)該在登錄頁面中的登錄請(qǐng)求成功后進(jìn)行處理,但是此時(shí)存在一個(gè)問題,用戶登錄成功進(jìn)入后臺(tái)頁面,然后用戶刷新頁面,就會(huì)提示導(dǎo)航失敗,控制臺(tái)也會(huì)報(bào)錯(cuò),因此筆者認(rèn)為應(yīng)該在登錄成功進(jìn)入后臺(tái)頁面之后開始處理。
筆者后臺(tái)的主體頁面框架為MainLayout
,因此筆者在此進(jìn)行路由處理。
const getMenu = () => { apiAuthMenuList().then(res => { menuList.value = handleMenu(res.content) //菜單處理 addRouteAll(res.content) }) } onMounted(() => { getMenu() })
導(dǎo)航失敗
?? [Vue Router warn] : No match found for location with path "/dashboard"
這是因?yàn)槁酚商D(zhuǎn)的時(shí)機(jī)要早于組件掛載,因此在組件掛載并處理路由前,路由就已經(jīng)跳轉(zhuǎn)并報(bào)錯(cuò)了。
筆者解決這個(gè)問題的思路有兩個(gè):
- 首先定義全局變量
routeReady
,初始值為false
,當(dāng)路由處理完成后變?yōu)?code>true - 在路由守衛(wèi)
beforeEach
中判斷,如果routeReady
為false
則處理路由,處理完成后跳轉(zhuǎn)。 - 創(chuàng)建一個(gè)Loading頁面,如果路由沒有匹配的地址則跳轉(zhuǎn)至Loading頁面,并在該頁面進(jìn)行判斷:
- 如果
routeReady
為true
,說明去往的地址并不在該用戶的權(quán)限菜單中,轉(zhuǎn)向404頁面 - 如果
routeReady
為false
,則說明路由未加載完成,那么就在當(dāng)前頁面等待,等routeReady
為true
時(shí),再執(zhí)行上面的判斷
- 如果
筆者這里用了方法2。
//截獲所有未匹配的路由,進(jìn)入Loading頁面 { path: "/:pathMatch(.*)*", component: () => import("../views/LoadingView.vue") }
//LoadingView.vue watchEffect(() => { if (globalStore.routeReady) { const routeList = router.getRoutes() if (routeList.find(i => i.path === router.currentRoute.value.fullPath)) { router.push(router.currentRoute.value.fullPath) } else { router.push("/notFound") } } })
通過這種方式,可以在用戶刷新頁面后有一個(gè)順滑的體驗(yàn)。
進(jìn)入第一個(gè)路由
目前還存在一個(gè)問題,用戶在登錄跳轉(zhuǎn)后,會(huì)進(jìn)入后臺(tái)頁面,但是此時(shí)不會(huì)進(jìn)入到任一菜單中:
而我們希望登錄跳轉(zhuǎn)后能自動(dòng)進(jìn)入到第一個(gè)菜單,即:
因此我們需要一個(gè)方法來找到第一個(gè)可用的路由:
const getFirstRoute = (routes: RouteRecordRaw[]): false | RouteRecordRaw => { const routerPaths: string[] = [] const routers = router.getRoutes() routers.forEach(item => { if (item.path) routerPaths.push(item.path) }) let find: boolean | RouteRecordRaw = false for (const key in routes) { if (routes[key].meta?.type != "menu_dir" && routerPaths.indexOf(routes[key].path) !== -1) { return routes[key] } else if (routes[key].children && routes[key].children?.length) { find = getFirstRoute(routes[key].children!) if (find) return find } } return find }
然后調(diào)用這個(gè)方法即可:
const init = () => { const firstRoute = getFirstRoute(menuList.value!) if (firstRoute) { router.push(firstRoute.path) } } onMounted(() => { init() })
后記
到此這篇關(guān)于Vue3中簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)添加路由的文章就介紹到這了,更多相關(guān)Vue3動(dòng)態(tài)添加路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Vue中延遲執(zhí)行某個(gè)函數(shù)的實(shí)現(xiàn)方式
在Vue中延遲執(zhí)行某個(gè)函數(shù),你可以使用setTimeout()函數(shù)或者Vue提供的生命周期鉤子函數(shù),本文通過一些示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-12-12vue實(shí)現(xiàn)仿淘寶結(jié)賬頁面實(shí)例代碼
本文是小編給大家分享的vue實(shí)現(xiàn)仿淘寶結(jié)賬頁面實(shí)例代碼,主要功能是仿照淘寶頁面的結(jié)算購物車商品時(shí)自動(dòng)算出合計(jì)價(jià)格的頁面,具體實(shí)例代碼大家參考下本文2017-11-11vue 項(xiàng)目中實(shí)現(xiàn)按鈕防抖方法
這篇文章主要介紹了vue 項(xiàng)目中實(shí)現(xiàn)按鈕防抖方法,首先需要新建 .js文件存放防抖方法,引入防抖文件,methods中添加方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼
這篇文章主要介紹了vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06Vue列表如何實(shí)現(xiàn)滾動(dòng)到指定位置樣式改變效果
這篇文章主要介紹了Vue列表實(shí)現(xiàn)滾動(dòng)到指定位置樣式改變效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05