SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇:使用 vue-router 进行动态加载菜单

Posted 累成一条狗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇:使用 vue-router 进行动态加载菜单相关的知识,希望对你有一定的参考价值。

前提:

(1) 相关博文地址:

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(一):搭建基本环境:https://www.cnblogs.com/l-y-h/p/12930895.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(二):引入 element-ui 定义基本页面显示:https://www.cnblogs.com/l-y-h/p/12935300.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(三):引入 js-cookie、axios、mock 封装请求处理以及返回结果:https://www.cnblogs.com/l-y-h/p/12955001.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(四):引入 vuex 进行状态管理、引入 vue-i18n 进行国际化管理:https://www.cnblogs.com/l-y-h/p/12963576.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(五):引入 vue-router 进行路由管理、模块化封装 axios 请求、使用 iframe 标签嵌套页面:https://www.cnblogs.com/l-y-h/p/12973364.html

(2)代码地址:

https://github.com/lyh-man/admin-vue-template.git

 

一、获取动态菜单数据

1、介绍

(1)为什么使用动态加载菜单?
  在之前的 router 中,菜单的显示都是写死的。
  而实际业务需求中,可能需要动态的从后台获取菜单数据并显示,这就需要动态加载菜单了。
比如:
  需要根据用户权限,去展现不同的菜单选项。

(2)项目中如何使用?
  项目中使用时,各个 vue 组件事先定义好,并存放在固定位置。
  用户登录成功后,根据后台返回的菜单数据,动态拼接路由信息,并显示相应的菜单项(不同权限用户显示不同菜单)。
  点击菜单项,会连接到相应的 vue 组件,跳转路由并显示。

2、使用 mock 模拟返回 动态菜单数据

(1)Step1:
  封装一个 菜单请求模块,并引入。
  之前博客中已介绍使用了 模块化封装 axios 请求,这里新建一个 menu.js 用来处理菜单相关的请求。

import http from \'@/http/httpRequest.js\'

export function getMenus() {
    return http({
        url: \'/menu/getMenus\',
        method: \'get\'
    })
}

 

 

 引入 封装好的模块。

 

 

 

(2)Step2:
  使用 mock 模拟菜单数据。
  封装一个 menu.js 并引入。

import Mock from \'mockjs\'

// 登录
export function getMenus() {
    return {
        // isOpen: false,
        url: \'api/menu/getMenus\',
        type: \'get\',
        data: {
            \'code\': 200,
            \'msg\': \'success\',
            \'data\': menuList
        }
    }
}

