vue-element-admin難點

公司的后臺管理系統(tǒng)是以vue-element-admin為模板的,做的過程中,遇到了不少難點,比如登陸鑒權(quán),動態(tài)路由側(cè)邊欄生成,網(wǎng)上查了不少資料 ,自己也踩了不少坑,覺得有必要總結(jié)一下

先說一下關(guān)于路由的處理,vue-element-admin設(shè)置了一個全局路由守衛(wèi),下邊是源碼
router.beforeEach(async(to, from, next) => {
  // 進(jìn)度條啟動
  NProgress.start()
  // 設(shè)置頁面標(biāo)題
  document.title = getPageTitle(to.meta.title)
  // 確定用戶是否已登錄
  const hasToken = getToken()

  // 如果已經(jīng)登錄
  if (hasToken) {
    // 在登錄狀態(tài)下,如果還去訪問'/login'這個路由
    if (to.path === '/login') {
      // 就會重定向到主頁
      next({ path: '/' })
      // 進(jìn)度條完成
      NProgress.done()
    } else {
      // 如果已經(jīng)登錄的狀態(tài)下,訪問的不是'/login'這個路由,那么就要確定登錄用戶是否已通過getInfo獲得其權(quán)限角色
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      // 如果已經(jīng)獲得了權(quán)限角色,就正常訪問要訪問的路由
      if (hasRoles) {
        next()
      } else {
        // 如果沒有獲取權(quán)限角色
        try {
          // 獲取用戶權(quán)限角色
          // 注意:角色必須是對象數(shù)組!例如:['admin']或,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')

          // 基于角色生成可訪問路由圖
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

          // 動態(tài)添加可訪問路由
          router.addRoutes(accessRoutes)

          // 確保addRoutes完整的hack方法
          // 設(shè)置replace:true,這樣導(dǎo)航就不會留下歷史記錄
          next({ ...to, replace: true })
        } catch (error) {
          // 在獲取角色,動態(tài)生成可訪問路由的過程中,如果出現(xiàn)異常就會訪問'/login?redirect=${to.path}'這個路由
          // 其實就是會訪問登錄頁面,然后重新登錄,登錄成功后,直接跳轉(zhuǎn)到'to.path'這個頁面
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* 未登錄狀態(tài)下*/
    if (whiteList.indexOf(to.path) !== -1) {
      // 如果要訪問的路由,是在免登錄的白名單中,那就可以直接訪問
      next()
    } else {
      // 沒有訪問權(quán)限的其他頁將重定向到登錄頁。
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})
大體的意思是,當(dāng)我們訪問任意路由時,vue-element-admin都會通過router.beforeEach這個全局路由守衛(wèi)來攔截,然后從Cookie中獲取token,這里會分為兩種情況
  • token存在
    token存在的情況下,就會根據(jù)用戶角色,生成動態(tài)路由,然后把動態(tài)路由和靜態(tài)路由組合成可訪問路由,然后訪問要訪問的路由,這里會有一個特殊的情況,那就是登錄狀態(tài)下,我們還去訪問/login這個登錄頁面,那么就會重定向到/這個根路由,原因是已經(jīng)登錄了,沒有必要再去登錄一遍
  • token不存在
    token不存在的情況下呢,要判斷 一下我們要訪問的路由是不是在白名單中,如果在,那就直接可以訪問了,如果不在白名單中,就說明需要我們先登錄,那就會跳轉(zhuǎn)到登錄頁面,這個時候,會把我們要訪問的路由,作為參數(shù),跟/login組合到一起,也就是/login?redirect=${to.path}這種形式
另外一個難點就是動態(tài)路由的生成了,我們公司是直接根據(jù)用戶角色動態(tài)返回該角色對應(yīng)的路由表,這跟vue-element-admin源代碼里的還是有些區(qū)別的,vue-element-admin里的思路是下邊這樣的
  1. 創(chuàng)建vue實例的時候?qū)ue-router掛載,但這個時候vue-router掛載一些登錄或者不用權(quán)限的公用的頁面。
  2. 當(dāng)用戶登錄后,獲取用role,將role和路由表每個頁面的需要的權(quán)限作比較,生成最終用戶可訪問的路由表。
    這個路由表由兩部分組成,一部分是router.js里定義的constantRoutes,另外一部分是router.js定義的asyncRoutes的過濾后的結(jié)果
  3. 調(diào)用router.addRoutes(store.getters.addRouters)添加用戶可訪問的路由。
  4. 使用vuex管理路由表,根據(jù)vuex中可訪問的路由渲染側(cè)邊欄組件
具體細(xì)節(jié)如下
 // 先拿到用戶角色權(quán)限
const { roles } = await store.dispatch('user/getInfo')
 // 基于角色生成可訪問路由圖
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
  • 我們看下generateRoutes這個函數(shù)的邏輯
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      // 先聲明一個變量,作為可訪問路由數(shù)組
      let accessedRoutes
      // 判斷登錄用戶的角色數(shù)組是否包含admin,如果包含
      if (roles.includes('admin')) {
        // 直接把異步路由賦值給可訪問路由數(shù)組
        accessedRoutes = asyncRoutes || []
      } else {
        // 如果當(dāng)前登錄用戶的角色數(shù)組,不包含admin,那么就調(diào)用'filterAsyncRoutes'方法
        // 這個方法會根據(jù)用戶角色數(shù)組,結(jié)合asyncRoutes這個異步路由里每一項的角色設(shè)置,來過濾asyncRoutes這個異步路由
        // 舉個例子,登錄用戶的角色是editor,而asyncRoutes里有A和B兩個路由,A路由的meta里定義的roles是admin,而B路由的meta里定義的roles是editor,那么過濾后的asyncRoutes就只包含B路由
        // 最后把過濾后的路由賦值給可訪問路由數(shù)組
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      // 然后調(diào)用SET_ROUTES,把可訪問路由存入vuex來管理
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}
  • 再來看下 filterAsyncRoutes這個函數(shù)的邏輯
// 把定義好的異步路由和登錄用戶的角色數(shù)組作為參數(shù)傳進(jìn)來
export function filterAsyncRoutes(routes, roles) {
  const res = []
  // 遍歷異步路由
  routes.forEach(route => {
    // 淺拷貝異步路由的每一項
    const tmp = { ...route }
    // 調(diào)用hasPermission這個方法,對比登錄用戶的角色和異步路由里的每一個路由的meta里的roles是否有重合
    // 如果有重合,證明用戶有權(quán)限訪問這個路由,那么就把這個路由放入res
    if (hasPermission(roles, tmp)) {
      // 如果該路由可以被登錄用戶訪問,那么就要看一下這個路由是否還有子路由
      if (tmp.children) {
        // 如果有子路由,那么就把子路由也通過filterAsyncRoutes這個函數(shù)來過濾一下,把沒有權(quán)限的路由剔除,有權(quán)限訪問的路由保存下來并更新當(dāng)前路由的子路由
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  // 最后把過濾后的路由,也就是當(dāng)前用戶角色可以訪問的路由的數(shù)組返回
  return res
}
  • hasPermission這個函數(shù)的邏輯就比較簡單了
// 它的參數(shù)是 登錄用戶的角色數(shù)組,和異步路由數(shù)組種的其中一個路由
function hasPermission(roles, route) {
  // 如果異步路由數(shù)組種的其中一個路由包含了meta,并且meta定義了roles
  if (route.meta && route.meta.roles) {
    // 就遍歷登錄用戶的角色數(shù)組,如果路由的meta.roles里定義的角色跟登錄用戶的角色數(shù)組有重合,就返回true
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    // 如果被遍歷到的異步路由數(shù)組種的其中一個路由,沒有定義meta,或者沒有定義meta.roles那么就直接返回true
    // 這種情況,說明該路由是一個通用路由,所有的角色都可以訪問
    return true
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容