Vue重構項目之權限配置篇

序言

最近項目需求不是不少,想到公司後臺管理項目有些過於臃腫,相幫它減減肥。javascript

奈何君有意,了卻她無情。css

jQ+bootstrapt的非主流內衣、template-web的硬核外套、html+css+js的老實牌秋褲、cdn銘牌的各類包包,一眼看上去還不錯是否是。跟着她回到家,一開門人傻了!......html

若要愛請深愛,若不愛請拜拜。前端

顯然我選擇了後者,我們步入正題哈 next()vue

項目架構-權限配置

分析

通常比較簡單的後臺管理系統就是系統登陸用戶角色較少,維護人員不是不少,可能就admin和editor兩種,這種權限比較簡單,在路由的meta標籤加個role差很少就夠了,舉個栗子java

{
    path: '/dynamic',
    component: Layout,
    name: 'dynamic',
    meta: {
      title: '動態管理',
      icon: 'icon-duihuakuang',
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    children: [
      {
        path: 'dynamicList',
        component: () => import(''),
        name: 'dynamicList',
        meta: { title: '動態列表', noCache: true }
      },
      {
        path: 'rootdynamic',
        component: () => import(''),
        name: 'rootdynamic',
        meta: { title: '機器人動態列表', noCache: true }
      }
    ]
  }
複製代碼

可是我這個有點不同,複雜一點角色分權限等級,並且用戶較多。因此說這樣的顯然沒法實現。react

思考

通常登陸流程是git

獲取token--->拿token獲取用戶信息以及後端返回路由--->前端篩選權限路由--->登陸成功--->動態顯示該用戶的路由github

咱們沒有token,後端有定時任務,過時後端接口都返回403,前端就直接重定向到登陸頁,因此第一步要不要無所謂了,可是在這文裏仍是走正常流程(項目上你們也要靈活應變,不走尋常路才能走遍全部路!)。web

實踐

獲取token

思考:你的數據到底是存儲到本地localStorage仍是用Vuex?(期待你的見解)

我這裏都寫下怎麼選擇取決與我的(不要爲了使用vuex而使用vuex,大多數項目真的不必)

login({ commit }, userInfo) {
    const {
      username,
      password,
      verification
    } = userInfo
    return new Promise((resolve, reject) => {
      login({
        userName: username.trim(),
        passWord: password,
        captcha: verification
      }).then(response => {
        if (response) {
          //localStorage.setItem('info', JSON.stringify(response.data))
          //僞裝有token,你有就把後端給的寫上
          commit('SET_TOKEN', new Date().getTime())
          commit('SET_LEVEL', response.data.level)
          setToken(new Date().getTime())
          resolve(response)
        } else {
          this.$message({
            type: 'error',
            message: response.errMsg
          })
        }
      }).catch(error => {
        reject(error)
      })
    })
  },
複製代碼

拿token獲取用戶信息以及後端返回路由

getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
          //路由信息存本地
        localStorage.setItem('sidebars', JSON.stringify(response.data))
        const data = JSON.parse(localStorage.getItem('info'))
        const {
          roleName,
          userName,
          department
        } = data
        commit('SET_ROLES', roleName)
        commit('SET_NAME', userName)
        commit('SET_AVATAR', '頭像')
        commit('SET_INTRODUCTION', department)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  }
複製代碼

前端篩選權限路由

後端返回的路由是tree類型的數據,顯然不能直接拿過來。

路由.png

既然這樣,本身動手吧。前端router.jsconstantRoutesasyncRoutes兩個路由菜單

  • constantRoutes:每一個用都有的路由,比方說登陸頁、首頁、404...
  • asyncRoutes:全部權限頁面
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: '首頁', icon: 'icon-shouye', affix: true }
      }
    ]
  }
]
export const asyncRoutes = [
]
const router = createRouter()
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
export default router
複製代碼

main.js裏面引入import './permission'

來看看permission.js該怎麼寫

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  // 開始進度條
  NProgress.start()
  // 設置頁面標題
  document.title = getPageTitle(to.meta.title)
  // 肯定用戶是否已登陸
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done() 
    } else {
      // 肯定用戶是否已經過getInfo得到其權限角色
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // 拿到用戶我的信息
          const roles = await store.dispatch('user/getInfo')
          // 權限路由匹配
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // 動態添加路由
          router.addRoutes(accessRoutes)
          // hack方法 確保addRoutes已完成
          // 設置replace:true,這樣導航就不會留下歷史記錄
          next({ ...to, replace: true })
        } catch (error) {
          // 初始化
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免費登陸白名單中,直接進入
      next()
    } else {
      // 其餘沒有訪問權限的頁面將重定向到登陸頁面。
      next(`/login?redirect=${to.path}`)
      // next()
      NProgress.done()
    }
  }
})
router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})
複製代碼

看看generateRoutes這裏的roles其實就是用戶信息,證實拿到了後端路由而後下一步

generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles) {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }  
      /**功能權限點,爲後文埋下伏筆 const data = JSON.parse(localStorage.getItem('sidebars')) const permission = [] data.forEach(element => { if (element.children) { element.children.forEach(item => { item.children.forEach(i => { permission.push(i.menuId) }) }) } }) commit('SET_PERMISSION', permission) */
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
複製代碼

下面詳細看看重點路由篩選,添加動態路由(完整文件見附錄

/** * 遞歸過濾異步路由表 * @param routes asyncRoutes * @param roles */
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = {
      ...route
    }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}
/** * 經過和後端路由菜單比較肯定當前用戶是否有權限 * @param roles * @param route */
function hasPermission(roles, route) {
//拿到後端返回的路由,將菜單名遞歸出來
  const data = JSON.parse(localStorage.getItem('sidebars'))
  const name = []
  data.forEach(element => {
    name.push(element.menuName)
    if (element.children) {
      element.children.forEach(item => {
        name.push(item.menuName)
      })
    }
  })
  //將後端返回的和當前路由對比有的話就經過,進入動態路由無的話就pass
  if (route.meta && route.meta.title) {
    return name.some(item => {
      return route.meta.title === item
    })
  } else {
    return false
  }
}
複製代碼

登陸成功動態顯示該用戶的路由

既然有了頁面權限,理論上應該有功能權限 其實應該還會有功能權限點,就是頁面內他或許不能新增,只能看到列表,這種是頁面內權限點配置,通常採用自定義指令的方式比較方便

權限配置-頁面功能權限點配置

將後端返回的路由中的功能權限點經過遞歸提取出來

首先引入文件

mainjs裏面引入

import directives from '@/utils/directives.js'
Vue.use(directives)
複製代碼

而後編寫邏輯

directives.js裏面封裝下指令

// 頁面功能權限判斷
function checkPermission(el, binding) {
  const { value } = binding
  if (value) {
  //這個是我在篩選路由時順便存下的
    const permissionArr = store.getters && store.getters.permissionArr
    const permissionRoles = Number(value)

    const hasPermission = permissionArr.some(val => {
      return permissionRoles === val
    })
    if (!hasPermission) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  } else {
    throw new Error(`need roles! Like v-permission="['admin','editor']"`)
  }
}
export default (Vue) => {
  Vue.directive('permission', {
    inserted(el, binding) {//初始化調用
      checkPermission(el, binding)
    }
    // update(el, binding) {//這是在刷新的時候調用
    // checkPermission(el, binding)
    // }
  })
}
複製代碼

最後頁面使用

<el-button v-for="(item) in dataSource.tool" :key="item.key" v-permission="item.permission" class="filter-item" type="primary" @click="handleAdd(item.name)" >
{{ item.name }}
</el-button>
複製代碼

結尾

談談架構框架選擇

原本想用react+ant的,可是奈何感受react都是js文件不太對個人胃口, (我的感受都是js文件層次不是很清晰,相對來說vue的熟悉成本較低)

既然有的選,那我們就用Vue

Vue3.0雖然能夠用,可是考慮到不肯定因素,還須要時間去完善它

我們選穩的2.x版本,而後UI庫就Element UI吧。

權限配置到這裏就結束了,有什麼問題你們能夠留言一塊兒討論

如今正在重構頁面coding,也遇到一些問題,高德地圖結合echarts展現問題、角色頁面角色等級問題、二次封裝table組件。

等我攢夠一波坑,再來和你們分享。

Vue重構項目之權限配置篇到此結束,謝謝你的閱讀!!!我們下期見

附錄

vuex裏面的permission部分

import {
  asyncRoutes,
  constantRoutes
} from '@/router'

/** * 經過和後端路由菜單比較肯定當前用戶是否有權限 * @param roles * @param route */
function hasPermission(roles, route) {
  const data = JSON.parse(localStorage.getItem('sidebars'))
  const name = []
  data.forEach(element => {
    name.push(element.menuName)
    if (element.children) {
      element.children.forEach(item => {
        name.push(item.menuName)
      })
    }
  })
  if (route.meta && route.meta.title) {
    return name.some(item => {
      return route.meta.title === item
    })
  } else {
    return false
  }
}

/** * 遞歸過濾異步路由表 * @param routes asyncRoutes * @param roles */
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = {
      ...route
    }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

const state = {
  routes: [],
  addRoutes: [],
  permissionArr: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  },
  SET_PERMISSION: (state, arr) => {
    state.permissionArr = arr
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles) {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      const data = JSON.parse(localStorage.getItem('sidebars'))
      const permission = []
      data.forEach(element => {
        if (element.children) {
          element.children.forEach(item => {
            item.children.forEach(i => {
              permission.push(i.menuId)
            })
          })
        }
      })
      commit('SET_ROUTES', accessedRoutes)
      commit('SET_PERMISSION', permission)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

複製代碼

寫在最後

我是涼城a,一個前端,熱愛技術也熱愛生活。

與你相逢,我很開心。

若是你想了解更多,請點這裏,期待你的小⭐⭐

  • 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊和關注😊

  • 本文首發於掘金,未經許可禁止轉載💌

相關文章
相關標籤/搜索