/*
    menuId       表示当前菜单项 ID
    parentId     表示父菜单项 ID
    name_zh      表示菜单名(中文)
    name_en      表示菜单名(英语)
    url          表示路由跳转路径(自身模块 或者 外部 url)
    type         表示当前菜单项的级别(0: 目录,1: 菜单项,2: 按钮)
    icon         表示当前菜单项的图标
    orderNum     表示当前菜单项的顺序
    subMenuList  表示当前菜单项的子菜单
*/
var menuList = [{
        \'menuId\': 1,
        \'parentId\': 0,
        \'name_zh\': \'系统管理\',
        \'name_en\': \'System Control\',
        \'url\': \'\',
        \'type\': 0,
        \'icon\': \'el-icon-setting\',
        \'orderNum\': 0,
        \'subMenuList\': [{
                \'menuId\': 2,
                \'parentId\': 1,
                \'name_zh\': \'管理员列表\',
                \'name_en\': \'Administrator List\',
                \'url\': \'sys/UserList\',
                \'type\': 1,
                \'icon\': \'el-icon-user\',
                \'orderNum\': 1,
                \'subMenuList\': []
            },
            {
                \'menuId\': 3,
                \'parentId\': 1,
                \'name_zh\': \'角色管理\',
                \'name_en\': \'Role Control\',
                \'url\': \'sys/RoleControl\',
                \'type\': 1,
                \'icon\': \'el-icon-price-tag\',
                \'orderNum\': 2,
                \'subMenuList\': []
            },
            {
                \'menuId\': 4,
                \'parentId\': 1,
                \'name_zh\': \'菜单管理\',
                \'name_en\': \'Menu Control\',
                \'url\': \'sys/MenuControl\',
                \'type\': 1,
                \'icon\': \'el-icon-menu\',
                \'orderNum\': 3,
                \'subMenuList\': []
            }
        ]
    },
    {
        \'menuId\': 5,
        \'parentId\': 0,
        \'name_zh\': \'帮助\',
        \'name_en\': \'Help\',
        \'url\': \'\',
        \'type\': 0,
        \'icon\': \'el-icon-info\',
        \'orderNum\': 1,
        \'subMenuList\': [{
            \'menuId\': 6,
            \'parentId\': 5,
            \'name_zh\': \'百度\',
            \'name_en\': \'Baidu\',
            \'url\': \'https://www.baidu.com/\',
            \'type\': 1,
            \'icon\': \'el-icon-menu\',
            \'orderNum\': 1,
            \'subMenuList\': []
        }, {
            \'menuId\': 7,
            \'parentId\': 5,
            \'name_zh\': \'博客\',
            \'name_en\': \'Blog\',
            \'url\': \'https://www.cnblogs.com/l-y-h/\',
            \'type\': 1,
            \'icon\': \'el-icon-menu\',
            \'orderNum\': 1,
            \'subMenuList\': []
        }]
    }
]

 

 在 index.js 中声明,并开启 mock 拦截。

import * as menu from \'./modules/menu.js\'
fnCreate(menu, true)

 

 

(3)使用
  使用时,通过如下代码可以访问到 mock。
  this.$http 是封装的 axios 请求(详情可见:https://www.cnblogs.com/l-y-h/p/12973364.html)

// 获取动态菜单
this.$http.menu.getMenus().then(response => {
    console.log(response)
})

 

(4)简单测试一下
  在 Login.vue 的登录方法中,简单测试一下。

dataFormSubmit() {
    this.$http.login.getToken().then(response => {
        // 获取动态菜单
        this.$http.menu.getMenus().then(response => {
            console.log(response)
        })
        this.$message({
            message: this.$t("login.signInSuccess"),
            type: \'success\'
        })
        // 保存 token
        setToken(response.data.token)
        this.updateName(this.dataForm.userName)
        console.log(response)
        this.$router.push({
            name: \'Home\'
        })
    })
}

 

 

 

(5)测试结果

 

 

 

3、动态菜单数据国际化问题

(1)问题
  动态菜单数据国际化问题,数据都是保存在数据库中,中文系统下返回英文数据、或者 英文系统下返回中文数据,感觉都有些别扭,所以需要进行国际化处理,某语言系统返回该语言的数据。

(2)解决
思路一:
  从数据库下手,给表维护字段,比如:name-zh、name-en,使用时,根据语言选择对应的字段显示即可。修改时修改语言对应的字段即可,简单明了。
  当然这个缺点也很明显,增加了额外的字段,如果还有其他语言,那么会增加很多额外的字段。

思路二:
  也从数据库下手,额外增加一个语言数据表,里面维护了各种语言下对应的数据。使用时,从这张表查对应的数据即可。修改时也只要修改这张表对应的字段即可。
  缺点也有,毕竟多维护了一张数据表。

思路三:
  数据库不做国际化处理,由前端或者后端去做,数据库维护的是 国际化的变量名。即从数据库取出数据后,根据该数据返回国际化数据。
  缺点嘛,国际化数据都写死了,无法直接修改。需要修改国际化的配置文件。

(3)项目中使用
  在这个项目中,我采用第一种方式,此项目就是一个练手的项目,国际化也就 中英文切换,增加一个额外的字段影响不大。所以 上面 mock 数据中,我使用了 name_zh 表示中文、name_en 表示英语。
  当然,有更好的方式,还望大家不吝赐教。

 

4、修改 router 获取动态菜单数据

(1)思路:
Step1:首先确定获取动态菜单数据的时机。
  一般在登录成功跳转到主页面时,获取动态菜单数据。
  由于涉及到路由的跳转(登录页面 -> 主页面),所以可以使用 beforeEach 进行路由守卫操作。
  但由于每次路由跳转均会触发 beforeEach ,所以为了防止频繁获取动态路由值,可以使用一个全局的布尔型变量去控制,比如 false 表示未获取动态路由,获取动态路由后,将其置为 true,每次路由跳转时,判断该值是否为 true,为 false 就重新获取动态菜单数据并处理。

Step2:其次,处理动态菜单数据,将其变化成 route 的形式。
  route 格式一般如下:
其中:
  path:指路由路径(可以根据路径定位路由)。
  name:指路由名(可以根据路由名定位路由)。
  component:指路由所在的组件。
  children:指的是路由组件中嵌套的子路由。
  meta:用于定义路由的一些元信息。

route = {
    path: \'\',
    name: \'\',
    component: null,
    children: [],
    meta: {}
}

简单举例:
  如下菜单数据转换成 route:
    url 可以转换为 path、name,用于定位路由。
    url 也用于定位 component 组件。
    subMenuList 可以转换为 children,用于设置子路由(当然子路由也进行同样处理)。
    其余的属性,可以放在 meta 中。

【动态菜单数据:】
[{
        \'menuId\': 1,
        \'parentId\': 0,
        \'name_zh\': \'系统管理\',
        \'name_en\': \'System Control\',
        \'url\': \'\',
        \'type\': 0,
        \'icon\': \'el-icon-setting\',
        \'orderNum\': 0,
        \'subMenuList\': [{
                \'menuId\': 2,
                \'parentId\': 1,
                \'name_zh\': \'管理员列表\',
                \'name_en\': \'Administrator List\',
                \'url\': \'sys/UserList\',
                \'type\': 1,
                \'icon\': \'el-icon-user\',
                \'orderNum\': 1,
                \'subMenuList\': []
            }
        ]
    }
]

