vue后臺(tái)管理系統(tǒng)登錄權(quán)限

做后臺(tái)項(xiàng)目區(qū)別于做其它的項(xiàng)目,權(quán)限驗(yàn)證與安全性是非常重要的,可以說是一個(gè)后臺(tái)項(xiàng)目一開始就必須考慮和搭建的基礎(chǔ)核心功能。我們所要做到的是:不同的權(quán)限對(duì)應(yīng)著不同的路由,同時(shí)側(cè)邊欄也需根據(jù)不同的權(quán)限,異步生成。這里先簡(jiǎn)單說一下,我實(shí)現(xiàn)登錄和權(quán)限驗(yàn)證的思路。

  • 登錄:當(dāng)用戶填寫完賬號(hào)和密碼后向服務(wù)端驗(yàn)證是否正確,驗(yàn)證通過之后,服務(wù)端會(huì)返回一個(gè)token,拿到token之后(我會(huì)將這個(gè)token存貯到cookie中,保證刷新頁(yè)面后能記住用戶登錄狀態(tài)),前端會(huì)根據(jù)token再去拉取一個(gè) user_info 的接口來獲取用戶的詳細(xì)信息(如用戶權(quán)限,用戶名等等信息)。
  • 權(quán)限驗(yàn)證:通過token獲取用戶對(duì)應(yīng)的路由,通過 router.addRoutes 動(dòng)態(tài)掛載這些路由。

點(diǎn)擊登錄按鈕之后觸發(fā)的登錄操作:

handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then((res) => {
            this.loading = false
            this.$router.push({ path: this.redirect || '/' })
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

login函數(shù):

login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        commit('SET_TOKEN', response.token)
        setToken(response.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

登錄時(shí)只調(diào)用登錄接口,將token使用setToken方法存入cokkie中,在調(diào)用的request中,封裝axios方法,使用攔截器在每次調(diào)用接口前將token放入header中,這樣封裝好的調(diào)用接口方法不用每次都存入token,極大的簡(jiǎn)化了我們的代碼

獲取用戶信息:
用戶登錄成功之后,我們會(huì)在全局鉤子router.beforeEach中攔截路由,判斷是否已獲得token,在獲得token之后我們就要去獲取用戶的基本信息了
在permission文件中:

router.beforeEach(async(to, from, next) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)
  const hasToken = getToken()
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          await store.dispatch('user/getInfo')
          const accessRoute = await store.dispatch('router/getSysRouter')
          router.addRoutes(accessRoute)
          next({ ...to, replace: true })
        } catch (error) {
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

這樣寫可以在每次登錄或者刷新頁(yè)面的時(shí)候,發(fā)起獲取用戶信息的請(qǐng)求,以及路由信息的請(qǐng)求

  • 添加路由權(quán)限】

添加路由我們是由后端來控制的,通過請(qǐng)求路由接口,再動(dòng)態(tài)將路由表生成,在上面的代碼中我們已經(jīng)在permission文件中發(fā)起了請(qǐng)求,接下來我們要把路由表放到vuex中,再通過請(qǐng)求出來的信息,將返回的信息拼接成我們需要的路由。
在之前通過后端動(dòng)態(tài)返回前端路由一直很難做的,因?yàn)関ue-router必須是要vue在實(shí)例化之前就掛載上去的,不太方便動(dòng)態(tài)改變。不過好在vue2.2.0以后新增了router.addRoutes,有了這個(gè)我們就可相對(duì)方便的做權(quán)限控制了。
以下是router/index文件和在vuex中創(chuàng)建的router文件:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
import menuModule from '@/store/modules/router'
const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: menuModule.state.router
})
const router = createRouter()
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}
export default router

將router暴露出來,我們?cè)谏厦娴膒ermission文件中會(huì)將router引入,使用addRoute將得到的路由動(dòng)態(tài)加載出來

import { reqGet } from '@/api/httpReq'
const map = {
  layout: () => import('@/layout'),
  dashboardIndex: () => import('@/views/dashboard/index'),
  doBusinessIndex: () => import('@/views/doBusiness/index'),
  doBusinessAgentDetail: () => import('@/views/doBusiness/agentDetail'),
  carSearchIndex: () => import('@/views/carSearch/index'),
  carSearchDetail: () => import('@/views/carSearch/detail'),
  smartCarbetIndex: () => import('@/views/smartCarbet/index'),
  alarmSearchIndex: () => import('@/views/alarmSearch/index'),
  alarmSearchDetail: () => import('@/views/alarmSearch/detail'),
  tagSearchIndex: () => import('@/views/tagSearch/index')
}
const state = {
  router: [
    {
      path: '/login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: '/',
      component: map['layout'],
      redirect: '/dashboard',
      children: [{
        path: 'dashboard',
        name: '業(yè)務(wù)看板',
        component: map['dashboardIndex'],
        meta: { title: '業(yè)務(wù)看板', icon: 'yingyongguanli' }
      }]
    }
  ]
}

const mutations = {
  pushRouterIn(state, data) {
    state.router.push(data)
  }
}

const actions = {
  getSysRouter(context) {
    return new Promise(resolve => {
      reqGet('/sys/menu/list', 'get').then(res => {
        var arr = res.menuList
        var asyncRoute = initRouter(arr, context.state.router)
        context.state.router = asyncRoute
        resolve(asyncRoute)
      })
    })
  }
}
function initRouter(arr, router) {
  var arr2 = router
  for (let i in arr) {
    let c1 = {}
    let a1 = arr[i]
    c1.meta = a1.meta
    c1.name = a1.name
    c1.path = a1.path
    c1.redirect = a1.redirect
    c1.component = map[a1.component]
    c1.children = []
    if (a1.children.length) {
      for (let n in a1.children) {
        let a2 = a1.children[n]
        let c2 = {}
        if (a2.meta.length) {
          c2.meta = a2.meta
        }
        c2.path = a2.path
        c2.name = a2.name
        c2.component = map[a2.component]
        c2.hidden = true
        c1.children.push(c2)
      }
    }
    arr2.push(c1)
  }
  return arr2
}

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

由于獲取的路由信息為字符串,前端引入的component部分不能直接使用后臺(tái)的信息,所以在這里使用了一個(gè)轉(zhuǎn)換映射的過程,即將components的name 和 本地components 做一個(gè)映射
如:

const map={
 login:require('login/index').default // 同步的方式
 login:()=>import('login/index')      // 異步的方式
}
//你存在服務(wù)端的map類似于
const serviceMap=[
 { path: '/login', component: 'login', hidden: true }
]
//之后遍歷這個(gè)map,動(dòng)態(tài)生成asyncRouterMap
//并將 component 替換為map[component]

在路由router中,我們保留了必要的基礎(chǔ)路由,其余的路由由后臺(tái)獲取的數(shù)據(jù)動(dòng)態(tài)添加push進(jìn)router中,將路由添加好之后,通過之前的使用svg的文章,我們可以很輕松的將左側(cè)菜單渲染出來,路由一定要放到vuex中,這樣就能在路由改變的時(shí)候?qū)?shù)據(jù)同步渲染到頁(yè)面中,在permission獲取路由信息中,大量的使用了async await Promise這樣的方式將信息處理異步問題,以免由于異步調(diào)用接口,使代碼路由還未生成就已經(jīng)執(zhí)行next()方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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