Vue-根據(jù)角色生成動態(tài)路由及菜單-4(完)-完結(jié)動態(tài)路由

1.安裝axios,創(chuàng)建請求和響應(yīng)攔截器,建立環(huán)境變量文件,模擬獲得token和userInfo接口。終端中運(yùn)行 npm i axios 安裝axios。在/src目錄下創(chuàng)建utils/request.js 用來封裝請求。同時在項(xiàng)目根目錄下創(chuàng)建.env環(huán)境變量文件( .env.development為開發(fā)時環(huán)境變量,.env.production為生產(chǎn)時環(huán)境變量 )。 axios.create的baseURL使用了環(huán)境變量,因?yàn)槊看涡薷沫h(huán)境變量都需要重新編譯才能生效,所以平時開發(fā)的時候?yàn)榱耸r都是直接在此添加一行實(shí)際用到的Url(切記提交代碼前改為使用環(huán)境變量的 (づ╥﹏╥)づ 之前忘記了2次導(dǎo)致生產(chǎn)環(huán)境請求了測試環(huán)境服務(wù)器,教訓(xùn)?。?。

image

request.js中主要做了創(chuàng)建axios實(shí)例并攔截請求和響應(yīng),做一些處理。實(shí)際項(xiàng)目中根據(jù)需要在請求攔截中附加token,在響應(yīng)攔截中根據(jù)后端返回的響應(yīng)碼做對應(yīng)的處理。

request.js大概代碼:


import axios from 'axios'

import Store from '@/store'

const timeOut = 10000

const axiosInstance = axios.create({

 baseURL: process.env.VUE_APP_BASE_URL,

 withCredentials: true, // send cookies when cross-domain requests

 timeout: timeOut // request timeout 1

})

axiosInstance.interceptors.request.use(

  config => {

   const token = Store.getters.token

   if (token) {

     config.headers['Authorization'] = 'Bearer ' + token

   }

   return config

 },

  error => {

   return Promise.reject(error)

 }

)

axiosInstance.interceptors.response.use(

  response => {

   // console.log(response)

   const res = response.data

   // console.log(res)

   const code = res.code

   if (code === 20000) {

     return res

   } else {

     // TODO 根據(jù)實(shí)際項(xiàng)目中接口返回的狀態(tài)碼進(jìn)行處理(比如拿到?jīng)]有token的響應(yīng)跳轉(zhuǎn)login等操作)

     return Promise.reject(res)

   }

 },

  error => {

 }

)

export default axiosInstance

2. 創(chuàng)建測試api。在src/下創(chuàng)建apis目錄,生成login.js用來處理和登錄相關(guān)的api。這里測試只用了login登錄和getInfo獲得用戶信息兩個接口

image

然后在之前views/Login.vue測試頁面中簡單寫一個輸入用戶名+密碼以及一個登錄按鈕。點(diǎn)擊登錄按鈕dispatch登錄操作對應(yīng)的loginFn,拿到token并存儲到state中;后繼續(xù)dispatch獲得用戶信息對應(yīng)的getUserInfo拿到roles和userInfo并存儲到state中。這里獲得token后存到了本地,取的時候也從本地取,本地沒有時默認(rèn)為'',防止刷新后store中數(shù)據(jù)丟失又需要重新登錄。

image

import Vue from 'vue'

import Vuex from 'vuex'

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

import { login, getInfo } from '@/apis/login'

Vue.use(Vuex)

export default new Vuex.Store({

 state: {

   app: {

     sideBarIsCollapse: false, // 左側(cè)菜單欄是否收起

   },

   routes: asyncRoutes.concat(constantRoutes),

   token: localStorage.getItem('token') || '',

   userInfo: null,

   roles: [],

 },

 getters: {

   token(state) {

     return state.token

   },

   roles(state) {

     return state.roles

   },

   routes(state) {

     return state.routes

   }

 },

 mutations: {

   TOGGLE_SIDE_BAR(state) {

     state.app.sideBarIsCollapse = !state.app.sideBarIsCollapse

   },

   SET_ROUTES(state, roles){

     console.log(state, roles)

   },

   SET_TOKEN(state, token) {

     state.token = token

     localStorage.setItem('token', token)

   },

   SET_INFO(state, data) {

     state.userInfo = data

   },

   SET_ROLES(state, roles) {

     state.roles = roles

   },

   LOGOUT(state) {

     state.token = ''

     localStorage.removeItem('token')

   }

 },

 actions: {

   loginFn({ commit }, data) {

     return new Promise((resolve) => {

       login(data).then(res => {

         if (res?.data) {

           commit('SET_TOKEN', res.data)

           resolve(res.data)

           //   dispatch('getUserInfo', res.data)

         }

       })

     })

   },

   getUserInfo({ commit }, token) {

     return new Promise((resolve, reject) => {

       getInfo(token).then(res => {

         // console.log(res)

         if (res?.data) {

           commit('SET_INFO', res.data)

           commit('SET_ROLES', res.data.roles)

           // commit('SET_ROUTES', res.data.roles)

           resolve(res.data)

         }

       }).catch(e => {

         reject(e)

       })

     })

   }

 },

})

Login.vue中dispatch獲得用戶信息后就可以跳轉(zhuǎn)到首頁了。


async handleLogin() {

     const token = await this.loginFn(this.loginForm)

     if (token) {

       const info = await this.getUserInfo(token)

       console.log(info)

       if (info.id) {

         this.$router.push('/')

       }

     }

   }

3. 到這里就只需要根據(jù)用戶的role過濾出他有權(quán)限看的頁面的路由動態(tài)生成左側(cè)導(dǎo)航菜單,并且使用router.beforeEach路由守衛(wèi)進(jìn)行權(quán)限判斷攔截處理。

之前為了測試,router/index.js中new VueRouter時傳入的routes為所有路由,現(xiàn)在改為只傳入不需要權(quán)限就能訪問的constantRoutes;同時把store/index.js中的state中 routes 改為空數(shù)組;并新增2個方法,調(diào)用getUserInfo后拿到role根據(jù)用戶角色從所有路由中過濾能訪問的路由數(shù)據(jù)賦值給state中的routes。

image

filterAsyncRoutes方法中,在有子路由即有children情況下使用遞歸處理:


function hasPermission(roles, route) {

 if (route.meta && route.meta.rolesAuths) {

   return roles.some(role => route.meta.rolesAuths.includes(role)) // rolesAuths只要包含roles中任意一個元素即滿足 true

 } else {

   return true // 沒寫權(quán)限的默認(rèn)允許  比如404

 }

}

function filterAsyncRoutes(routes, roles) {

 const res = []

 routes.forEach(route => {

   const tmp = { ...route }

   if (hasPermission(roles, tmp)) { // 父級路由有權(quán)限才處理子頁面權(quán)限

     if (tmp.children) {

       tmp.children = filterAsyncRoutes(tmp.children, roles)

     }

     res.push(tmp)

   }

 })

 // console.log(res)

 return res

}

在/src下生成permission.js進(jìn)行路由守衛(wèi),并在main.js中引入。代碼僅提供思路參考,實(shí)際根據(jù)項(xiàng)目完善異常處理


import store from '@/store'

import router from '@/router'

// 不需要鑒權(quán)的頁面

const whitePagePaths = ['/login']

router.beforeEach((to, from, next) => {

 console.log(to, from)

 const token = store.getters.token

 if (token) {

   if (to.path === '/login') {

     next({ path: '/' }) // 如果是/login, 則跳轉(zhuǎn)到 /, 該跳轉(zhuǎn)動作依舊走一遍 router.beforeEach 邏輯

   } else {

     const roles = store.getters.roles

     if (roles.length > 0) {

       next()

     } else {

       // 如果只有token但是沒有拿到userInfo(比如說刷新了頁面),則重新獲取一次

       store.dispatch('getUserInfo').then(res => {

         if (res) {

           const roles = res.roles

           store.commit('SET_ROUTES', roles)

           const routes = store.state.routes

           routes.forEach(item => {

             router.addRoute(item)   //  !!!! 生成動態(tài)路由的關(guān)鍵api

           })

           next(to.path)

         }

       }).catch(e => {

         console.log(e)

         localStorage.removeItem('token')

         store.dispatch('SET_TOKEN', '')

         router.push('/login')

       })

     }

   }

 } else {

   if (whitePagePaths.includes(to.path)) {

     next()

   } else {

     next(`/login?redirect=${to.fullPaht || ''}`)

   }

 }

})

image

到這里根據(jù)用戶角色生成動態(tài)路由全部實(shí)現(xiàn)思路走了一遍。實(shí)現(xiàn)方式只是多種優(yōu)秀方式中的一種,這4篇關(guān)聯(lián)文章僅供自己記錄實(shí)現(xiàn)思路回顧用,如有見解歡迎交流

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

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