记一次vue addroutes的痛苦经历

1,078 阅读3分钟

背景

最近在写一个springboot+vue的rbac后台管理系统,其中权限校验那是必不可少的要素;前端也就自然需要通过vue的addroutes来动态添加用户拥有的路由信息;

思路

再参考了非常多的资料后,开始整理了开发的思路

1 可以把静态路由和动态路由分开添加

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

//匹配路由名字获得对应组件
export const ruleMapping = {
    'menu': () => import("@/views/system/Menu"),
    'user': () => import("@/views/system/User"),
    'job': () => import("@/views/system/Job"),
    'role': () => import("@/views/system/Role"),
    'dept': () => import("@/views/system/Dept"),
}

//已存静态路由
const routes = [
    {
        path: '/',
        redirect: "/home"
    },
    {
        path: '/login',
        component: () => import("@/components/Login"),
    }
]

//动态路由的头部
export const authRoutes = {
    path: '/home',
    component: () => import("@/components/Home"),
    children: [{
        path: '/',
        redirect: "/index",
    },{
        path: '/index',
        component: () => import("@/components/Index"),
    }]
}

//创建路由的方法
const createRouter = () => new VueRouter({
    mode: 'hash',
    routes:routes
})

//重置路由信息
export const resetRouter = () => {
    const newRouter = createRouter()
    router.matcher = newRouter.matcher
};

const router = createRouter()

export default router

2 将后端返回的权限信息封装成vue的路由信息

putAuthList(context, menuVoList) {
     let tmpAuthRoutes = deepCopy(authRoutes)
     function iteration(menuVoList) {
         menuVoList.forEach((menuItem) => {
             if (menuItem.routerUrl && menuItem.componentUrl) {
                 let param = {};
                 param.path = menuItem.routerUrl
                 param.component = ruleMapping[menuItem.componentUrl]
                 tmpAuthRoutes.children.push(param)
             }

             if (menuItem.childMenu) {
                 iteration(menuItem.childMenu)
             }
         })
     }
     iteration(menuVoList);
     router.addRoutes([tmpAuthRoutes])
 }

3 退出登录后,重置路由

//重置路由信息
export const resetRouter = () => {
    const newRouter = createRouter()
    router.matcher = newRouter.matcher
};

问题

如上第二步的putAuthList方法(已经是修改后的)就是出问题的集中区域;

尝试1

路由信息是这样的,没有包含所谓的动态头部路由

//已存静态路由,
export let routes = [
    {
        path: '/',
        redirect: "/home"
    },
    {
        path: '/login',
        component: () => import("@/components/Login"),
    }
    ,{
        path: '/home',
        component: () => import("@/components/Home"),
        children: [{
            path: '/',
            redirect: "/index",
        },{
            path: '/index',
            component: () => import("@/components/Index"),
        }]
    }
]

然后直接把动态路由信息添加到静态路由的children中

putAuthList(context, menuVoList) {
    > let tmpAuthRoutes = router.options.routes;
    function iteration(menuVoList) {
        menuVoList.forEach((menuItem) => {
            if (menuItem.routerUrl && menuItem.componentUrl) {
                let param = {};
                param.path = menuItem.routerUrl
                param.component = ruleMapping[menuItem.componentUrl]
                > tmpAuthRoutes[2].children.push(param)
            
            if (menuItem.childMenu) {
                iteration(menuItem.childMenu)
            }
        })
    }
    iteration(menuVoList);
    router.addRoutes(tmpAuthRoutes)
}

这样做确实能够进入登录后页面

但是退出后再登录会发现添加了非常多的重复路由信息,虽然使用了resetRouter,但是新的router.routes已经是添加过动态路由的routes了

尝试2

保留头部动态路由的方案,通过深拷贝来复制头部动态路由,头部动态路由副本去添加动态路由,最后再addroutes到路由表中,这样就不会影响到静态路由了;resetRouter也才能生效

//已存静态路由
const routes = [
    {
        path: '/',
        redirect: "/home"
    },
    {
        path: '/login',
        component: () => import("@/components/Login"),
    }
]

//动态路由的头部
export const authRoutes = {
    path: '/home',
    component: () => import("@/components/Home"),
    children: [{
        path: '/',
        redirect: "/index",
    },{
        path: '/index',
        component: () => import("@/components/Index"),
    }]
}
putAuthList(context, menuVoList) {
    > let tmpAuthRoutes = JSON.parse(JSON.stringify(authRoutes))
    > console.log(tmpAuthRoutes)
    > console.log(authRoutes)
    function iteration(menuVoList) {
        menuVoList.forEach((menuItem) => {
            if (menuItem.routerUrl && menuItem.componentUrl) {
                let param = {};
                param.path = menuItem.routerUrl
                param.component = ruleMapping[menuItem.componentUrl]
                > tmpAuthRoutes.children.push(param)
            }
            if (menuItem.childMenu) {
                iteration(menuItem.childMenu)
            }
        })
    }
    iteration(menuVoList);
    router.addRoutes(tmpAuthRoutes)
}

结果是,根本连首页都进不去,为什么呢?看控制台可以知道,component部分完全没有;是因为JSON.parse(JSON.stringIf()),只是对字符串进行了转换,而获取组件是通过function来完成的;(失败)

尝试3

也就是最开始贴出思路的代码,尝试2和3的区别就是深拷贝方案的不同,这里贴一下深拷贝的函数吧,来源于网络:

/**
 * 深拷贝一个值
 * @param value
 */
export default function deepCopy(data, hash = new WeakMap()) {
    if(typeof data !== 'object' || data === null){
        throw new TypeError('传入参数不是对象')
    }
    // 判断传入的待拷贝对象的引用是否存在于hash中
    if(hash.has(data)) {
        return hash.get(data)
    }
    let newData = {};
    const dataKeys = Object.keys(data);
    dataKeys.forEach(value => {
        const currentDataValue = data[value];
        // 基本数据类型的值和函数直接赋值拷贝
        if (typeof currentDataValue !== "object" || currentDataValue === null) {
            newData[value] = currentDataValue;
        } else if (Array.isArray(currentDataValue)) {
            // 实现数组的深拷贝
            newData[value] = [...currentDataValue];
        } else if (currentDataValue instanceof Set) {
            // 实现set数据的深拷贝
            newData[value] = new Set([...currentDataValue]);
        } else if (currentDataValue instanceof Map) {
            // 实现map数据的深拷贝
            newData[value] = new Map([...currentDataValue]);
        } else {
            // 将这个待拷贝对象的引用存于hash中
            hash.set(data,data)
            // 普通对象则递归赋值
            newData[value] = deepCopy(currentDataValue, hash);
        }
    });
    return newData;
}