VUEX 管理后台角色权限

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

项目的管理后台有个角色权限系统,不同的角色能拥有不同的管理权限(权限以页面的形式呈现,拥有权限就可以访问对应页面)。
所以很明显,前端的 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)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容