vue一步到位的實現(xiàn)動態(tài)路由
最近在寫vue項目,需要由后臺傳來當(dāng)前用戶對應(yīng)權(quán)限的路由表,前端通過調(diào)接口拿到后處理(后端處理路由),就是配置vue動態(tài)路由啦。
由于錯信了一些網(wǎng)上的文章:(,導(dǎo)致在這個問題上耗費了不少時間,想想,還是自己寫一篇文章來記錄一下那些遇到的坑吧。
接下來手把手記錄一下,如何從零開始配置vue動態(tài)路由。
首先呢,先看看靜態(tài)路由的配置,簡單預(yù)覽一下,熟悉的可以直接跳過,說這部分,是為了熟悉一下路由的配置,配置動態(tài)路由其實就是把靜態(tài)路由存到數(shù)據(jù)庫,在取出來放進路由表(靜態(tài)是直接寫在路由表里)。
靜態(tài)路由的回顧
1.創(chuàng)建router/index.js文件,這里只有一些簡單的頁面
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/view/login/Login'
import Index from '@/layout/Index'
import Welcome from '@/layout/welcome/Welcome'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
component: Index,
redirect: '/welcome',
meta: {requireAuth: true},
children: [
{
path: '/welcome',
component: Welcome,
name: '首頁',
meta: {title: '首頁', requireAuth: true}
}
]
}
]
})2.創(chuàng)建router/permission.js文件,配置導(dǎo)航守衛(wèi),可以在每次路由跳轉(zhuǎn)前做一些操作,待會獲取動態(tài)路由操作也在這里配置
import router from '@/router/index'
import 'element-ui/lib/theme-chalk/index.css'
import '@fortawesome/fontawesome-free/css/all.min.css'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
NProgress.configure({
easing: 'ease', // 動畫方式
speed: 500, // 遞增進度條的速度
showSpinner: false, // 是否顯示加載ico
trickleSpeed: 200, // 自動遞增間隔
minimum: 0.3 // 初始化時的最小百分比
})
// 白名單
const whiteList = ['/login'] // no redirect whitelist
// 導(dǎo)航守衛(wèi)
router.beforeEach((to, from, next) => {
NProgress.start()
if (to.meta.requireAuth) {
// 判斷該路由是否需要登錄權(quán)限
if (sessionStorage.getItem('loginName') !== null) {
// 判斷本地是否存在token
next()
// 這里是待會獲取異步路由的地方
} else {
// 未登錄,跳轉(zhuǎn)到登陸頁面
next({
path: '/login'
})
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
if (sessionStorage.getItem('loginName') !== null) {
// 判斷本地是否存在token
next(`/?redirect=${to.path}`)
} else {
next(`/login?redirect=${to.path}`)
}
}
}
})
router.afterEach(() => {
// 在即將進入新的頁面組件前,關(guān)閉掉進度條
NProgress.done()
})這里引用了一個插件,Nprogress,就是下圖的藍色小進度條。不用也可以

3.在main.js里引入剛才創(chuàng)建的router下的index.js文件和permission.js文件,如下
import router from '@/router'
import './router/permission'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})4.在App入口中放入路由跳轉(zhuǎn)的視圖區(qū),跳轉(zhuǎn)的路由頁面都顯示在這里
// APP.vue文件
<div id="app">
<router-view />
</div>現(xiàn)在靜態(tài)路由就配好了,可以正常的跳轉(zhuǎn)了!
文件夾長這樣

動態(tài)路由的配置
接下來開始配置動態(tài)路由了!
有坑的地方我會說明一下。
1.首先呢,router/index.js,沒有什么需要改的,把你需要動態(tài)獲取的路由信息刪除掉即可,可以留一部分靜態(tài)路由,如登錄頁,首頁等等,若全刪,routes賦為[]即可。
2.創(chuàng)建一個文件,我命名為getAsyncRouter.js,用來處理獲取到的動態(tài)路由信息。
// 用于處理動態(tài)菜單數(shù)據(jù),將其轉(zhuǎn)為 route 形式
export function fnAddDynamicMenuRoutes (menuList = [], routes = []) {
// 用于保存普通路由數(shù)據(jù)
let temp = []
// 用于保存存在子路由的路由數(shù)據(jù)
let route = []
// 遍歷數(shù)據(jù)
for (let i = 0; i < menuList.length; i++) {
// 存在子路由,則遞歸遍歷,并返回數(shù)據(jù)作為 children 保存
if (menuList[i].children && menuList[i].children.length > 0) {
// 獲取路由的基本格式
route = getRoute(menuList[i])
// 遞歸處理子路由數(shù)據(jù),并返回,將其作為路由的 children 保存
route.children = fnAddDynamicMenuRoutes(menuList[i].children)
// 保存存在子路由的路由
routes.push(route)
} else {
// 保存普通路由
temp.push(getRoute(menuList[i]))
}
}
// 返回路由結(jié)果
return routes.concat(temp)
}
// 返回路由的基本格式
function getRoute (item) {
// 路由基本格式
let route = {
// 路由的路徑
path: item.url,
// 路由名
name: item.name,
// 路由所在組件
// component: (resolve) => require([`@/layout/Index`], resolve),
component: (resolve) => require([`@/view${item.curl}`], resolve),
meta: {
id: item.id,
icon: item.icon
},
// 路由的子路由
children: []
}
// 返回 route
return route
}這里是兩個函數(shù),把獲取到的數(shù)據(jù)處理成路由表數(shù)據(jù),將其變化成 route 的形式。
fnAddDynamicMenuRoutes用于遞歸菜單數(shù)據(jù)。getRoute用于返回某個數(shù)據(jù)轉(zhuǎn)換的 路由格式。
注釋寫的應(yīng)該算是很詳細了,主要講一下思路:
- 對數(shù)據(jù)進行遍歷,
- 定義兩個數(shù)組(temp,route),temp 用于保存沒有子路由的路由,route 用于保存存在子路由的路由。
- 如果某個數(shù)據(jù)存在 子路由,則對子路由進行遍歷,并將其返回結(jié)果作為當(dāng)前數(shù)據(jù)的 children。并使用 route 保存路由。
- 如果某個數(shù)據(jù)不存在子路由,則直接使用 temp 保存路由。
- 最后,返回兩者拼接的結(jié)果,即為轉(zhuǎn)換后的數(shù)據(jù)。
route 格式一般如下:
path:指路由路徑(可以根據(jù)路徑定位路由)。name:指路由名(可以根據(jù)路由名定位路由)。component:指路由所在的組件。children:指的是路由組件中嵌套的子路由。meta:用于定義路由的一些元信息。
這里有個大坑?。。?nbsp;在配置component的時候,需要動態(tài)導(dǎo)入組件,也就是vue異步組件,查看文檔如下

