vue实现动态路由进行权限管理

831 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

第一种方式:后端控制路由:需要使用vuex

  • 后端传递类似这样的数据:{path:'/home',name:'home',component:'Home',icon:'icon1',title:'首页'};
  • 我们需要调用这个请求,得到数据,然后放到vuex和localstorage中;
  • 什么时候调用这个请求呢?那就是在路由守卫里,但是我们全局的路由守卫每一次路由的变化都会调用,那么我们需要每一次路由的变化都去调用数据吗?当然不需要,我们只需要拿到后就放在vuex或者localStorage中,后面路由再变化的时候,如果有就不需要再调用了,没有再调用。
  • 拿到数据以后,我们需要做什么呢?肯定是需要将这部分数据放入我们的routes里面啊,然后通过router.addRouters(routes)进行路由的替换,以此实现不同的角色拥有不同的路由。

代码:

这里之所以要将获取到的数据同时存在vuex和localStorage(sessionStorage)中,是因为如果仅仅存放在vuex中的话,那么我们进入页面后刷新,就会导致vuex也重新加载了,动态路由数据就会消失,出现页面404。

//登录界面
<template>
    <button @click='login'>登录</button>
</template>
<script>
    import {login} from '@/server/api'
    import { useStore } from 'vuex' // 引入useStore 方法
    import { useRouter, useRoute } from "vue-router"
    export default {
        name:'Login',
        setup() {
            const store = useStore()  // 该方法用于返回store 实例
            const router =useRouter() //==>this.$router
            const route=useRoute()//this.$route
            const login = async () => {
                let res = await login();
                if(res.code == 200) {
                    let menuList = res.data.data.menuList; //导航
                    localStorage.setItem('menuList', JSON.stringify(menuList))
                    store.commit("setMenuList", menuList);
                    let routes = res.data.data.routes; //路由
                    localStorage.setItem('routes', JSON.stringify(routes))
                    store.commit("setRouters", routes);
                    let token = res.data.data.token;
                    localStorage.setItem('token', token);
                    router.push({path:'/home'});
                }
            }
        }
        return {login}
    }
</script>
//vuex
import { createStore } from 'vuex'export default createStore({
  state: {
      isLoadRouters:false,
      routes:[],
      menuList:[]
  },
  mutations: {
      setRouters(state,data) {
          state.routes = data;
      },
      setMenuList(state,data) {
          state.menuList = data;
      },
      setIsLoadRouters(state, data) {
          state.isLoadRouters = data;
      },
​
  },
  actions: {}
})

router.addRoute第一个参数是动态路由所在“位置”,是指定“位置”的name属性,第二个参数是要添加的路由信息。

//vue-router
import { createRouter, createWebHistory } from 'vue-router'
import store from '@/store/index'const  routes = [ 
   {
        path: "/login",
        name: "Login",
        component: () => import ("@/views/login/Login.vue"),
        meta: {
            title: '登录',
        }
    },
    {
        path: "/home",
        name: "Home",
        component: () => import ( "@/views/home/Home.vue"),
    }
]
const routerHistory = createWebHistory()
const routers = createRouter({
    history: routerHistory,
    routes: routes
})
​
router.beforeEach((to, from, next) =>   {
    let isLoadRouters = store.state.isLoadRouters;
    let token = localStorage.getItem('token');
    let routes = JSON.parse(localStorage.getItem('routes'));
    if (to.path == '/login') {
        next()
    } else {
        //用户已登录
        if (token && JSON.stringify(routes) != '[]') {
            if (isLoadRouters) {
                next()   //路由已添加,直接跳转到目标页面
            } else {
                //解决刷新页面空白,重新加载路由,并跳转到目标页
                let route = JSON.parse(localStorage.getItem('routes'))
                store.commit('setRouters', route);
                store.commit('isLoadRouters', true);
                //添加路由
                for (let item of route) {
                    router.addRoute('home', {
                        path: item.path,
                        component: () => import(`@/views/${item.component}`),
                        meta: {
                            title: item.title,
                            requiresAuth: true
                        }
                    })
                }
                next({...to,replace: true})
            }
        } else { //无登录信息,跳转到登录页
            store.commit('setMenuList', []);
            store.commit('isLoadRouters', false);
            localStorage.setItem('token', '');
            localStorage.setItem('menuList', JSON.stringify([]));
            localStorage.setItem('routes', JSON.stringify([]));
            next(`/login`)
        }
    }
});
 
export default routers

