vue-router+vuex实现加载动态路由和菜单-爱咖啡-51CTO博客

标签: | 发表时间:2019-07-09 10:31 | 作者:
出处:https://blog.51cto.com

前言

动态路由加载和动态菜单渲染的应用在后端权限控制中十分常见,后端只要加载权限路由进行渲染返回到浏览器就可以。在前后端分离中,权限控制动态路由和动态菜单也是一个非常常见的问题。其实我们最最理想的效果是什么呢?
我们访问一个应用,在登录之前有哪些路由是一定要加载的呢?你看我总结如下,你看下是不是这些:

      1.登录路由 (登录功能路由)
2.系统路由(系统消息路由,比如欢迎界面,404,error等的路由)

但是在vue中,一旦实例化,就必须初始化路由,但这个时候你还没有登录,没有获取你的权限路由呀,如果加载全部路由,那么在浏览器上输入路由你就可以访问(这个问题可以使用router.beforeEach钩子进行权限鉴定解决),那么在前后端分离的开发项目中,vue是如何实现动态路由加载实现权限控制的呢?这就是我们这篇文章要写的内容。

我们写过后台渲染都知道怎么去实现,那么放到vue中如何去实现呢?我们先罗列几个问题进行思考,如下

      1.vue中路由是如何初始化,放入到vue实例中的?
2.vue中提供了什么实现动态路由加载呢?

我们先顺着这两个问题进行思考,并且顺着这两个问题,我们进行对应方案解决,这个过程中会会出现很多新的问题,我们也针对新问题出对应方案,并且进行优化。

路由初始化

路由初始化发生在什么时候呢?我们可以看主入口文件main.js,下面是我贴出的我的一个项目案例:

      import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

import '@/styles/index.scss' // global css

import App from './App'
import router from './router'
import store from './store'

import i18n from './lang' // Internationalization
import './icons' // icon
import './errorLog' // error log
import './permission' // permission control
import './mock' // simulation data

import * as filters from './filters' // global filters

Vue.use(Element, {
  size: 'medium', // set element-ui default size
  i18n: (key, value) => i18n.t(key, value)
})

// register global utility filters.
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.config.productionTip = false

// vue实例化就已经把router初始化了
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

通过上面的主入口文件,我们就知道,这个路由初始胡就发生在vue实例化时。这个也很好理解如果你没有初始化路由,那么你就默认只能进入到主窗口,那么接下来主窗口中你没有路由你怎么跳转?程序也不知道你有哪些地方可以跳转呀,路由都是需要先注册到实例中,实例才能定位到相应的视图。从中我们知道, 路由初始化发生在vue实例化时

那么这个时候我们接着我们想要的权限控制目标走:程序一开始,只注册登录路由、系统信息路由(欢迎页面,404路由,error路由),我们称这些为静态路由,登录后我们通过接口获取权限拿到了菜单,这个时候需要进行添加动态路由,把这些菜单信息注册为路由,我们称这些为动态路由。那么vue实例化时,vue-router就已经被初始化,那么我们是不是能够通过类似于往router实例里面添加路由项的方式进行注册路由呢?我们可以查阅文档,也可以查看vue-router源码,有一个叫做addRoutes的方法进行动态注册路由信息,路由对象其实就是一个路由数组,我们通过addRoutes就可以进行动态注册路由,这个跟那个数组中extend功能类似的。

所以说道这里我们知道可以 通过addRoutes进行动态路由注册。好,那么我们就顺着这个思路走下去。

在登录模块中,登录成功后,我们通过api获取后台权限菜单,然后注册路由。代码如下:

      // 登录页登录方法
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid && this.isSuccess) {
          this.loading = true
          this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
           // 在这个时候进行获取后台权限及菜单
            this.$store.dispatch('getMenus', this.loginForm.name).then((res) => {
             // 把这个菜单信息注册为路由信息
              this.$router.addRoutes(menuitems)
            })
            this.loading = false
           // 除了登录路由、和系统消息路由,这个跟路由是一个欢迎路由,是静态路由
            this.$router.push({ path: '/' })
          }).catch(() => {
            this.$message.error('登陆失败,请检查用户名或密码是否正确')
            this.loading = false
          })
        } else {
          if (!this.isSuccess) {
            this.$message.error('请拉滑动条')
          }
          console.log('error submit!!')
          return false
        }
      })
  }