【转换后:】
[
    path: \'\',
    name: \'\',
    componment: null,
    children: [{
        path: \'sys-UserList\',
        name: \'sys-UserList\',
        componment: () => import(/sys/UserList.vue),
        meta: {
            \'menuId\': 2,
            \'parentId\': 1,
            \'type\': 1,
            \'icon\': \'el-icon-user\',
            \'orderNum\': 1,
            \'name_zh\': \'管理员列表\',
            \'name_en\': \'Administrator List\',
        }
    ]],
    meta: {
        \'menuId\': 1,
        \'parentId\': 0,
        \'type\': 0,
        \'icon\': \'el-icon-setting\',
        \'orderNum\': 0,
        \'name_zh\': \'系统管理\',
        \'name_en\': \'System Control\',
    }
]

 

Step3:使用 addRoute 方法,添加动态路由到主路由上。
  对于转换好的路由数据(route),可以使用 addRoute 将其添加到主路由上。
注(有个小坑,简单说明一下,后续会演示):
  此处若直接使用 addRoute 添加路由,通过 路由实例的 options 方法会无法看到新添加的路由信息,也即无法通过 路由实例去获取动态添加的路由数据。
  若想看到新添加的路由信息,可以指定一个路由实例上的路由,并在其 children 中绑定动态路由,然后通过 addRoute 添加该路由,数据才会被显示。

 

(2)代码实现 -- 获取动态菜单数据:
Step1:确定获取动态菜单数据的时机。
  时机:一般在登录成功并进入主页面时,获取动态菜单并显示。
  由于涉及到路由跳转(登录页面 -> 主页面),所以可以使用 beforeEach 定义一个全局守卫,当从登录页面跳转到主页面时可以触发获取数据的操作。
  当然,有其他方式触发获取数据的操作亦可。

