美文网首页
VUEX 管理后台角色权限

VUEX 管理后台角色权限

作者: Primers | 来源:发表于2019-12-29 21:38 被阅读0次

    这次的项目是一个基于另一个项目改造的,后端可以因为架构类似只要修改就行,而前端就是完全的重造。就有了这个第一次自己搭建前端环境的经历了。

    项目的管理后台有个角色权限系统,不同的角色能拥有不同的管理权限(权限以页面的形式呈现,拥有权限就可以访问对应页面)。
    所以很明显,前端的 Router 是动态的,不同角色登陆进来的路由表不同,左侧的菜单也不同。

    首先这是思路

    这里我采用的是路由数据完全又后端提供的方式。

    登陆成功,返回角色 —— 通过角色获取权限表 —— 用权限表构建路由表 —— 动态添加路由

    project src
    ├── router
    |   ├── index.js
    |   └── router.js
    ├── store
        ├── index.js
        └── modules
            └── routerStore.html
    



    router / router.js 中先定义一些路由,用于未登陆前的页面访问

    // 获取权限后一些默认共有的页面
    export const routerMap = [
      {
        path: '/index',
        name: 'index',
        meta: { title: '后台主页'  },
        component: () => import('pages/index'),
        children: [] // 项目通过子路由形式显示页面,后面生成的路由都放到这里面
      },
    ]
    // 无权限路由表,未登陆前可用的页面,如登录、注册、404等
    export const publicRoutes = [
      {
        path: '/login',
        name: 'login',
        meta: { title: '登录' },
        component: () => import('pages/login')
      },{
        path: '*',  // 数组中前方所有路由匹配不上,则匹配该条
        name: 'error',
        mata: { title: '出错拉' },
        component: () => import('pages/error')
      }
    ]
    



    router / index.js 中是路由构建和路由守卫,由此决定何时获取路由表。

    import Vue from 'vue'
    import Router from 'vue-router'
    import store from '@/store'
    import { publicRoutes } from './router'
    
    Vue.use(Router)
    const router = new Router({ routes: publicRoutes }) // 先使用无权限路由表生成路由
    
    // 路由守卫,每次跳转后渲染页面前执行
    router.beforeEach((to, from, next) => {
      // 从 cookie 中获取 token 判断是否需要登录
      const needLogin = store.getters.getCookie('token') ? false : true
      if (needLogin) return to.name === 'login' ? next() : next({name: 'login'})
      
      // 从 store 中获取值判断是否已经获得了权限列表
      const hasGetRules = store.state.routerStore.hasGetRules // 返回 ture 或 false
      if (hasGetRules) return next()
    
      // 前面未额能跳转,则进行权限获取
      store.dispatch('authorization').then(rules => { // 后台验证角色,返回 rules 权限表
        store.dispatch('concatRoutes', rules).then(routers => { // 对权限表进行处理,返回路由表
          router.matcher = new Router().matcher; // 防止重复的路由项
          router.addRoutes(routers)
          // 路由添加完毕,跳转到默认页面
          next({name: 'index'})
        }).catch(() => {
          next({ name: 'error' })
        })
      }).catch(() => {
        store.commit('storeCookier', {opr: 'del'}) // 身份验证失败,清空 token 等信息
        next({name: 'login'})
      })
    })
    
    export default router
    



    store / index.js 中定义一些方法和缓存的储存

    import Vue from 'vue'
    import Vuex from 'vuex'
    import routerStore from './modules/routerStore'
    
    Vue.use(Vuex)
    
    let vue = Vue.prototype
    const store = new Vuex.Store({
      state: {
        token: $tools.handleCookie.get('token'), // 自己定义的方法,从cookie中获值
        user_role: $tools.handleCookie.get('user_role')
      },
      modules: { routerStore },
      mutations: {
        // 设置cookie {操作类型, 名称, 设置时的参数}
        storeCookier(state, { argument }) { }
      },
      getters: {
        // 获取cookie
        getCookie: (state) => (name) => { },
      },
      actions: {
        // 身份验证,获取权限表
        authorization({ getters }) {
          // 请求是异步的所以使用 Promise
          return new Promise((resolve, reject) => {
            let params = { user_role }
            vue.$http
              .get(url, params)
              .then(res => { resolve(res) })
              .catch(res => { reject(res) })
          })
        }
      }
    })
    
    export default store
    



    store / modules / routerStore.js 将获取到的权限表变成路由表并储存起来

    import { publicRoutes, routerMap } from '@/router/router'
    
    // 构建菜单表方法,这里顺便把菜单那列表也保存下来
    const generaMenu = (rules) => {
      let list = []
      rules.sort((a, b) => (a.index + '') >= (b.index + '') ? 1 : -1) // 排序,小到大
      rules.forEach(item => {
        let obj = { title, icon, path }
        if (item.children) obj.children = generaMenu(item.children) // 有子项
        list.push(obj)
      })
      return list
    }
    
    // 构建路由表方法,把 rules 中每项的属性重组放入 routesList 中
    const generaRoutesList = (routesList, rules) => {
      rules.forEach(item => {
        if (item.router_path) { // 有路径就添加进路由
          let obj = {
            path: item.path,
            name: item.name,
            meta: {
              title: item.title
            },
            component: resolve => require([ item.file_path ], resolve )
          }
          routesList.push(obj)
        }
        if (item.children) generaRoutesList(routesList, item.children) // 继续遍历添加子级入路由
      })
    }
    
    const state = {
      hasGetRules: false, // 默认定义为未获取权限列表
    }
    
    const mutations = {
      // 最后拼接和处理路由表,以及储存数据
      CONCAT_ROUTES (state, {routesList, menuList}) {
        // 处理包裹页面的路由
        let indexPage = routerMap[0]
        indexPage.children = routesList // 将路由表插入到子路由的位置
        indexPage.redirect = indexPage.path + '/' + routesList[0].path // 设置默认显示页面为路由表第一个
        
        state.routers = routerMap.concat(publicRoutes) // 重点:前面 404 的 path: '*' 必须拼到最后
        state.menu = menuList
        state.hasGetRules = true
      }
    }
    
    const actions = {
      concatRoutes ({ commit }, rules) {
        return new Promise((resolve, reject) => {
          try {
            // 构造路由
            let routesList = []
            generaRoutesList(routesList, rules)
            // 构造菜单
            let menuList = generaMenu(rules)
            // 拼接和保存路由并返回
            commit('CONCAT_ROUTES', { routesList, menuList })
            resolve(state.routers) 
          } catch (err) { reject(err) }
        })
      }
    }
    
    export default {
      state,
      mutations,
      actions
    }
    

    流程算大功告成了,这里我说一些重点和坑

    • 重构路由项时( generaRoutesList )

    每个子项的 component 引入需用 component: resolve => require([ item.file_path ], resolve ) 否则运行时 webpack 报错,模块引用失败

    • 下面两点放一起 ,有关联
    • 拼接新路由和公共路由时( CONCAT_ROUTES )

    必须把公共路由拼在新路由的下方,newRouter.concat(publicRoutes),一方面是把路由补充完整,一方面是公共路由中 404 的 { path: '*' } 使其必须在最后,如果你没有这个 path 就没有这个顾虑。

    • 添加动态路由时( dispatch('concatRoutes', rules) )、

    使用 router.addRoutes(newRouter) 是这个能动态添加路由的关键,但这个方法只是添加新的路由,不会清除一开始初始化时放进去的公共路由,所以添加前先使用router.matcher = new Router().matcher重置一下,否则会报警告,有重复路由项。当然,如果你没有 { path: '*' } 的路由项,你根本不需要拼接,和重置matcher

    • 坑,当上次与下次路由跳转的页面路径一样是,会报错。
    // router / index.js
    import Router from 'vue-router'
    // 处理一下 Push 方法取消报错,不推荐。
    const routerPush = Router.prototype.push
    Router.prototype.push = function (location) {
      return routerPush.call(this, location).catch(error=> error)
    }
    

    相关文章

      网友评论

          本文标题:VUEX 管理后台角色权限

          本文链接:https://www.haomeiwen.com/subject/nnxuoctx.html