// 登录方法计算属性
computed: { 
   ...mapGetters([ 
    'menuitems', 
   ]) 
  },

总结一下:
登录成功以后(持久化token),调用获取权限菜单(保存在store里面),这个时候就完成了登录后动态初始化权限菜单的功能。那么这里面所有的路由就是当前用户可访问的菜单,就实现了我们的目标效果。但是呢,store存储权限菜单会有个问题,一旦刷新里面的值就刷掉了,那么这个时候就重新实例化的时候就会跳到404路由中,菜单信息也没有了,那如何解决这个刷新时的问题呢?

我们先分析一下思路:

      1.初始化vue实例时,初始化router,包括所有的静态路由。
2.全局钩子检查token是否有效?
        a.如果有效,则通过token获取用户信息保存到store中,根据用户信息获取权限菜单保存到store中,
        动态注册权限菜单的路由信息;
        b.如果token无效,重新定位到静态登录路由进行登录.
3.登录模块中,登录成功后获取用户信息保存到store中,将token保存到store中并持久化到本地,
获取权限菜单保存到store中,动态注册权限菜单的路由信息
4.动态加载完路由后,直接跳到欢迎界面的静态路由
5.一旦页面刷新,那么token就会从store中清除,token失效,那么就会去获得持久化在本地的token
,重新去获取用户信息,权限菜单,重新动态注册路由。
6.token持久化在本地也是有时间限制的,假设token有效期为一周,一旦过了有效期,那么会走2的b情况。

那么上面的思路就是动态加载权限菜单路由信息的简述,整个的环路就通了,刷新问题就解决了。

代码如下:

      import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookie

NProgress.configure({ showSpinner: false })// NProgress Configuration

// 权限判断
function hasPermission(roles, permissionRoles) {
  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

const whiteList = ['/login', '/authredirect']// no redirect whitelist

// 全局钩子
router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  // 如果有token
  if (getToken()) { // determine if there has token
    // 登录后进入登录页
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
    } else {
      // 当进入非登录页时,需要进行权限校验
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
           const roles = res.data.data.roles // note: roles must be a array! such as: ['editor','develop']
           store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
             router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: 
           })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

备注:根据模块独立性,我把登录中获取权限列表去掉,都放置在全局钩子中,把上面的代码直接引入到主入口文件main.js中。

另外这里采用vuex进行状态管理,所以从新捋一下思路:

      1.vue实例化,初始化静态路由
2.全局钩子进行检查:
    a.token有效
          -如果当前跳转路由是登录路由,直接进入根路由/
            -如果跳转路由非登录路由,则需要进行权限校验,如果用户信息和权限菜单没拉取,
            则进行拉取后将权限菜单动态注册到router中,进行权限判断,如果有用户信息和权限菜单信息,
            则直接进行权限判断。
    b.token无效
          -如果在白名单中,则直接进入
            -进入到登录页

3.全局状态管理采用vuex

到这里我们就已经完成了vue-router+vuex动态注册路由控制权限的方式就说完了,这里我留个思考题给大家:现在根据上面的方式我再引入一个产品实体,(用户 - 产品 - 菜单 ), 用户可以有多个产品权限,每个产品有公用的菜单,也有各产品定制化的菜单,那么这个时候我在前端如果做好权限校验呢?要求:当前用户当前产品的权限菜单才可被访问。

相关 [vue router vuex] 推荐:

ssr vuejs/vue-hackernews-2.0: HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering

- -
This is a demo primarily aimed at explaining how to build a server-side rendered Vue app, as a companion to our SSR documentation. #install dependenciesnpm install#or yarn#serve in dev mode, with hot reload at localhost:8080npm run dev#build for productionnpm run build#serve in production modenpm start.

vue-router+vuex实现加载动态路由和菜单-爱咖啡-51CTO博客