现在我们成功给路由添加了后台返回的路由数据,最后再让页面的导航栏也添加上就行了!

第二种方式:前端控制路由

第一种方式就存在一个问题:那就是后端不好返回数据,因为后端返回的数据必须是:{path:'xxx',component:xxx,meta:{title:'xxx',icon:'xxx}},就很麻烦,所以就还有一种方式:前端控制路由,你后端只返回一个用户的角色就可以了,前端拿到角色后,自己进行路由的增添修改。

思路:在路由配置里,通过meta属性,扩展权限相关的字段,在路由守卫里通过判断这个权限标识,实现路由的动态增加,及页面跳转;如:我们增加一个role字段来控制角色.

  • 登录后返回用户信息,保存在vuex和localStorage(sessionStore)中;

  • 在vue-router中配置好静态路由,写好动态路由;

  • 页面跳转时,判断在路由守卫里是否已经实现了动态路由;

    • 没有实现,就根据用户信息往静态路由中添加动态路由(需要的才添加);
    • 实现了,就跳过;

这就是大致思路了,其实看着还是比较简单的。

//登录
import {login} from '@/server/api'
import { useStore } from 'vuex' // 引入useStore 方法
...
setup() {
    const store = useStore()  // 该方法用于返回store 实例
    const login = async () => {
        let res = await login();
        if(res.code === 200) {
            localStorage.setItem('user',res.data.user)
            store.commit('setUser',res.data.user)
            ...
        }
    }
    return {login}
}
//vue-touter
import { createRouter, createWebHistory } from 'vue-router'const  routes = [ 
   {
        path: "/login",
        name: "Login",
        component: () => import ("@/views/login/Login.vue"),
        meta: {
            title: '登录',
        }
    },
    {
        path: "/home",
        name: "Home",
        component: () => import ( "@/views/home/Home.vue")
    }
]
​
export const asyncRoutes = [
    {
        path: '/jobData',
        component: () => import ( "@/views/jobData/JobData.vue"),
        name: 'JobData',
        meta: {
          title: 'JobData',
          icon: 'lock',
          // 这个意思就是admin、editor这两个角色,这个菜单是可以显示
          roles: ['admin', 'editor']
        }
    },
    {
        path:'/roleData',
        component:()=>import("@/views/roleData/RoleData.vue"),
        name:'RoleData',
        meta:{
            title:'RoleData',
            icon:'role',
            roles:['admin']
        }
    }
]
​
router.beforeEach((to,from,next)=>{
    let isLoadRouters = store.state.isLoadRouters;
    let token = localStorage.getItem('token');
    let user = localStorage.getItem('')
    if (to.path == '/login') {
        next()
    } else {
        //用户已登录
        if (token && user.role) {
            if (isLoadRouters) {
                next()   //路由已添加,直接跳转到目标页面
            } else {
                //解决刷新页面空白,重新加载路由,并跳转到目标页
                let user = localStorage.getItem('')
                store.commit('setUser', route);
                store.commit('isLoadRouters', true);
                //添加路由
                let menuList = [];
                for (let item of asyncRoutes) {
                    if(item.meta.roles.includes(user.role)) {
                       router.addRoute({
                            path: item.path,
                            component: () => import(`@/views/${item.component}`),
                            meta: {
                                title: item.title,
                                requiresAuth: true
                            }
                        }) 
                        menuList.push(item);
                    }
                }
                next({...to,replace: true})
            }
        } else { //无登录信息,跳转到登录页
            store.commit('setMenuList', []);
            store.commit('setUser', null);
            store.commit('isLoadRouters', false);
            localStorage.setItem('token', '');
            localStorage.setItem('user', null));
            next(`/login`)
        }
    }
})
​
​
const routerHistory = createWebHistory()
const routers = createRouter({
    history: routerHistory,
    routes: routes
})
export default routers
//vuex
import { createStore } from 'vuex'export default createStore({
  state: {
      isLoadRouters:false,
      user:null,
      menuList:[]
  },
  mutations: {
      setUser(state,data) {
          state.user = data;
      },
      setMenuList(state,data) {
          state.menuList = data;
      },
      setIsLoadRouters(state, data) {
          state.isLoadRouters = data;
      }
  },
  actions: {}
})

下面再去根据menuList配一下页面导航栏就行了..

如果有嵌套子路由的话,稍微麻烦一点,不过我想大致原理是差不多的,可能就是添加判断的时候要麻烦一些,后面有空或者遇见了这种情况再说,哈哈~