【轉(zhuǎn)載】vue動態(tài)路由的實現(xiàn)思路及踩坑

原文:Vue Router 實現(xiàn)動態(tài)路由和常見問題及解決方法

Vue項目實現(xiàn)動態(tài)路由的方式大體可分為兩種:

  • 前端將全部路由規(guī)定好邪码,登錄時根據(jù)用戶角色權限來動態(tài)展示路由邑飒;
  • 路由存儲在數(shù)據(jù)庫中携茂,前端通過接口獲取當前用戶對應路由列表并進行渲染米母;

第一種方式在很多Vue UI Admin上都實現(xiàn)了茂腥,可以去讀一下他們的源碼理解具體的實現(xiàn)思路,這里就不過多展開。第二種方式現(xiàn)在來說也比較常見了,因為近期項目正好用到所以單獨講一下欺栗,這里我使用的方案是利用Vue Router的一些特性實現(xiàn)后端主導的動態(tài)路由。

使用到的功能特性

Vue Router 全局前置守衛(wèi)

官網(wǎng)解釋

這里我們主要借助全局前置守衛(wèi)的「前置」特性,在頁面加載前將當前用戶所用到的路由列表注入到Router實例中迟几,注入使用到的方法則是下面的router.addRoutes方法消请。

Vue Router router.addRoutes 實例方法

官網(wǎng)解釋

router.addRoutes方法可以為Router實例動態(tài)添加路由規(guī)則,剛好為我們實現(xiàn)動態(tài)路由提供了注入方法类腮。

Vue Router 路由懶加載

官網(wǎng)解釋

懶加載這個功能不是動態(tài)路由的必要功能臊泰,但既然提供了這一特性,所以就直接在項目中使用了蚜枢。

具體思路

基礎信息準備

前端代碼實現(xiàn)基本靜態(tài)路由缸逃,例如:登錄頁路由,服務器錯誤頁路由等(這里有一個坑厂抽,后面講)需频。數(shù)據(jù)庫存儲全部動態(tài)路由信息。

數(shù)據(jù)庫如何存儲動態(tài)路由信息修肠?我選擇的方案是現(xiàn)將路由引用的對象字符串化贺辰,再將路由列表轉(zhuǎn)化為JSON格式傳輸給后端户盯,經(jīng)后端處理后存儲到數(shù)據(jù)庫里嵌施。總之在前后端進行傳遞的是JSON格式的路由列表信息莽鸭。

如何將路由中引用的對象字符串化吗伤?我遇到的實際問題是:使用的UI組件提供了布局方案,需要引用布局組件并在子路由處引用具體頁面硫眨。我選擇的解決方案是:區(qū)別對待需要引用布局組件的component屬性足淆,使用簡短字符串代替布局組件,使用文件路徑字符串代替頁面引入礁阁。具體實現(xiàn)可以看后面的代碼實例巧号。

利用全局前置守衛(wèi)對路由信息進行判斷

判斷用戶是否登錄 ——》若未登錄,跳轉(zhuǎn)至登錄頁面 ——》若已經(jīng)登錄姥闭,判斷是否已獲取路由列表 ——》若未獲取丹鸿,從后端獲取、解析并保存到Vuex中 ——》若已獲取棚品,跳轉(zhuǎn)至目標頁面

這里我沒做太多考察靠欢,直接將取到數(shù)據(jù)存儲到了Vuex中,在實際項目應用的過程中應考慮數(shù)據(jù)存儲的安全性铜跑。

如何實現(xiàn)路由列表解析门怪?

  1. JSON格式的路由信息解析為JavaScript列表對象;
  2. 利用列表對象的filter方法實現(xiàn)解析函數(shù)锅纺,通過component判斷是否為布局組件掷空;
  3. 若為布局組件,使用布局組件代替component字符串;
  4. 若為具體頁面坦弟,使用loadView函數(shù)加載對應的具體頁面疼电;
  5. 利用 router.addRoutes 方法動態(tài)添加路由

這一步就很簡單了,將解析好的路由列表通過router.addRoutes方法添加到Router實例中即可减拭。

簡單的實現(xiàn)代碼

// router/index.js
import Vue from 'vue'
import store from '@/store'
import Router from 'vue-router'
import { getToken } from '@/lib/util'
 
