后台鉴权 & 动态路由+颗粒化权限

# 后台鉴权 & 动态路由+颗粒化权限

参考教程:

Vue中后台鉴权的另一种方案 - 动态路由 (opens new window)

手摸手,带你用vue撸后台 系列二(登录权限篇) (opens new window)

[TOC]

# 一、动态路由-实现思路

image-20200227113325932

# 1.1 登录完成后的行为

# 1.1.1 路由钩子获取动态路由信息

在全局钩子router.beforeEach中拦截路由。

# 1.1.1.1 正常跳转

判断是否获取了动态路由信息,存在则不获取路由。

# 1.1.1.2 刷新页面

刷新页面肯定会丢失Vuex,所以重新获取动态路由。

# 1.1.2 递归解析后端路由

# 1.1.3 通过addRouter与基本路由信息进行拼接

创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。

# 1.1.4 存储到Vuex,用于侧边栏显示

# 二、动态路由

动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组。

router.addRoutes(routers: Array<RouteConfig>)
1

# 2.1 routes

interface RouteConfig = {
  path: string,
  component?: Component,
  name?: string, // 命名路由
  components?: { [name: string]: Component }, // 命名视图组件
  redirect?: string | Location | Function,
  props?: boolean | Object | Function,
  alias?: string | Array<string>,
  children?: Array<RouteConfig>, // 嵌套路由
  beforeEnter?: (to: Route, from: Route, next: Function) => void,
  meta?: any,

  // 2.6.0+
  caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
  pathToRegexpOptions?: Object // 编译正则的选项
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.2 具体实现

# 2.2.1 转换 - 将后端返回来的值转成vue能识别的路由

function addRouter(routerlist) {
  const router = []
  try {
    routerlist.forEach(e => {
      if (e.path && e.component) {
        let e_new = {
          path: e.path,
          name: e.path,
        }
        if (e.children) {
          const children = addRouter(e.children)
          // 保存权限
          e_new = {
            ...e_new,
            children: children
          }
        }

        if (e.is_show === 2) {
          e_new = {
            ...e_new,
            hidden: true
          }
        } else {
          e_new = {
            ...e_new,
            component: () => import(`@/views/${e.component}`)
          }
        }

        if (e.icon !== '' && e.name !== '') {
          e_new = {
            ...e_new,
            meta: {
              title: e.name,
              icon: e.icon
            }
          }
        } else if (e.name !== '' && e.icon === '') {
          e_new = {
            ...e_new,
            meta: {
              title: e.name
            }
          }
        }
        router.push(e_new)
      }
    })
  } catch (error) {
    console.error(error)
    return []
  }
  return router
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 转换前(后端)
[{
    path: '/pluginMgt',
    component: 'layout/Layout',
    name: '运营分析',
    icon: 'database',
    is_show: 1,
    children: [{
        path: 'index',
        component: 'index',
        name: '账号分析',
        is_show: 1,
        icon: '',
    }]
}]

// 转换后(前端)
[{
    path: '/pluginMgt',
    component: 'layout/Layout',
    name: '运营分析',
    icon: 'database',
    is_show: 1,
    children: [{
        path: 'index',
        component: 'pluginMgt/index',
        name: '账号分析',
        is_show: 1,
        icon: '',
    }]
}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 2.2.2 拼接 - router.addRoutes

let last = [{
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
},{
    path: '*',
    redirect: '/404',
    hidden: true
}]
asyncRouter = asyncRouter.concat(last)
router.addRoutes(asyncRouter) // vue-router提供的addRouter方法进行路由拼接
store.dispatch('setRouterList', asyncRouter) // 存储到vuex
store.commit('set_init', true) // 避免再次发起请求
1
2
3
4
5
6
7
8
9
10
11
12
13
actions: {
    // 动态设置路由 此为设置途径
    setRouterList({
        commit
    }, routerList) {
        commit('set_router', StaticRouterMap.concat(routerList)) // 进行路由拼接并存储
    }
}
1
2
3
4
5
6
7
8
const StaticRouterMap = [{
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    name: 'index',
    children: [{
      path: '/',
      name: 'index',
      component: () => import('@/views/index/index'),
      meta: {
        title: '首页',
        icon: 'home_defualt'
      }
    }]
  },
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 2.2.3 整体流程

if (user.state.init) {
    // 获取了动态路由 data一定true,就无需再次请求 直接放行
    next()
} else {
    // data为false,一定没有获取动态路由,就跳转到获取动态路由的方法
    gotoRouter(to, next)
}

function gotoRouter(to, next) {
  getUserMenu(store.getters.token) // 获取动态路由的方法
    .then(res => {
      const asyncRouter = addRouter(res.data) // 进行递归解析
      return asyncRouter
    })
    .then(asyncRouter => {
      let last = [{
          path: '/404',
          component: () => import('@/views/404'),
          hidden: true
        },
        {
          path: '*',
          redirect: '/404',
          hidden: true
        }
      ]
      asyncRouter = asyncRouter.concat(last)
      router.addRoutes(asyncRouter) // vue-router提供的addRouter方法进行路由拼接
      console.log(asyncRouter)
      store.dispatch('setRouterList', asyncRouter) // 存储到vuex
      store.commit('set_init', true)
      next({
        ...to,
        replace: true
      }) // hack方法 确保addRoutes已完成
    })
    .catch(e => {
      console.log(e)
      removeToken()
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 三、颗粒化权限

参考教程:Vue中后台鉴权 - 自定义指令实现权限颗粒化 (opens new window)

# 3.1 实现思路

/**
 * @export 自定义指令
 */
export function directive() {
  Vue.directive('permit', {
    bind(el, binding) {
        // 一行三目运算符就可
        // return store.getters.roleTest.includes(binding.value)
      !store.getters.roleTest.includes(binding.value) ? el.parentNode.removeChild(el) : {}
    }
  })
}

// roleTest: [1]
// v-permit="1"
// <el-button v-permit="1" type="primary">增加</el-button> 显示
// <el-button v-permit="2" type="primary">增加</el-button> 不显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
actions: {
    // 存储颗粒化权限
    setroles({
      commit
    }, roleList) {
      commit('SET_ROLES', roleList)
    }
}
1
2
3
4
5
6
7
8
// 需要引入到main.js
import { directive } from './utils/directive'
// ....
directive()
// ....
1
2
3
4
5