美文网首页
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