Step2:定义一个全局变量,用于控制是否获取过动态菜单数据。
  由于在 beforeEach 内部定义路由,每次路由跳转均会触发此操作,为了防止频繁获取动态菜单,可以定义一个全局布尔变量来控制是否已经获取过动态菜单。
  可以在 router 实例中 添加一个变量用于控制(isAddDynamicMenuRoutes)。
  通过 router.options.isAddDynamicMenuRoutes 可以获取、修改该值。

// 创建一个 router 实例
const router = new VueRouter({
    // routes 用于定义 路由跳转 规则
    routes,
    // mode 用于去除地址中的 #
    mode: \'history\',
    // scrollBehavior 用于定义路由切换时,页面滚动。
    scrollBehavior: () => ({
        y: 0
    }),
    // isAddDynamicMenuRoutes 表示是否已经添加过动态菜单(防止频繁请求动态菜单)
    isAddDynamicMenuRoutes: false
})

 

Step3:获取动态数据。
  由于之前对主页面的路由跳转,已经定义过一个 beforeEach,用于验证 token 是否存在。
  从登录页面跳转到主页面会触发该 beforeEach,且登录后存在 token,所以可以直接复用。

import http from \'@/http/http.js\'

router.beforeEach((to, from, next) => {
    // 当开启导航守卫时,验证 token 是否存在。
    // to.meta.isRouter 表示是否开启动态路由
    if (to.meta.isRouter) {
        // console.log(router)
        // 获取 token 值
        let token = getToken()
        // token 不存在时,跳转到 登录页面
        if (!token || !/\\S/.test(token)) {
            next({
                name: \'Login\'
            })
        }
        // token 存在时,判断是否已经获取过 动态菜单,未获取,即 false 时,需要获取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    console.log(response)
                })
        }
    }
}

 

当然,需要对代码的逻辑进行一些修改(填坑记录 =_= )。

填坑:
  页面刷新时,动态路由跳转不正确。
  之前为了验证 token,增加了一个 isRouter 属性值,用于给指定路由增加判断 token 的逻辑。
  对于动态路由,页面刷新后,路由会重新创建,即此时动态路由并不存在,也即 isRouter 不存在,从而 if 中的逻辑并不会触发,动态路由并不会被创建,从而页面跳转失败。
  所以在 if 判断中,增加了一个条件 isDynamicRoutes,用于判断是否为动态路由,因为动态路由只存在于主页面中,所以其与 isRouter 的作用(为主页面增加 token 验证逻辑)不矛盾。

  isDynamicRoutes 可以写在公用的 validate.js 中。使用时需要将其引入。
  由于此项目动态路由路径中均包含 DynamicRoutes,所以以此进行正则表达式判断。

/**
 * 判断是否为 动态路由
 * @param {*} s
 */
export function isDynamicRoutes(s) {
    return /DynamicRoutes/.test(s)
}

 

 

import { isDynamicRoutes } from \'@/utils/validate.js\'

// 添加全局路由导航守卫
router.beforeEach((to, from, next) => {
    // 当开启导航守卫时,验证 token 是否存在。
    // to.meta.isRouter 表示是否开启动态路由
    // isDynamicRoutes 判断该路由是否为动态路由(页面刷新时,动态路由没有 isRouter 值,此时 to.meta.isRouter 条件不成立,即动态路由拼接逻辑不能执行)
    if (to.meta.isRouter || isDynamicRoutes(to.path)) {
        // 获取 token 值
        let token = getToken()
        // token 不存在时,跳转到 登录页面
        if (!token || !/\\S/.test(token)) {
            next({
                name: \'Login\'
            })
        }
        // token 存在时,判断是否已经获取过 动态菜单,未获取,即 false 时,需要获取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    console.log(response)
                })
        }
    }
}

 

