Vue中后台鉴权的另外一种思路 - 动态路由的实现与优化

借用大佬的一张图,侵权立删javascript

前言

在今年年初在掘金发布了一篇文章记一次Vue动态渲染路由的实现,如今代码通过不断的Reviewcss

如今彻底优化了以前的实现方法,代码量减小不少,逻辑更加简单,同时也更加稳定前端

demo已经部署到github,欢迎体验~~ vue-element-asyncLogin, 你的start是个人动力!vue

鉴权-前端路由 VS 鉴权-动态路由

​ 前端路由鉴权相信只要了解过vue-element-admin的都知道,前端鉴权方案是彻底可行的,思路清晰,难度适中,项目中彻底可使用,那么相对来讲动态路由优点在什么地方呢java

  1. 前端鉴权不够灵活,线上版本每次修改权限页面,都须要从新打包项目
  2. 中小型项目中 前端鉴权明显更加好用,成本更低,程序员们也不用996了(雾),可是对于权限等级不少,而且比较大的项目,维护这一套鉴权路由,毫无疑问是一个大工程,而且面对频繁变动的需求,bug会出现的更加频繁,前端工程师工做量大大增长,这时候彷佛前端鉴权就再也不是好的方案
  3. 动态路由并非回归到刀耕火种的时代,而是一种新的思路,路由配置仍是由前端完成,仅仅将状态交给了后端,不一样角色的路由显示交给后端控制,前端不须要管理路由,最多只须要管理权限颗粒化的问题

实现思路

  1. 路由跳转 先判断是否登陆 未登陆只能访问白名单页面,访问其余页面所有重定向到登陆页面
  2. 登陆行为触发,获取动态路由,递归解析动态路由信息,而且addRouter,同时存储到Vuex,而且记录获取路由的状态
  3. 跳转页面不会获取动态路由,刷新页面从新获取动态路由

相比较以前使用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~~

相关文章
相关标签/搜索