vue2动态路由权限管理总结

实现动态路由有两种方式,一种是后端返回什么,前端就展示什么,另一种是后端只返回角色,前端根据角色拼接数据信息展示。相比第一种方式,第二种方式在企业中更常用

 第一种方式:

(一)后端需返回类似Vue-router形式的json文件,如

[
    {path:'/userlist',name:'userlist',title:'用户列表',component:'Userlist.vue'},
	{path:'/role',name:'role',title:'角色管理',component:'Role.vue'},
    //...
]

(二)构建基础路由

const routes =  [
    {
        path: '/',
        name: 'Home',
        component: ()=>import('@/xx/xx/Home.vue')
    },
    {
        path: '/login',
        name: 'login',
        component: ()=>import('@/xx/xx/Login.vue')
    }
    //...
]

(三)配置路由守卫beforeEach

router.beforeEach(async (to, from, next) => {
  // 页面刷新 发送请求,获取路由数据
  //缓存路由 vuex
  if (store && store.state.nav.length == 0) {
    // 没有缓存路由,发送请求
    let res = await xx();
    // 将路由存放到vuex中
    store.dispatch('SETNAV', res.data.result);
    //调用addDynamicRoutes方法,动态路由生成
    let dynamicRoutes = addDynamicRoutes(res.data.result);
    router.addRoutes(dynamicRoutes);//动态路由数据添加
    next({...to});
  } else {
    next();
  }
})

(四)addDynamicRoutes方法

//路由动态生成(后端返回什么,就展示什么)
function addDynamicRoutes(res){
    res.forEach(v=>{
        routes.push({
            path:v.path,
            name:v.name,
            meta:{title:v.title},
            component:()=>import('@/xx/xx' + v.component)
        })
    });
    return routes;
}

(五)store里面的缓存

state: {
    nav: [],
},
getters: {
	navData: state => state.nav
},
mutations: {
    SETNAV(state,data){
      state.nav = data;
    }
},
actions: {
    SETNAV(content,data){
      content.commit('SETNAV',data);
    }
},

注:前端页面的命名应与后端返回的component中的一致

第二种方式:

(一)构建基础路由(router.config.js)

export const constantRouterMap = [{
    path:'/',
    component:()=>import('@/layout/Userlayout'),
    redirect:'/login',
    hidden:true,
    children:[{
    	path:'login',
    	name:'login',
    	component:()=>import('login.vue')
	}]
},{
    path:'/404',
        component:()=>import('404.vue')
},
    //...
 ]

(二)将基础路由放到router.js文件下

import { constantRouterMap } from './router.config';
const routes = constantRouterMap;

(三)配置路由守卫,在跳转之前进行拦截(permission.js)

//免登录名单
const whiteList = ['login','register',...];

router.beforeEach((to,from,next)=>{
    //动态生成网站标题
    to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
    
    //判断是否有access_token
    if(Vue.ls.get(ACCESS_TOKEN)){
        //有token,并且是登录页
        if(to.path === 'login'){
            next({
                path:'/'
            })
        }else{
            //判断vuex中是否有用户信息
            //Object.keys把对象的keys值放进数组里
           if(!store.getters.userInfo || Object.keys(store.getters.userInfo).length === 0){
                // 没有用户信息
                //1.获取用户信息
                store.dispatch('GetInfo')
                .then(()=>{
                    //拿到后端放回的用户信息里面包含了roles相关信息
                    const roles = res.data && res.data.roles;

                    //2.根据roles权限生成可访问的路由表
                    store.dispatch('GenerateRoutes',roles)
                    .then(()=>{
                        // 把最终生成vue-router的路由表放进vue router中
                        router.addRoutes(store.getters.addRoutes)

                        // 请求带有redirect重定向时,自动登录重定向到该地址
                        // decodeURIComponent将地址路径解析
                        const redirect = decodeURIComponent(from.query.redirect || to.path)

                        if(to.path === redirect){
                            // 确保addRoutes已完成
                            next({
                                ...to,
                                replace:true
                            })
                        }else{
                            // 跳转到目的路由
                            next({
                                path:redirect
                            })
                        }
                    })
                })
                .catch(()=>{
                    // 1.适用框架中的提示框,提示用户信息没找到
                    // ...

                    //2.调用接口,退出登录
                    store.dispatch('logout')
                    .then(()=>{
                        next({
                            path:'/login',
                            query:{
                                redirect: to.fullPath //把跳转的path地址作为参数传到下一个页面
                            }
                        })
                    })
                })

            }else{
                // 有用户信息
                next();
            }
        }
    }else{
        if(whiteList.includes(to.name)){
            //在免登录白名单里,直接放行
            next();
        }else{
            next({
                path:'/login'
            })
        }
    }
    
})

(四)将permission.js文件引入到main.js中

import './permission'

(五)store里面的用户信息