(3)代码实现 -- 处理动态菜单数据
Step1:
  设置全局布尔变量为 true,上面已经定义了一个 isAddDynamicMenuRoutes 变量,当获取动态菜单成功后,将其值置为 true(表示获取动态菜单成功),用于防止频繁获取菜单。
  然后,再去处理动态菜单数据,此处将处理逻辑抽成一个方法 fnAddDynamicMenuRoutes。

import { isDynamicRoutes } from \'

// 添加全局路由导航守卫
router.beforeEach((to, from, next) => {
    // 当开启导航守卫时,验证 token 是否存在。
    // to.meta.isRouter 表示是否开启动态路由
    // isDynamicRoutes 判断该路由是否为动态路由(页面刷新时,动态路由没有 isRouter 值,此时 to.meta.isRouter 条件不成立,即动态路由拼接逻辑不能执行)
    if (to.meta.isRouter || isDynamicRoutes(to.path)) {
        // 获取 token 值
        let token = getToken()
        // token 不存在时,跳转到 登录页面
        if (!token || !/\\S/.test(token)) {
            next({
                name: \'Login\'
            })
        }
        // token 存在时,判断是否已经获取过 动态菜单,未获取,即 false 时,需要获取
        if (!router.options.isAddDynamicMenuRoutes) {
                http.menu.getMenus().then(response => {
                    // 数据返回成功时
                if (response && response.data.code === 200) {
                    // 设置动态菜单为 true,表示不用再次获取
                    router.options.isAddDynamicMenuRoutes = true
                    // 获取动态菜单数据
                    let results = fnAddDynamicMenuRoutes(response.data.data)
                    console.log(results)
                }
                })
        }
    }
}

 

Step2:
  fnAddDynamicMenuRoutes 用于递归菜单数据。
  getRoute 用于返回某个数据转换的 路由格式。
下面注释写的应该算是很详细了,主要讲一下思路:
  对数据进行遍历,
  定义两个数组(temp,route),temp 用于保存没有子路由的路由,route 用于保存存在子路由的路由。
  如果某个数据存在 子路由,则对子路由进行遍历,并将其返回结果作为当前数据的 children。并使用 route 保存路由。
  如果某个数据不存在子路由,则直接使用 temp 保存路由。
  最后,返回两者拼接的结果,即为转换后的数据。

// 用于处理动态菜单数据,将其转为 route 形式
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
    // 用于保存普通路由数据
    let temp = []
    // 用于保存存在子路由的路由数据
    let route = []
    // 遍历数据
    for (let i = 0; i < menuList.length; i++) {
        // 存在子路由,则递归遍历,并返回数据作为 children 保存
        if (menuList[i].subMenuList && menuList[i].subMenuList.length > 0) {
            // 获取路由的基本格式
            route = getRoute(menuList[i])
            // 递归处理子路由数据,并返回,将其作为路由的 children 保存
            route.children = fnAddDynamicMenuRoutes(menuList[i].subMenuList)
            // 保存存在子路由的路由
            routes.push(route)
        } else {
            // 保存普通路由
            temp.push(getRoute(menuList[i]))
        }
    }
    // 返回路由结果
    return routes.concat(temp)
}