- -
动态路由加载和动态菜单渲染的应用在后端权限控制中十分常见,后端只要加载权限路由进行渲染返回到浏览器就可以. 在前后端分离中,权限控制动态路由和动态菜单也是一个非常常见的问题. 其实我们最最理想的效果是什么呢. 我们访问一个应用,在登录之前有哪些路由是一定要加载的呢. 你看我总结如下,你看下是不是这些:.

Vuex mapGetters,mapActions - 哈哈呵h - 博客园

- -
在 src 目录下创建 store.js 文件,并在 main.js 文件中导入并配置. //引入 vuex 并 use. //导入 store 对象. //配置 store 选项,指定为 store 对象,会自动将 store 对象注入到所有子组件中,在子组件中通过 this.$store 访问该 store 对象.

Vue 移动端框架

- - IT瘾-jianshu
vonic 一个基于 vue.js 和 ionic 样式的 UI 框架,用于快速构建移动端单页应用,很简约. 中文文档| github地址| 在线预览. vux 基于WeUI和Vue(2.x)开发的移动端UI组件库. 基于webpack+vue-loader+vux可以快速开发移动端页面,配合vux-loader方便你在WeUI的基础上定制需要的样式.

vue路由权限校验

- - 掘金前端
做后台系统的时候,难免会有用户权限的判断. admin可以查看全部菜单,user只能查看部分菜单. 一开始接触这个需求的时候,完全是纯前端做的. 在配置路由的时候,加一个roles的属性,通过判断用户的roles是否与路由的roles属性相匹配来作为显示隐藏的依据. // 过滤路由 menuList-菜单 roles-用户角色 const checkMenuList = (menuList, roles) => { for (let i = 0; i < menuList.length; i++) {.

Advanced Onion Router v0.3.0.20 最新中文图文教程

- - 细节的力量
Advanced Onion Router( AdvOR)是由hexhub开发的一个 tor实用的组合工具,经过作者不断完善,现在的AdvOR是目前tor组合工具中很好的工具之一, 美博园一直跟踪其最新版发布,本文是美博园根据多年使用结合最新版写出的突出主要功能的详细中文图文教程,AdvOR软件中自带有详细的英文教程:AdvOR\Help\AdvOR.html.

家居 Router 安全性瓦解,隨時被 Hack [ F-Secure 小貼士 ]

- - UNWIRE.HK 流動科技生活
記得早幾年逛街時,偶爾也能成功從一些完全沒有經過加密的私人 Wi-Fi 熱點上偷線上網,現在街上已甚少完全沒有加密的私人 Wi-Fi 熱點. 這個現象,代表大眾對 Wi-Fi 保安的認知性已經較過去大幅提高. 不過,如果認為在 Wi-Fi 網絡上套用加密設定後,就能有效杜絕黑客入侵的話,這樣絕對是對 Wi-Fi 保安認知上的謬誤.

方便易用的路由器及网络管理软件 - X-Router

- - 爱软件
怎样在没有路由器的情况下,通过单台电脑上网将内网所有用户共享上网. “软路由”就可以帮你实现上述功能,. 路由器软件不少,今天推荐一个最方便易用且功能强大的:. X-Router,X-Router不但拥有主流软路由的全部功能,同时拥有强大的. 网络管理功能,通过智能QoS,让你的小宽带带来大福利,免费版足够使用.

GitHub - bailicangdu/vue2-elm: 基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用

- -
初学vue时曾在网上搜索vue的实战项目源码,无奈大部分都是简单的demo,对于深究vue没有太大的帮助,剩下的一些大部分都是像音乐播放器之类的展示型项目,交互没有预期那么复杂. 但我们实际在工作中,经常会遇到有购物车的项目,这类项目因为涉及到money,所以对逻辑严谨度要求高,页面之间交互复杂,又会伴随着登录、注册、用户信息等等,常常会让我们很头疼.

微豆 - Vue 2.0 实现豆瓣 Web App 教程

- - SegmentFault 最新的文章
一个使用 Vue.js 与 Material Design 重构 豆瓣 的项目. 项目网站 http://vdo.ralfz.com/. # 克隆项目到本地 git clone https://github.com/RalfZhang/Vdo.git # 安装依赖 npm install # 在 localhost:8080 启动项目 npm run dev.