import { generatorDynamicRouter } from './generator-router';
import { constantRouterMap } from './router.config';

	state: {
        Info: {},
        routers: constantRouterMap,
        addRouters:[],
        roles: []
    },
    getters: {
        userInfo: state => state.Info,
        addRouters: state => state.addRouters
    },
    mutations: {
        SET_ROLES(state, roles) {
            state.roles = roles
        },
        SET_INFO(state, info) {
            state.Info = info;
        },
        SET_ROUTERS(state,routers){
            state.addRouters = routers;
            state.routers = constantRouterMap.concat(routers)
        }
    },
    actions: {
        // 调用接口,获取用户信息
        GetInfo(content) {
            return new Promise(async (resolve, reject) => {
                let res = await getinfo();
                let user = res.data;

                //确保拿到数据
                if (user && res.code === 0) {
                    // 1.将roles权限信息存进vuex里
                    content.commit('SET_ROLES', user.roles.permissions)

                    // 2.存用户信息
                    content.commit('SET_INFO', user);

                    //存其他有需要的信息
                    //...

                    //3.返回res
                    resolve(res)
                } else {
                    //返回错误信息
                    reject(new Error(res.message))
                }
            })

        },

        //将roles进行过滤,只要其中的permission菜单
        GenerateRoutes(content, roles) {
            return new Promise((resolve) => {
                let permissions = [];
                roles.forEach(role => {
                    role.permissions.forEach(p => permissions.push(p));
                });
                // 将permissions菜单进行从小到大排序
                permissions = permissions.sort((a, b) => (a.sortNo || 0) - (b.sortNo || 0));

                // 进行动态生成菜单
                const routes = generatorDynamicRouter(permissions);

                // 将routes存进vuex里
                content.commit('SET_ROUTERS',routes)
                resolve();
            })
        }
    },

(六)generator-router.js里面的方法

// 根级菜单
const rootRouter = {
    name: 'index',
    path: '/',
    redirect: '/xx/index',
    component: Layout,
    meta: { title: '首页' },
    children: []

}

// 前端路由表(页面位置component)
const constantRouterComponents = {
    xxLayout,
    //...
    '403': () => import('@/views/error/403'),
    '401': () => import('@/views/error/401'),
    '404': () => import('@/views/error/404'),
    //...
}


// 前端未找到页面路由(固定不用改)
const notFoundRouter = {
    path: '*',
    redirect: '/404',
    hidden: true
}

/**
 * 动态生成菜单
 * @returns {Promise<Router>}
 * @param permissions
 */
export function generatorDynamicRouter(permissions) {
    const menuNav = [];
    const childrenNav = [];

    // 1.将数组生成树结构
    listToTree(permissions, childrenNav, null);
    rootRouter.children = rootRouter.children.concat(childrenNav);

    // 替换掉redirect,替换成第一个孩子的path
    rootRouter.redirect = getFirstRouter(rootRouter.children);
    menuNav.push(rootRouter);

    //此时menuNav虽然有了path,但没有对应的component,所以需要生成让vue-router一样的路由表
    // 2.将树形结构生成vue-router路由表
    const routes = generator(menuNav);
    routes.push(notFoundRouter);
    return routes;
}

function getFirstRouter(router) {
    for (let i = 0; i < router.length; i++) {
        let rt = router[i];
        if (rt.children && rt.children.length > 0) {
            return getFirstRouter(rt.children);
        } else {
            return rt.path;
        }
    }
}

/**
 * 数组转树形结构
 * @param list 源数组
 * @param tree 树
 * @param parentId 父ID
 */
function listToTree(list, tree, parentId) {
    list.forEach(item => {
        //1.判断是否为父级菜单
        if (item.parentId === parentId) {
            const child = {
                ...item,
                children: []
            }

            // 2.迭代list,找到当前菜单符合的所有子菜单
            listToTree(list, child.children, item.id);

            // 3.删除不存在 children值的属性
            if (child.children.length <= 0) {
                delete child.children
            }

            // 4.加入到tree中
            tree.push(child)
        }
    });
}

/**
 * 格式化树形结构数据 生成 vue-router 层级路由表
 *
 * @param routerMap
 * @param parent
 * @returns {*}
 */
function generator(routerMap, parent) {
    return routerMap.map((item) => {
        let currentRouter = item
        if (!item.meta) {
            const {
                name,
                //一些所需的属性
                //...
            } = currentRouter;
            currentRouter = {
                // 如果有 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace形式
                path: item.path || `${parent && parent.path || ''}/${item.key}`,
                //路由名称
                name: item.key,
                // 页面动态加载
                component: (constantRouterComponents[item.component || item.key]) || (() => import(
                    `@/views/${item.component}`)),
                //页面标题
                meta: {
                    title: name,
                    icon: icon || undefined,
                    //...
                }
            }
            // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
            if (!currentRouter.path.startsWith('http')) {
                currentRouter.path = currentRouter.path.replace('//', '/')
            }

            // 重定向
            item.redirect && (currentRouter.redirect = item.redirect)
        }
        // 是否有子菜单,并递归处理
        if (item.children && item.children.length > 0) {
            // Recursion
            currentRouter.children = generator(item.children, currentRouter)
        }
        return currentRouter
    })
}

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值