// 返回路由的基本格式
function getRoute(item) {
    // 路由基本格式
    let route = {
        // 路由的路径
        path: \'\',
        // 路由名
        name: \'\',
        // 路由所在组件
        component: null,
        meta: {
            // 开启路由守卫标志
            isRouter: true,
            // 开启标签页显示标志
            isTab: true,
            // iframe 标签指向的地址(数据以 http 或者 https 开头时,使用 iframe 标签显示)
            iframeUrl: \'\',
            // 开启动态路由标志
            isDynamic: true,
            // 动态菜单名称(nameZH 显示中文, nameEN 显示英文)
            name_zh: item.name_zh,
            name_en: item.name_en,
            // 动态菜单项的图标
            icon: item.icon,
            // 菜单项的 ID
            menuId: item.menuId,
            // 菜单项的父菜单 ID
            parentId: item.parentId,
            // 菜单项排序依据
            orderNum: item.orderNum,
            // 菜单项类型(0: 目录,1: 菜单项,2: 按钮)
            type: item.type
        },
        // 路由的子路由
        children: []
    }
    // 如果存在 url,则根据 url 进行相关处理(判断是 iframe 类型还是 普通类型)
    if (item.url && /\\S/.test(item.url)) {
        // 若 url 有前缀 /,则将其去除,方便后续操作。
        item.url = item.url.replace(/^\\//, \'\')
        // 定义基本路由规则,将 / 使用 - 代替
        route.path = item.url.replace(\'/\', \'-\')
        route.name = item.url.replace(\'/\', \'-\')
        // 如果是 外部 url,使用 iframe 标签展示,不用指定 component,重新指定 path、name 以及 iframeUrl。
        if (isURL(item.url)) {
            route[\'path\'] = `iframe-${item.menuId}`
            route[\'name\'] = `iframe-${item.menuId}`
            route[\'meta\'][\'iframeUrl\'] = item.url
        } else {
            // 如果是项目模块,使用 route-view 标签展示,需要指定 component(加载指定目录下的 vue 组件)
            route.component = () => import(`@/views/dynamic/${item.url}.vue`) || null
        }
    }
    // 返回 route
    return route
}

可以查看当前输出结果:

 

 

 

[
    {
        "path": "",
        "name": "",
        "component": null,
        "meta": {
            "isRouter": true,
            "isTab": true,
            "iframeUrl": "",
            "isDynamic": true,
            "name_zh": "系统管理",
            "name_en": "System Control",
            "icon": "el-icon-setting",
            "menuId": 1,
            "parentId": 0,
            "orderNum": 0,
            "type": 0
        },
        "children": [
            {
                "path": "sys-UserList",
                "name": "sys-UserList",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "管理员列表",
                    "name_en": "Administrator List",
                    "icon": "el-icon-user",
                    "menuId": 2,
                    "parentId": 1,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "sys-RoleControl",
                "name": "sys-RoleControl",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "角色管理",
                    "name_en": "Role Control",
                    "icon": "el-icon-price-tag",
                    "menuId": 3,
                    "parentId": 1,
                    "orderNum": 2,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "sys-MenuControl",
                "name": "sys-MenuControl",
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "",
                    "isDynamic": true,
                    "name_zh": "菜单管理",
                    "name_en": "Menu Control",
                    "icon": "el-icon-menu",
                    "menuId": 4,
                    "parentId": 1,
                    "orderNum": 3,
                    "type": 1
                },
                "children": []
            }
        ]
    },
    {
        "path": "",
        "name": "",
        "component": null,
        "meta": {
            "isRouter": true,
            "isTab": true,
            "iframeUrl": "",
            "isDynamic": true,
            "name_zh": "帮助",
            "name_en": "Help",
            "icon": "el-icon-info",
            "menuId": 5,
            "parentId": 0,
            "orderNum": 1,
            "type": 0
        },
        "children": [
            {
                "path": "iframe-6",
                "name": "iframe-6",
                "component": null,
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "https://www.baidu.com/",
                    "isDynamic": true,
                    "name_zh": "百度",
                    "name_en": "Baidu",
                    "icon": "el-icon-menu",
                    "menuId": 6,
                    "parentId": 5,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            },
            {
                "path": "iframe-7",
                "name": "iframe-7",
                "component": null,
                "meta": {
                    "isRouter": true,
                    "isTab": true,
                    "iframeUrl": "https://www.cnblogs.com/l-y-h/",
                    "isDynamic": true,
                    "name_zh": "博客",
                    "name_en": "Blog",
                    "icon": "el-icon-menu",
                    "menuId": 7,
                    "parentId": 5,
                    "orderNum": 1,
                    "type": 1
                },
                "children": []
            }
        ]
    }
]

 

5、使用 addRoute 添加路由:

  通过上面的步骤,已经获取到动态菜单数据,并将其转为路由的格式。
  现在只需要使用 addRoute 将其添加到路由上,即可使用该动态路由。
有个小坑,下面有演示以及解决方法。

 

(1)添加三个基本组件页面,用于测试。
  基本组件页面的位置,要与上面 component 指定的位置相一致。

【MenuControl.vue】
<template>
    <div>
        <h1>Menu Control</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

【RoleControl.vue】
<template>
    <div>
        <h1>Role Control</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

【UserList.vue】
<template>
    <div>
        <h1>User List</h1>
    </div>
</template>

<script>
</script>

<style>
</style>

 

 

 

(2)使用 addRoute 添加路由 -- 基本版
  根据项目需求,自行处理相关显示逻辑(此处只是最简单的版本)。
  由于此处的菜单显示,只是二级菜单。且菜单内容,均显示在 Home.vue 组件中,所以最终路由的格式应该如下所示,所有的路由均显示在 children 中。

{
    path: \'/DynamicRoutes\',
    name: \'DynamicHome\',
    component: () => import(\'@/views/Home.vue\'),
    children: [{
     }]
}

 

想要实现这个效果,得对转换后的动态数据进行进一步的处理。对于第一层菜单数据,需要指定相应的组件,此处为 Home.vue。

// 获取动态菜单数据
let results = fnAddDynamicMenuRoutes(response.data.data)
// 如果动态菜单数据存在,对其进行处理
if (results && results.length > 0) {
    // 遍历第一层数据
    results.map(value => {
        // 如果 path 值不存在,则对其赋值,并指定 component 为 Home.vue
        if (!value.path) {
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            value.component = () => import(\'@/views/Home.vue\')
        }
    })
}
console.log(results)

 

 

 

可以看到路由信息都已完善,此时,即可使用 addRoute 方法添加路由。

// 使用 router 实例的 addRoutes 方法,给当前 router 实例添加一个动态路由
router.addRoutes(results)
// 查看当前路由信息
console.log(router.options)
// 测试一下路由是否能正常跳转,能跳转,则显示路由添加成功
setTimeout(()=> {
    router.push({name : "sys-UserList"})
}, 3000)

 

 

 

  如上图所示,动态路由添加成功,且能够成功跳转。
  但是有一个问题,如何获取到动态路由的值呢?

  一般通过 router 实例对象的 options 方法可以查看到当前路由的信息。(使用 router.options 或者 this.$router.options 可以查看)。

  但是从上图可以看到,动态添加的数据并没有在 options 中显示出来,但路由确实添加成功了。没有看源代码,所以不太清除里面的实现逻辑(有人知道的话,还望不吝赐教)。谷歌搜索了一下,没有找到满意的答案,基本都是说如何在 options 中显示路由信息(=_=),所以此处省略原理,以后有机会再补充,此处直接上解决办法。

 

获取动态路由值一般有两种方式。
  第一种:通过 router 实例对象的 options 方式。
    动态路由使用静态路由的 children 展开,即事先定义好静态路由,添加动态路由时,将该动态路由添加到静态路由 children 上,即可通过 options 显示。 

  第二种:通过 vuex 方式。
    通过 vuex 保存当前动态路由的信息。即单独保存一份动态路由的信息,使用时从 vuex 中获取即可。当然,使用 localStorage、sessionStorage 保存亦可。

 

(3)获取动态路由值 -- 方式一
  通过静态路由的 children 的形式,添加动态路由信息。
Step1:
  定义一个静态路由,当然,router 实例对象中的 routes 也需要修改。

// 用于保存动态路由
const dynamicRoutes = [{
    path: \'/DynamicRoutes\',
    component: () => import(\'@/views/Home.vue\'),
    children: []
}]

// 创建一个 router 实例
const router = new VueRouter({
    // routes 用于定义 路由跳转 规则
    routes: dynamicRoutes.concat(routes),
    // routes,
    // mode 用于去除地址中的 #
    mode: \'history\',
    // scrollBehavior 用于定义路由切换时,页面滚动。
    scrollBehavior: () => ({
        y: 0
    }),
    // isAddDynamicMenuRoutes 表示是否已经添加过动态菜单(防止频繁请求动态菜单)
    isAddDynamicMenuRoutes: false
})

 

Step2:
  修改 addRoutes 规则。
  首先需要将 动态路由添加到 静态路由的 children 方法中。
  然后使用 addRoutes 添加静态路由。

// 如果动态菜单数据存在,对其进行处理
if (results && results.length > 0) {
    // 遍历第一层数据
    results.map(value => {
        // 如果 path 值不存在,则对其赋值,并指定 component 为 Home.vue
        if (!value.path) {
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            value.component = () => import(\'@/views/Home.vue\')
        }
    })
}
// 将动态路由信息添加到静态路由的 children 中
dynamicRoutes[0].children = results
dynamicRoutes[0].name = \'DynamicRoutes\'
// 使用 router 实例的 addRoutes 方法,给当前 router 实例添加一个动态路由
router.addRoutes(dynamicRoutes)
// 查看当前路由信息
console.log(router.options)
// 测试一下路由是否能正常跳转,能跳转,则显示路由添加成功
router.push({name : "sys-UserList"})

 

 

 

  通过上图,可以看到,使用 addRoutes 添加数据到静态路由的 children 后,可以使用 router 实例对象的 options 查看到相应的动态数据。

  但是还是出现了一些小问题,由于静态路由指定了 Home.vue 组件,所以其 children 不应该再出现 Home.vue 组件,否则出现了会出现上图那样的情况(Home.vue 组件中套 Home.vue 组件)。

 

  感觉没有什么好的解决办法,只能粗暴的舍弃一些数据。
  比如舍弃第一层的数据,将其余数据拼接成一个数组,然后添加到静态路由的 children 上。

// 保存动态菜单数据
let temp = []
// 如果动态菜单数据存在,对其进行处理
if (results && results.length > 0) {
    // 舍弃第一层数据
    results.map(value => {
        if (!value.path) {
            temp = temp.concat(value.children)
        }
    })
}
// 将动态路由信息添加到静态路由的 children 中
dynamicRoutes[0].children = temp
dynamicRoutes[0].name = \'DynamicRoutes\'
// 使用 router 实例的 addRoutes 方法,给当前 router 实例添加一个动态路由
router.addRoutes(dynamicRoutes)
// 查看当前路由信息
console.log(router.options)
// 测试一下路由是否能正常跳转,能跳转,则显示路由添加成功
router.push({name : "sys-UserList"})

 

 

 

当然,不舍弃第一层数据,将其全部拼接成一个数组亦可。

// 保存动态菜单数据
let temp = []
// 如果动态菜单数据存在,对其进行处理
if (results && results.length > 0) {
    // 将全部数据拼接成一个数组
    results.map(value => {
        if (!value.path) {
            temp = temp.concat(value.children)
            value.children = []
            value.path = `/DynamicRoutes-${value.meta.menuId}`
            value.name = `DynamicHome-${value.meta.menuId}`
            temp = temp.concat(value)
        }
    })
}
// 将动态路由信息添加到静态路由的 children 中
dynamicRoutes[0].children = temp
dynamicRoutes[0].name = \'DynamicRoutes\'
// 使用 router 实例的 addRoutes 方法,给当前 router 实例添加一个动态路由
router.addRoutes(dynamicRoutes)

以上是关于SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇:使用 vue-router 进行动态加载菜单的主要内容,如果未能解决你的问题,请参考以下文章

在vue中用了elemen插件,怎么让复选改为单选

vue+elemen把时间作为参数搜索数据注意一点

vue + elemen可远程搜索select选择器的封装(思路及源码分享)

10.17 elemen.js

基于Vue和elemen-ui做一个点击文字显示图片功能并封装成组件

在Vue+element 开发中报: The template root requires exactly one elemen 错的解决和原因