Vue中后台鉴权的另一种思路 - 动态路由的实现与优化_weixin_33858336的博客-程序员秘密

技术标签: ViewUI  前端  后端  javascript  

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

前言

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

现在完全优化了之前的实现方法,代码量减少很多,逻辑更加简单,同时也更加稳定

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

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

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

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

实现思路

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

相比较之前使用localStorage存储登录状态,现在把登录状态交给cookice进行管理

路由信息全部交给Vuex进行管理,不再从localStorage里面走,增加了系统的稳定性

配置基础路由

具体的实现思路

router/router.js

// ......
// 静态路由
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~~

转载于:https://juejin.im/post/5caeb3756fb9a068967791b3

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33858336/article/details/91434777

智能推荐

ASP.NET 2.0 AJAX中Webservice调用方法示例_aome1470的博客-程序员秘密

ASP.NET 2.0 AJAX中能够在客户端js中很方便地调用服务器Webservice,以下为一些调用的示例。笔者安装的ASP.NET 2.0 AJAX版本为AJAX November CTP。三个示例分别为:1 带参数的WS方法2不带参数的WS方法3参数类型为DataTable的WS方法一、WebMethod注意要点:1 WebMethod类需要添加命名空间 Micro...

SDUT-3398 数据结构实验之排序一:一趟快排(水题)_Leslie_Blog的博客-程序员秘密

数据结构实验之排序一:一趟快排Time Limit: 1000MS Memory Limit: 65536KBSubmit StatisticProblem Description给定N个长整型范围内的整数,要求输出以给定数据中第一个数为枢轴进行一趟快速排序之后的结果。 Input连续输入多组数据,每组输入数据第一行给出正整数N(N < = 10^5),随后给出N个长整型范围内的整数,数字间...

swing 按钮字体字体_装饰字体_culi4814的博客-程序员秘密

swing 按钮字体字体 This is the last in the series of font categories. We’ve looked at Old Style, Modern, Slab Serif, Sans Serif, Script fonts and their characteristics. We’re going to finish up today with a...

一个比较完整的Inno Setup 安装脚本_innosetup美化界面脚本_小兵qwer的博客-程序员秘密

[Setup]; 注: AppId的值为单独标识该应用程序。; 不要为其他安装程序使用相同的AppId值。; (生成新的GUID,点击 工具|在IDE中生成GUID。)AppId={{A9861883-31C5-4324-BD9A-DC3271EEB675};程序名AppName=ISsample;版本号AppVerName=ISsample 1.0.0.0;发

切换Composer镜像源_beekimlin的博客-程序员秘密

Composer官方网址 https://packagist.org/创建项目并载入composer镜像# test2为项目名 5.4为laravel版本composer create-project --prefer-dist laravel/laravel test2 "5.4.*"更新composer自身版本composer selfupdate更新composer的所有...

随便推点

oracle: jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 无效的列类型: 1111..._weixin_30364147的博客-程序员秘密

https://www.cnblogs.com/mmlw/p/5808072.htmlorg.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: Parame...

react的高级技术点总结(一)_react 高级技术_奇喑的博客-程序员秘密

文章目录children propreact组件的生命周期方法componentDidMount()componentDidUpdate()componentWillUnmount()小结结语children prop我们知道在子组件通过props来获取父组件提供的数据,通过上篇博客的内容(react之jsx语法)我们知道props可以是一些很常见的基本数据类型。如果我们想在父组件给子组件传递一个动态组件,这个组件也可以直接作为props的一部分,但是往往不推荐这种做法,因为react提供了一种语法来满

CCBPM 常用API接口说明_ccbpm变量_暮回首,灯火阑珊处的博客-程序员秘密

接口中的参数说明:Fk_flow:流程编号userNo、BP.Web.WebUser.No:登录帐号Fk_node:节点编号Workid:工作IDFID:父流程ID1.系统登录a) 前台登录:BP.WF.Dev2Interface.Port_Login(userNo);b) 登录流程设计器:/WF/Admin/XAP/Designer.aspxc)

关于生产者消费者模式的C#实现_c#生产者消费者模式_夏目知秋的博客-程序员秘密

今天是圣诞节,大家 Merry Chrismas~以前都是在C++项目中写界面,现在接触了C#感觉比MFC和QT好用多了,决定以后除了特殊要求外都用C#开发:)。记录一下用C#实现生产者消费者模式吧。先介绍一下这个模式,简而言之就是生产者(可能有数个)生产东西,消费者(可能有数个)消费前面生产的东西。举个生活中的例子就是苹果有好几个厂家(生产者)生产iphone,线下线上的购买者(消费者)通过...

Webpack踩坑集_immocha的博客-程序员秘密

Module not found: Error: Can't resolve 'css-loader'今天用到了less文件,安装了less-loader,配置完成后运行,就开始报错,先是:Module not found: Error: Can’t resolve ‘style-loader’,于是重新装style-loader,然后:Module not found: Error: Ca...

Android传递对象数据的两种方式(Serializable和Parcelable)_android serializable 传递_北极熊的微笑的博客-程序员秘密

Android中传递对象数据的方式有两种:Serializable和Parcelable。Serializable方式:通过将对象进行序列化来进行传输。Parcelable方式:通过将对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,从而达到传输的目的。备注:传输效率上来说,Parcelable方式优于Serializable方式。具体步骤如下所示:(一)Ser...

推荐文章

热门文章

相关标签