背景: 公司打算做一個(cms),最開始前端參照vue-element-admin的思路,驗證了前端鑑權的可能性,大佬寫的程式碼思路清奇,值得學習,後來考慮諸多因素,介面安全性 前端鑑權的難度 以及專案的複雜度,最後選擇採用後端渲染動態路由的形式
本文相對過時 請看Vue中後臺鑑權的另一種思路 - 動態路由的實現與優化
使用的是Vue+Element的後臺管理模板github
思路參考了一下文章
Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)
實現思路
基礎的一些思路和Vue 動態路由的實現Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)一樣,核心部分加入了自己的理解
- 每次路由跳轉 先判斷 是否登入 登入了才會去進行路由相關邏輯
- 獲取變數
getRouter
,存在則直接放行 因為路由配置存在 - 假如重新整理頁面
getRouter
變數就不存在了,所以 就要在去獲取一次 - 獲取到了在儲存到
getRouter
上,便於以後使用,減少請求
以下為具體實現思路
配置基礎路由
基礎路由為不登入也可以訪問的路由
const StaricRouterMap = [
{
path: '/login',
component: login,
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'dashboard',
hidden: true,
meta: { title: '根目錄' },
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}
]
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '*',
redirect: '/404',
hidden: true
}, // ....
]
export default new Router({
mode: 'history', // 後端支援可開
scrollBehavior: () => ({ y: 0 }),
routes: StaricRouterMap
})
複製程式碼
與後端同學定製路由結構 (以下為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'// 獲取元件的方法
function addRouter(routerlist) {
routerlist.forEach(e => {
// 刪除無用屬性
delete e.code
delete e.sort
delete e.generatemenu
delete e.description
delete e.permName
delete e.id
delete e.parentId
e.path = e.url
delete e.url
e.component = _import(e.name) // 動態匹配元件
if (e.redirect === '') {
delete e.redirect
}
if (e.icon !== '' && e.title !== '') { // 配置 選單標題 與 圖示
e.meta = {
title: e.title,
icon: e.icon
}
}
if (e.title !== '' && e.icon === '') {
e.meta = {
title: e.title
}
}
delete e.icon
delete e.title
if (e.children != null) {
// 存在子路由就遞迴
addRouter(e.children)
}
})
return routerlist
}
export { addRouter }
複製程式碼
處理後的路由
我們處理後的路由後面需要與現有的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 NProgress from 'nprogress' // Progress 進度條
import 'nprogress/nprogress.css' // Progress 進度條樣式
import { Message } from 'element-ui'
import { addRouter } from './utils/addRouter'
var getRouter
router.beforeEach((to, from, next) => {
NProgress.start() // 進度條
if (localStorage.getItem('login_static') === '1') {
if (to.path === '/login') {
Message('您已登入,如需切換使用者請退出重新登入')
next({ path: '/' })
}
if (!getRouter) {
if (getRouterList('router')) {
// 路由資訊存在 說明請求階段以及完成 直接解析路由資訊
getRouter = getRouterList('router') // 拿到路由
gotoRouter(to, next)
} else {
// localStorage不存在路由資訊 這需要 請求路由資訊 並解析路由
setRouterList(to, next) // 儲存路由到localStorage
}
} else {
// getRouter變數存在 說明路由資訊存在 直接通過
next()
}
} else {
if (to.path === '/login') {
next()
} else {
next(`/login`)
}
}
})
router.afterEach(() => {
NProgress.done() // 結束Progress
})
function gotoRouter(to, next) {
try {
getRouter = addRouter(getRouter)
router.addRoutes(getRouter) // 動態新增路由
global.antRouter = router.options.routes.concat(getRouter) // 將路由資料傳遞給全域性變數,做側邊欄選單渲染工作
next({ to, replace: true })
} catch (error) {
localStorage.setItem('login_static', 0)
}
}
function setRouterList(to, next) {
store.dispatch('getRouter').then(asyncRouter => { // 請求路由資料
localStorage.setItem('router', JSON.stringify(asyncRouter))
getRouter = getRouterList('router') // 拿到路由
gotoRouter(to, next)
})
}
function getRouterList(name) {
return JSON.parse(localStorage.getItem(name))
}
複製程式碼
修改側邊欄的應用路由地址
需要注意的是 通過 addRoutes合併的路由 不會被this.$router.options.routes
獲取到,所以需要將獲取的路由拼接到this.$router.options.routes
上
最後修改渲染側邊欄部分部分的程式碼
src\views\layout\components\Sidebar\index.vue
computed: {
// ....
routes() {
return global.antRouter // 這裡應該最好使用vuex的全域性變數
},
// ....
}
複製程式碼
這樣就實現了動態渲染後端傳遞的路由,,感覺還是用可以優化的地方,歡迎指正