可知,有兩種導(dǎo)入方式,import和require方式。這里簡單說一下,
1 import屬于es6導(dǎo)入方式,只支持靜態(tài)導(dǎo)入,也就是說,你不能夠使用變量或表達式,只能使用字符串。
2 require屬于commonJS方式,可以支持動態(tài)的導(dǎo)入,但筆者嘗試中,可以使用``模板字符串方式,貌似也無法直接使用變量。這里盡量用字符串拼接,而不是用變量去代替拼接的字符串!
由于import不支持動態(tài)導(dǎo)入方式,筆者采用的是require的方式,并且在webpack文檔中有這樣一段話。

綜上所述,就是說,組件的導(dǎo)入可以使用字符串拼接的方式導(dǎo)入,可以使用模板字符串導(dǎo)入(字符串中含有變量),但是使用模板字符串導(dǎo)入時不能夠只使用變量?。”仨氈付ㄒ粋€目錄,不能全用變量代替!
// 字符串拼接方式
component: import('@/view'+'/sys/user')
// 模板字符串方式,webpack4+版本需使用require方式,注意,`@${item.curl}`,是不行的!必須指定一個目錄,不能全用變量代替
component: (resolve) => require([`@/view${item.curl}`], resolve),這個現(xiàn)象其實是與webpack import()的實現(xiàn)高度相關(guān)的。由于webpack需要將所有import()的模塊都進行單獨打包,所以在工程打包階段,webpack會進行依賴收集。
此時,webpack會找到所有import()的調(diào)用,將傳入的參數(shù)處理成一個正則,如:
import('./app'+path+'/util') => /^\.\/app.*\/util$/也就是說,import參數(shù)中的所有變量,都會被替換為【.*】,而webpack就根據(jù)這個正則,查找所有符合條件的包,將其作為package進行打包。
所以動態(tài)導(dǎo)入的正確姿勢,應(yīng)該是盡可能靜態(tài)化表達包所處的路徑,最小化變量控制的區(qū)域。
3.有了處理動態(tài)路由數(shù)據(jù)的函數(shù),那就需要在合適的地方調(diào)用,以發(fā)揮作用。這時就需要用到beforeEach了,在router/permission.js文件,配置導(dǎo)航守衛(wèi),可以在每次路由跳轉(zhuǎn)前做一些操作,這里就是獲取動態(tài)路由了
import router from '@/router/index'
import 'element-ui/lib/theme-chalk/index.css'
import '@fortawesome/fontawesome-free/css/all.min.css'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import {fnAddDynamicMenuRoutes} from '@/router/getAsyncRouter'
import {getRouter} from '@/api/sys/Menu'
import store from '@/store'
NProgress.configure({
easing: 'ease', // 動畫方式
speed: 500, // 遞增進度條的速度
showSpinner: false, // 是否顯示加載ico
trickleSpeed: 200, // 自動遞增間隔
minimum: 0.3 // 初始化時的最小百分比
})
// 白名單
const whiteList = ['/login'] // no redirect whitelist
// 導(dǎo)航守衛(wèi)
router.beforeEach((to, from, next) => {
NProgress.start()
try {
// 判斷是否已經(jīng)獲取過動態(tài)菜單,未獲取,則需要獲取一次
if (store.getters.dynamicRoutes.length === 0) {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
getRouter().then(res => {
if (res.code === 0) {
let menuRouter = fnAddDynamicMenuRoutes(res.data[0].children)
store.dispatch('app/dynamicRoutes', menuRouter).then(() => {
router.addRoutes(store.getters.dynamicRoutes)
next({...to, replace: true})
})
} else {
console.log('獲取動態(tài)路由失敗!')
next({path: '/login'})
}
})
}
} else {
// 路由已存在或已緩存路由
if (to.meta.requireAuth) {
if (sessionStorage.getItem('loginName') !== null) {
// 判斷本地是否存在token
next()
} else {
// 未登錄,跳轉(zhuǎn)到登陸頁面
next({path: '/login'})
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
if (sessionStorage.getItem('loginName') !== null) {
// 判斷本地是否存在token
next(`/?redirect=${to.path}`)
} else {
next(`/login?redirect=${to.path}`)
}
}
}
}
} catch (error) {
console.log('出錯了')
next(`/login?redirect=${to.path}`)
}
})
router.afterEach(() => {
// 在即將進入新的頁面組件前,關(guān)閉掉進度條
NProgress.done()
})
注釋寫的比較詳細了,主要說下思路:
首先確定獲取動態(tài)菜單數(shù)據(jù)的時機。一般在登錄成功跳轉(zhuǎn)到主頁面時,獲取動態(tài)菜單數(shù)據(jù)。
- 由于涉及到路由的跳轉(zhuǎn)(登錄頁面 -> 主頁面),所以可以使用 beforeEach 進行路由導(dǎo)航守衛(wèi)操作?!?nbsp;
- 但由于每次路由跳轉(zhuǎn)均會觸發(fā) beforeEach ,所以為了防止頻繁獲取動態(tài)路由值,這里采用vuex方式存儲了獲取的路由信息,如果已獲取路由,就直接跳轉(zhuǎn),否則就獲取一次動態(tài)菜單數(shù)據(jù)并處理。
如果不采用vuex方式也可以存到sessionStorage里面,總之就是保存一下獲取的路由信息,以免重復(fù)獲取。
4接下來看一看,store的寫法
const state = {
dynamicRoutes: []
}
const mutations = {
DYNAMIC_ROUTES (state, routes) {
state.dynamicRoutes = routes
}
}
const actions = {
dynamicRoutes ({commit}, routes) {
commit('DYNAMIC_ROUTES', routes)
}
}注意:vue是單頁面應(yīng)用程序,所以頁面一刷新數(shù)據(jù)部分?jǐn)?shù)據(jù)也會跟著丟失,所以我們需要將store中的數(shù)據(jù)存儲到本地,才能保證路由不丟失。
到這里,動態(tài)路由就已經(jīng)配置好了,總體來說不是很難,主要是有坑!但只要理清了思路,寫起代碼就會很清晰。
說一些筆者遇到的問題
1.首先是component動態(tài)導(dǎo)入的問題,由于webpack的版本不同,需要根據(jù)情況來采用import和require方式來導(dǎo)入,如果導(dǎo)入不正確多半會報錯,找不到module等等。或者不報錯,但路由跳轉(zhuǎn)是空白頁。如果導(dǎo)入正確則一定可以跳轉(zhuǎn)正常,否則,請查找正確的導(dǎo)入方法!路由跳轉(zhuǎn)正確,能顯示跳轉(zhuǎn)的組件,就完成了一大半!
2.路由的跳轉(zhuǎn)的地方,由于我們項目采用的是左側(cè)菜單欄,有二級菜單,所以當(dāng)路由跳轉(zhuǎn)正常時,還需確定路由跳轉(zhuǎn)所渲染的位置是否正確!
后記
在刷新頁面時遇到一個問題,動態(tài)路由表會隨著vuex的刷新而消失。
處理:這里在app.vue頁面刷新時利用sessionStorage存儲一下store,防止刷新丟失vuex。
如下:
export default {
name: 'App',
created () {
// 在頁面加載時讀取sessionStorage里的狀態(tài)信息
if (sessionStorage.getItem('storeData')) {
this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('storeData'))))
}
// 在頁面刷新時將vuex里的信息保存到sessionStorage里
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('storeData', JSON.stringify(this.$store.state))
})
// 兼容iphone手機
window.addEventListener('pagehide', () => {
sessionStorage.setItem('storeData', JSON.stringify(this.$store.state))
})
}
}另外,在登錄成功后也獲取了一下動態(tài)路由。這樣處理下來,刷新時也不會丟失路由了!
// 獲取動態(tài)路由
getRouter().then(res => {
if (res.code === 0) {
let menuRouter = fnAddDynamicMenuRoutes(res.data[0].children)
store.dispatch('app/dynamicRoutes', menuRouter).then(() => {
router.addRoutes(store.getters.dynamicRoutes)
})
console.log(store.getters.dynamicRoutes)
} else {
console.log('獲取動態(tài)路由失敗!')
router.push('/login')
}
})以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決element-ui el-input賦值后不能編輯的問題
這篇文章主要介紹了解決element-ui el-input賦值后不能編輯的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
Vue 實現(xiàn)對quill-editor組件中的工具欄添加title
這篇文章主要介紹了Vue 實現(xiàn)對quill-editor組件中的工具欄添加title,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08