Vue.use(Router)
 
// 定義靜態(tài)路由
const staticRoutes = [
 {
 path: '/login',
 name: 'login',
 meta: {
 title: '登錄頁面',
 hideInMenu: true
 },
 component: () => import('@/view/login/login.vue')
 },
 {
 path: '/401',
 name: 'error_401',
 meta: {
 hideInMenu: true
 },
 component: () => import('@/view/error-page/401.vue')
 },
 {
 path: '/500',
 name: 'error_500',
 meta: {
 hideInMenu: true
 },
 component: () => import('@/view/error-page/500.vue')
 }
]
 
// 定義登錄頁面名稱(為了方便理解才定義的)
const LOGIN_PAGE_NAME = 'login'
 
// 實例化 Router 對象
const router = new Router({
 staticRoutes,
 mode: 'history'
})
 
// 定義全局前置守衛(wèi)(里面有兩個坑要注意)
router.beforeEach((to, from, next) => {
 // 通過自定義方法獲取用戶 token 用來判斷用戶登錄狀態(tài)
 const token = getToken()
 if (!token && to.name !== LOGIN_PAGE_NAME) {
 // 如果沒有登錄而且前往的頁面不是登錄頁面蔽豺,跳轉(zhuǎn)到登錄頁
 next({ name: LOGIN_PAGE_NAME })
 } else if (!token && to.name === LOGIN_PAGE_NAME) {
 // 如果沒有登錄而且前往的頁面是登錄頁面,跳轉(zhuǎn)到登錄頁面
 // 這里有一個坑拧粪,一定要注意這一步和上一步得分開寫
 // 如果把前兩步判斷合并為 if (!token) next({ name:login })
 // 則會形成登錄頁面無限刷新的錯誤修陡,具體成因后面解釋
 next()
 } else {
 // 如果登錄了
 if (!store.state.app.hasGetRoute) {
 // 如果沒有獲取路由信息,先獲取路由信息而后跳轉(zhuǎn)
 store.dispatch('getRouteList').then(() => {
 router.addRoutes(store.state.app.routeList)
 // 這里也是一個坑可霎,不能使用簡單的 next()
 // 如果直接使用 next() 刷新后會一直白屏
 next({ ...to, replace: true })
 })
 } else {
 // 如果已經(jīng)獲取路由信息魄鸦,直接跳轉(zhuǎn)
 next()
 }
 }
})
 
export default router
// store/index.js
import router from '@/router'
import Main from '@/components/main'
import { getToken } from '@/lib/util'
import { getRoute } from '@/api/app'
 
const loadView = (viewPath) => {
 // 用字符串模板實現(xiàn)動態(tài) import 從而實現(xiàn)路由懶加載
 return () => import(`@/view/${viewPath}`)
}
 
const filterAsyncRouter = (routeList) => {
 return routeList.map((route) => {
 if (route.component) {
 if (route.component === 'Main') {
 // 如果 component = Main 說明是布局組件
 // 將真正的布局組件賦值給它
 route.component = Main
 } else {
 // 如果不是布局組件就只能是頁面的引用了
 // 利用懶加載函數(shù)將實際頁面賦值給它
 route.component = loadView(route.component)
 }
 // 判斷是否存在子路由,并遞歸調(diào)用自己
 if (route.children && route.children.length) {
 route.children = filterAsyncRouter(route.children)
 }
 }
 })
}
 
export default {
 state: {
 routeList: [],
 token: getToken(),
 hasGetRoute: false
 },
 mutations: {
 setRouteList(state, data) {
 // 先將 JSON 格式的路由列表解析為 JavaScript List
 // 再用路由解析函數(shù)解析 List 為真正的路由列表
 state.routeList = filterAsyncRouter(JSON.parse(data))
 // 修改路由獲取狀態(tài)
 state.hasGetRoute = true
 }
 },
 atcions: {
 getRouteList({ state, commit }) {
 return new Promise((resolve) => {
 const token = state.token
 getRoute({ token }).then((res) => {
 let data = res.data.data
 // 注意這里取出的是 JSON 格式的路由列表
 commit('setRouteList', data)
 resolve()
 })
 })
 }
 }
}

常見問題

頁面卡在登錄頁面而且不斷刷新

