借用大佬的一張圖,侵權立刪javascript
在今年年初在掘金髮布了一篇文章記一次Vue動態渲染路由的實現,如今代碼通過不斷的Reviewcss
如今徹底優化了以前的實現方法,代碼量減小不少,邏輯更加簡單,同時也更加穩定前端
demo已經部署到github,歡迎體驗~~ vue-element-asyncLogin, 你的start是個人動力!vue
前端路由鑑權相信只要瞭解過vue-element-admin的都知道,前端鑑權方案是徹底可行的,思路清晰,難度適中,項目中徹底可使用,那麼相對來講動態路由優點在什麼地方呢java
前端鑑權
明顯更加好用,成本更低,程序員們也不用996了(霧),可是對於權限等級不少,而且比較大的項目,維護這一套鑑權路由,毫無疑問是一個大工程,而且面對頻繁變動的需求,bug會出現的更加頻繁,前端工程師工做量大大增長,這時候彷佛前端鑑權就再也不是好的方案
相比較以前使用localStorage存儲登陸狀態,如今把登陸狀態交給cookice進行管理git
路由信息所有交給Vuex進行管理,再也不從localStorage裏面走,增長了系統的穩定性程序員
配置基礎路由github
具體的實現思路vue-router
router/router.jsvuex
// ......
// 靜態路由
export const StaticRouterMap = [
{
path: '/login',
component: login,
meta: { title: '管理員登陸' },
hidden: true
},
{
path: '/user',
component: userLogin,
redirect: '/user/userlogin',
name: 'user',
hidden: true,
children: [
{
path: 'userLogin',
component: () => import('@/views/userLogin/components/login'),
meta: { title: '商戶登陸' }
},
{
path: 'userRegistry',
component: () => import('@/views/userLogin/components/registry'),
meta: { title: '商戶註冊' }
}
]
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '根目錄', icon: 'dashboard', affix: true }
}
]
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
}
]
export default new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: StaticRouterMap
})
複製代碼
與後端同窗定製路由結構 (如下爲json)
後端會根據當前用戶權限動態返回路由結構 前端再也不須要考慮權限問題
[{
"id": 1,
"name": "Nested",
"code": null,
"description": null,
"url": "/nested",
"generatemenu": 0,
"sort": 0,
"parentId": null,
"permName": null,
"redirect": "/nested/menu1",
"title": "Nested",
"icon": "nested",
"children": [{
"id": 2,
"name": "Menu1",
"code": null,
"description": null,
"url": "menu1",
"generatemenu": 0,
"sort": 0,
"parentId": 1,
"permName": null,
"redirect": "",
"title": "Menu1",
"icon": "menu1",
"children": [{
"id": 4,
"name": "Menu1-1",
"code": null,
"description": null,
"url": "menu1-1",
"generatemenu": 0,
"sort": 0,
"parentId": 2,
"permName": null,
"redirect": "",
"title": "Menu1-1",
"icon": "",
"children": null
}, {
"id": 5,
"name": "Menu1-2",
"code": null,
"description": null,
"url": "menu1-2",
"generatemenu": 0,
"sort": 0,
"parentId": 2,
"permName": null,
"redirect": "",
"title": "Menu1-2",
"icon": "",
"children": null
}]
}, {
"id": 3,
"name": "Menu2",
"code": null,
"description": null,
"url": "menu2",
"generatemenu": 0,
"sort": 0,
"parentId": 1,
"permName": null,
"redirect": "",
"title": "Menu2",
"icon": "menu2",
"children": null
}]
}]
複製代碼
固然這不是直接用於渲染路由 咱們須要進行遞歸處理成爲咱們想要的數據
router/_import
export default file => {
return map[file] || null
}
const map = {
Nested: () => import('@/views/layout/Layout'),
Menu1: () => import('@/views/nested/menu1/index'),
'Menu1-1': () => import('@/views/nested/menu1/menu1-1'),
'Menu1-2': () => import('@/views/nested/menu1/menu1-2')
}
複製代碼
處理後端原始路由數據
../utils/addRouter
遞歸寫入比以前版本的遞歸刪除更加穩定,代碼量也更少
import _import from '../router/_import' // 獲取組件的方法
/** * 生成路由 * @param {Array} routerlist 格式化路由 * @returns */
export function addRouter(routerlist) {
const router = []
routerlist.forEach(e => {
let e_new = {
path: e.url,
name: e.name,
component: _import(e.name)
}
if (e.children) {
e_new = Object.assign({}, e_new, { children: addRouter(e.children) })
}
if (e.redirect) {
e_new = Object.assign({}, e_new, { redirect: e.redirect })
}
if (e.generatemenu == 0) {
e_new = Object.assign({}, e_new, { hidden: true })
}
if (e.icon !== '' && e.title !== '') {
e_new = Object.assign({}, e_new, {
meta: { title: e.title, icon: e.icon }
})
} else if (e.title !== '' && e.icon === '') {
e_new = Object.assign({}, e_new, { meta: { title: e.title }})
}
router.push(e_new)
})
return router
}
複製代碼
處理後的路由
咱們處理後的路由後面須要與現有的router進行拼接,這裏須要根據需求 修改處理路由的規則
[{
"name": "Nested",
"redirect": "/nested/menu1",
"children": [{
"name": "Menu1",
"children": [{
"name": "Menu1-1",
"children": null,
"path": "menu1-1",
"meta": {
"title": "Menu1-1"
}
}, {
"name": "Menu1-2",
"children": null,
"path": "menu1-2",
"meta": {
"title": "Menu1-2"
}
}],
"path": "menu1",
"meta": {
"title": "Menu1",
"icon": "menu1"
}
}, {
"name": "Menu2",
"children": null,
"path": "menu2",
"component": null,
"meta": {
"title": "Menu2",
"icon": "menu2"
}
}],
"path": "/nested",
"meta": {
"title": "Nested",
"icon": "nested"
}
}]
複製代碼
以上的都是準備工做,就是爲了將初始路由
與後端返回的動態路由
進行拼接
這部分代碼也是優化的核心
import router from './router'
import store from './store'
import { getToken, removeToken } from './utils/auth'
import NProgress from 'nprogress' // Progress 進度條
import 'nprogress/nprogress.css' // Progress 進度條樣式
import { Message } from 'element-ui'
import { getRouter } from './api/login'
import { addRouter } from './utils/addRouter'
const whiteList = ['/login']
var data = false // 本次demo用變量湊合一下,項目裏面應該放到vuex內
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
// 判斷cookice是否存在 不存在即爲未登陸
if (to.path !== '/login') {
if (data) {
// 獲取了動態路由 data必定true,就無需再次請求 直接放行
next()
} else {
// data爲false,必定沒有獲取動態路由,就跳轉到獲取動態路由的方法
gotoRouter(to, next)
}
} else {
Message({ message: '您已經登陸', type: 'info' })
next('/')
}
} else {
data = false
if (whiteList.indexOf(to.path) !== -1) {
// 免登錄白名單 直接進入
next()
} else {
if (to.path !== '/login') {
// 重定向到登陸頁面 不能這麼寫 由於假如以前的角色是 管理員頁面 後又登錄了非管理員 重定向的頁面就可能不存在,就會致使404
// next(`/login?redirect=${to.path}`)
next('/login')
} else {
next()
}
}
}
})
router.afterEach(() => {
NProgress.done() // 結束Progress
})
function gotoRouter(to, next) {
getRouter(store.getters.token) // 獲取動態路由的方法
.then(res => {
console.log('解析後端動態路由', res.data.data)
const asyncRouter = addRouter(res.data.data) // 進行遞歸解析
// 必定不能寫在靜態路由裏面,不然會出現,訪問動態路由404的狀況.因此在這列添加
asyncRouter.push({ path: '*', redirect: '/404', hidden: true })
return asyncRouter
})
.then(asyncRouter => {
router.addRoutes(asyncRouter) // vue-router提供的addRouter方法進行路由拼接
data = true // 記錄路由獲取狀態
store.dispatch('setRouterList', asyncRouter) // 存儲到vuex
store.dispatch('GetInfo')
next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
})
.catch(e => {
console.log(e)
removeToken()
})
}
複製代碼
Vuex內部的邏輯
import { StaticRouterMap } from '../../router/index'
state: {
//.....
RouterList: [] // 動態路由
},
mutations: {
set_router: (state, RouterList) => {
state.RouterList = RouterList
}
},
action: {
// 動態設置路由 此爲設置設置途徑
setRouterList({ commit }, routerList) {
commit('set_router', StaticRouterMap.concat(routerList)) // 進行路由拼接並存儲
},
}
複製代碼
相對以前的邏輯要簡單不少
須要注意的是 經過 addRoutes合併的路由 不會被this.$router.options.routes
獲取到,因此須要將獲取的路由拼接到this.$router.options.routes
上
最後修改渲染側邊欄部分部分的代碼
src\views\layout\components\Sidebar\index.vue
computed: {
// ....
routes() {
return this.$store.getters.routerList
},
// ....
}
複製代碼
我已精心準備了一個簡單的demo vue-element-asyncLogin,歡迎體驗,若是對你有幫助,請不要吝嗇你的start~~