Vue路由權限

在做後臺管理系統的時候,一般都會遇見路由權限的問題。

大家可以先體驗一下最終的例子 authority vue Router ,例子項目地址 authorityRouter。在例子的登錄頁面中,通過選擇不同的用戶類型來模擬不同的用戶賬號登錄的情況,通過不同的用戶類型登錄後臺的時候,可以看到左側的 menu 菜單是不同的,只有當有權限的時候纔可以進行查看,並且當手動輸入的時候都會直接到404頁面。

下面我就圍繞這個例子說一下實現原理:

路由導航守衛beforeEach

src/permission.js

import Vue from 'vue'
import router from './router'
import store from './store'

// 路由白名單,允許未登錄的時候可以查看的路由
const whiteList = ['/login']

router.beforeEach(async (to, from, next) => {
  // 設置文章標題
  if (to.meta && to.meta.title) {
    document.title = to.meta.title
  }
  // 獲取cookies
  // 有cookies的時候就默認登錄
  let authorityRouterType = Vue.$cookies.get('authorityRouterType')
  if (authorityRouterType && authorityRouterType * 1 >= 0) {
    authorityRouterType = authorityRouterType * 1
    store.commit('setLoginFlag', true)
    store.commit('setAuthorityType', authorityRouterType)
    // 處理的路由信息
    const asyncRoutes = !!store.state.permission.asyncRoutes.length
    if (asyncRoutes) {
      next()
    } else {
      // 當路由信息不存在的時候進行請求
      try {
        // 根據權限得到可用的路由信息
        const accessRoutes = await store.dispatch('permission/getRouter', authorityRouterType)
        router.addRoutes(accessRoutes)
        // set the replace: true, so the navigation will not leave a history record
        next({ ...to, replace: true })
      } catch (error) {
        Vue.prototype.$message.error('獲取路由失敗')
        if (!whiteList.includes(to.path)) {
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  } else {
    // 當未登錄的時候,當前路由在白名單中直接 next ,否則跳轉回登錄頁面,並攜帶路由path
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

此文件會被引入 main.js 進行執行。

store 信息處理

src/store/module/permission.js

import Vue from 'vue'
import { resetRouter } from '@/router/index'
import { formatAsyncRoute, asyncRoutesBase } from '@/router/dynamicRoutes/index'

/**
 * Use meta.role to determine if the current user has permission
 */
function hasPermission (role, route) {
  if (route.meta && route.meta.roles) {
    return route.meta.roles.includes(role)
  }
}

/**
 * 對路由進行篩選
 */
export function filterAsyncRoutes (routes, role) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(role, tmp)) {
      if (tmp.children && tmp.children.length > 0) {
        tmp.children = filterAsyncRoutes(tmp.children, role)
      } else {
        tmp.children = null
      }
      res.push(tmp)
    } else if (tmp.meta && route.meta.base) {
      res.push(tmp)
    }
  })
  return res
}

export default {
  namespaced: true,
  state: {
    asyncRoutes: [],
    accessedRoutes: []
  },
  mutations: {
    setAsyncRoutes (state, asyncRoutes) {
      asyncRoutes = [asyncRoutes]
      formatAsyncRoute(asyncRoutes)
      // 將基礎路由添加進去
      state.asyncRoutes = asyncRoutes.concat(asyncRoutesBase)
    },
    setAccessedRoutes (state, accessedRoutes) {
      state.accessedRoutes = accessedRoutes
    },
    resetAsyncRoutes (state) {
      state.asyncRoutes = []
    }
  },
  actions: {
    async getRouter ({ commit, state }, type) {
      // 根據用戶類型獲取路由信息
      const res = await Vue.axios.post('/api/authorityRouter/getRouter', {
        type
      })
      if (res.status && res.status === 200) {
        if (res.data.isok) {
          commit('setAsyncRoutes', res.data.data)
          return new Promise(resolve => {
            let accessedRoutes
            // 這裏 type = 0 表示管理員
            if (type === 0) {
              accessedRoutes = state.asyncRoutes || {}
            } else {
              accessedRoutes = filterAsyncRoutes(state.asyncRoutes, type)
            }
            commit('setAccessedRoutes', accessedRoutes)
            resolve(accessedRoutes)
          })
        } else {
          Vue.prototype.$message.error('獲取路由失敗')
        }
      } else {
        Vue.prototype.$message.error('網絡出錯,請重試')
      }
    },
    logout ({ commit, state }) {
      // 退出登錄
      resetRouter()
      commit('resetAsyncRoutes')
      commit('setAccessedRoutes', [])
    }
  }
}

router 路由

src/router/dynamicRoutes/asyncRoutesMap.js 將所有路由 import 待用

/**
* 該組件集合會配合後臺返回的動態路由表,匹配有角色權限的 component
*/
export default {
  Admin: () => import('@/views/Admin.vue'),
  AdminBase: () => import('@/views/AdminBase.vue'),
  AdminVip: () => import('@/views/AdminVip.vue'),
  AdminAdmin: () => import('@/views/AdminAdmin.vue')
}

src/router/dynamicRoutes/index.js

import asyncRoutesMap from './asyncRoutesMap'
const Page404 = () => import('@/views/Page404.vue')

// 基礎的動態路由,帶有通配符 * 的路由應該始終放置在路由表的最後面,會拼接到驗權生成的動態路由後面
const asyncRoutesBase = [
  {
    path: '/404',
    name: 'Page404',
    component: Page404,
    meta: {
      base: true,
      title: '404',
      requiresAuth: false
    }
  },
  {
    path: '*',
    redirect: '/404',
    hidden: true,
    meta: {
      base: true,
      title: '404',
      requiresAuth: false
    }
  }
]

// 對後臺返回的動態路由 component 屬性實體化工具函數
const formatAsyncRoute = (asyncRoutes) => {
  asyncRoutes.forEach(asyncRoute => {
    if (asyncRoute.component) {
      if (asyncRoutesMap[asyncRoute.component]) {
        asyncRoute.component = asyncRoutesMap[asyncRoute.component]
      } else {
        delete asyncRoute.component
      }
    }
    if (asyncRoute.children) {
      formatAsyncRoute(asyncRoute.children)
    }
  })
}

export {
  asyncRoutesMap,
  formatAsyncRoute,
  asyncRoutesBase
}

src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
const Login = () => import('../views/Login.vue')

Vue.use(VueRouter)

// 公共路由,和路由白名單裏的路由信息一致
const publicRoutes = [
  {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: {
      title: '登錄',
      requiresAuth: false
    }
  }
]

// 生成信息的公共路由
const createRouter = () => new VueRouter({
  routes: publicRoutes,
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
function resetRouter () {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router
export {
  publicRoutes,
  resetRouter
}

登錄信息處理

src/view/Login.vue

async login () {
  // 模擬登錄
  const res = await this.$axios.post('/api/authorityRouter/login', this.form)
  if (res.status && res.status === 200) {
    if (res.data.isok) {
      this.$store.commit('setLoginFlag', true)
      this.$store.commit('setAuthorityType', this.form.type)
      this.$cookies.set('authorityRouterType', this.form.type, { expires: 7, path: '' })
      // 重置router
      resetRouter()
      // 根據權限得到可用的路由信息
      const accessRoutes = await this.$store.dispatch('permission/getRouter', this.form.type)
      router.addRoutes(accessRoutes)

      this.$nextTick(() => {
        // 處理從其他頁面頁面跳往登錄頁面的情況
        if (this.$route.query.redirect) {
          this.$router.push(this.$route.query.redirect)
        } else {
          this.$router.push('/')
        }
      })
    } else {
      this.$message.error('登錄失敗')
    }
  } else {
    this.$message.error('網絡出錯,請重試')
  }
}

menu 菜單信息處理

src/view/Admin.vue

...mapState('permission', ['asyncRoutes', 'accessedRoutes']),
menuRoutes () {
  if (this.accessedRoutes.length) {
    // 這裏請根據具體情況定
    return this.accessedRoutes[0].children
  } else {
    return []
  }
}

實際上路由權限也是將所有頁面組件都導入了,只是 router 路由中是否添加對應的路由信息罷了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章