這個問題的解決方案在「實現(xiàn)代碼」中已經(jīng)提到了癣朗,只需要在判斷登錄狀態(tài)的時候注意不要將兩種未登錄狀態(tài)混為一談即可拾因。但這樣治標不治本,因為同樣的問題可以由不同形式的代碼導致旷余,那導致問題的原因是什么那绢记?然我們慢慢分析:

我們先假設不小心把兩種未登錄的狀態(tài)混在一起判斷:

if (!token) {
 next({ name: LOGIN_PAGE_NAME })
}

這里的next({ name: LOGIN_PAGE_NAME })方法會再一次激活全局前置守衛(wèi),從而導致再一次進入判斷并觸發(fā)next({ name: LOGIN_PAGE_NAME })正卧,如此遞歸調(diào)用下去蠢熄,頁面就會卡主并且不斷刷新。

動態(tài)路由配合路由懶加載

實現(xiàn)這一目的的方案也在代碼示例中展示了:

const loadView = (viewPath) => {
 return () => import(`@/view/${viewPath}`)
}

這里是運用了一個 JavaScript 不太常用的特性:字符串模板炉旷,使用此特性讓不支持字符串拼接的import操作能夠?qū)崿F(xiàn)動態(tài)import不同的模塊签孔。

動態(tài)路由刷新后 404

這應該是本方案中最常見的一個錯誤之一,其原意是很多人在創(chuàng)建「基本靜態(tài)路由」的時候回把 404 頁面的路由也加入在里面窘行,從而導致頁面加載初期動態(tài)路由還沒有加入到路由實例中饥追,匹配范圍最廣的 404 頁面就會跳出來。解決方法就是將 404 頁面的路由也加入到動態(tài)路由中罐盔。

動態(tài)路由刷新后變空白頁

造成這一問題的原因有很多但绕,我這里遇到的問題是使用 vue-router addRouter添加動態(tài)路由,第一次跳轉(zhuǎn)正常翘骂,刷新后變空白壁熄?

let flag = 0
router.beforeEach((to, from, next) => {
    if(to.path !== '/login'){
        if(flag == 0){
            console.log(flag)
            let privileges = ['list','edit'];
            let permission_routes = routeUtil.GenerateRoutes(privileges);
            router.addRoutes(permission_routes);
            flag++
            next({ ...to, replace: true })
        }else{
            next()
        }
    }else{
        next();
    }
})

動態(tài)路由頁面刷新時 Title 不穩(wěn)定

造成這一問題的原因很簡單:因為頁面刷新的時候路由信息還沒加載進來,所以根本沒有標題信息可供加載碳竟。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末草丧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子莹桅,更是在濱河造成了極大的恐慌昌执,老刑警劉巖烛亦,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異懂拾,居然都是意外死亡煤禽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門岖赋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來檬果,“玉大人,你說我怎么就攤上這事唐断⊙〖梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵脸甘,是天一觀的道長恳啥。 經(jīng)常有香客問我,道長丹诀,這世上最難降的妖魔是什么钝的? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮铆遭,結(jié)果婚禮上硝桩,老公的妹妹穿的比我還像新娘。我一直安慰自己疚脐,他們只是感情好亿柑,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棍弄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疟游。 梳的紋絲不亂的頭發(fā)上呼畸,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音颁虐,去河邊找鬼蛮原。 笑死,一個胖子當著我的面吹牛另绩,可吹牛的內(nèi)容都是我干的儒陨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笋籽,長吁一口氣:“原來是場噩夢啊……” “哼蹦漠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起车海,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤笛园,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體研铆,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡埋同,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棵红。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凶赁。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逆甜,靈堂內(nèi)的尸體忽然破棺而出哟冬,到底是詐尸還是另有隱情,我是刑警寧澤忆绰,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布浩峡,位于F島的核電站,受9級特大地震影響错敢,放射性物質(zhì)發(fā)生泄漏翰灾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一稚茅、第九天 我趴在偏房一處隱蔽的房頂上張望纸淮。 院中可真熱鬧,春花似錦亚享、人聲如沸咽块。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侈沪。三九已至,卻和暖如春晚凿,著一層夾襖步出監(jiān)牢的瞬間亭罪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工歼秽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留应役,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓燥筷,卻偏偏與公主長得像箩祥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肆氓,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354