背景: 公司打算作一个(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的全局变量 }, // .... } 复制代码