Vue-根据角色生成动态路由及菜单-4(完)-完结动态路由

1.安装axios,创建请求和响应拦截器,建立环境变量文件,模拟获得token和userInfo接口。终端中运行 npm i axios 安装axios。在/src目录下创建utils/request.js 用来封装请求。同时在项目根目录下创建.env环境变量文件( .env.development为开发时环境变量,.env.production为生产时环境变量 )。 axios.create的baseURL使用了环境变量,因为每次修改环境变量都需要重新编译才能生效,所以平时开发的时候为了省时都是直接在此添加一行实际用到的Url(切记提交代码前改为使用环境变量的 (づ╥﹏╥)づ 之前忘记了2次导致生产环境请求了测试环境服务器,教训啊)。

image

request.js中主要做了创建axios实例并拦截请求和响应,做一些处理。实际项目中根据需要在请求拦截中附加token,在响应拦截中根据后端返回的响应码做对应的处理。

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 根据实际项目中接口返回的状态码进行处理(比如拿到没有token的响应跳转login等操作)

     return Promise.reject(res)

   }

 },

  error => {

 }

)

export default axiosInstance

2. 创建测试api。在src/下创建apis目录,生成login.js用来处理和登录相关的api。这里测试只用了login登录和getInfo获得用户信息两个接口

image

然后在之前views/Login.vue测试页面中简单写一个输入用户名+密码以及一个登录按钮。点击登录按钮dispatch登录操作对应的loginFn,拿到token并存储到state中;后继续dispatch获得用户信息对应的getUserInfo拿到roles和userInfo并存储到state中。这里获得token后存到了本地,取的时候也从本地取,本地没有时默认为'',防止刷新后store中数据丢失又需要重新登录。

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, // 左侧菜单栏是否收起

   },

   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获得用户信息后就可以跳转到首页了。


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. 到这里就只需要根据用户的role过滤出他有权限看的页面的路由动态生成左侧导航菜单,并且使用router.beforeEach路由守卫进行权限判断拦截处理。

之前为了测试,router/index.js中new VueRouter时传入的routes为所有路由,现在改为只传入不需要权限就能访问的constantRoutes;同时把store/index.js中的state中 routes 改为空数组;并新增2个方法,调用getUserInfo后拿到role根据用户角色从所有路由中过滤能访问的路由数据赋值给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 // 没写权限的默认允许  比如404

 }

}

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)

   }

 })

 // console.log(res)

 return res

}

在/src下生成permission.js进行路由守卫,并在main.js中引入。代码仅提供思路参考,实际根据项目完善异常处理


import store from '@/store'

import router from '@/router'

// 不需要鉴权的页面

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, 则跳转到 /, 该跳转动作依旧走一遍 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)   //  !!!! 生成动态路由的关键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

到这里根据用户角色生成动态路由全部实现思路走了一遍。实现方式只是多种优秀方式中的一种,这4篇关联文章仅供自己记录实现思路回顾用,如有见解欢迎交流

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342