美文网首页学习
Vue-根据角色生成动态路由及菜单-4(完)-完结动态路由

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

作者: 起舞弄清影yr | 来源:发表于2023-03-26 17:36 被阅读0次

    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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/lmtardtx.html