背景: 公司打算作一個(cms),最開始前端參照vue-element-admin的思路,驗證了前端鑑權的可能性,大佬寫的代碼思路清奇,值得學習,後來考慮諸多因素,接口安全性 前端鑑權的難度 以及項目的複雜度,最後選擇採用後端渲染動態路由的形式css
本文相對過期 請看Vue中後臺鑑權的另外一種思路 - 動態路由的實現與優化前端
使用的是Vue+Element的後臺管理模板githubvue
思路參考了一下文章git
Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)github
issues/293vuex
基礎的一些思路和Vue 動態路由的實現Vue 動態路由的實現(後臺傳遞路由,前端拿到並生成側邊欄)同樣,核心部分加入了本身的理解element-ui
getRouter
,存在則直接放行 由於路由配置存在getRouter
變量就不存在了,因此 就要在去獲取一次getRouter
上,便於之後使用,減小請求如下爲具體實現思路json
基礎路由爲不登陸也能夠訪問的路由segmentfault
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的全局變量 }, // .... } 複製代碼這樣就實現了動態渲染後端傳遞的路由,,感受仍是用能夠優化的地方,歡迎指正