• Vue动态路由配置


    需求

    前端页面用户登录成功时,接口返回用户菜单,菜单里面包含新增的路由信息,将菜单解析成新增路由,并将其添加到router里面,以及进行持久化操作

    配置alias

    vue项目在经过编译打包之后,后期无法使用import导入组件,并且项目路径发生了改变,也无法简单使用require动态导入组件,这里解决方法是配置alias,同时用resolve配合require一起使用。

    配置如下:

    安装webpack(已安装则跳过),这里可以直接复制以下代码到package.json文件中的"devDependencies"属性中,重新install即可。

        "webpack": "^5.37.0",
        "webpack-bundle-analyzer": "^4.5.0",
        "webpack-cli": "^4.9.0",
        "webpack-dev-server": "^3.11.2",
        "webpack-merge": "^5.7.3",
        "webpackbar": "^5.0.0-3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    复制以下代码到vue.config.js配置文件中,可以使用开启alia功能,使用@符号替代src

    const { defineConfig } = require('@vue/cli-service')
    
    const path = require('path');//引入path模块
    
    function resolve(dir){
      return path.join(__dirname,dir)//path.join(__dirname)设置绝对路径
    }
    
    module.exports = defineConfig({
      transpileDependencies: true,
      devServer:{
        port:8080,
        //port:80,
        // proxy: {                 //设置代理,必须填
        //     '/api': {              //设置拦截器  拦截器格式   斜杠+拦截器名字,名字可以自己定
        //         target: 'http://localhost:9999',     //代理的目标地址(后端设置的端口号)
        //         changeOrigin: true,              //是否设置同源,输入是的
        //         pathRewrite: {                   //路径重写
        //             '/api': ''                     //选择忽略拦截器里面的单词
        //         }
        //         /*也就是在前端使用/api可以直接替换为(http://localhost:9090)*/
        //     }
        // }
      },
      chainWebpack:(config)=>{
        config.resolve.alias
            .set('@',resolve('./src'))
            .set('components',resolve('./src/components'))
            .set('views',resolve('./src/views'))
            .set('assets',resolve('./src/assets'))
        //set第一个参数:设置的别名,第二个参数:设置的路径
      },
      publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
      outputDir: 'dist',
      assetsDir: 'static',
      productionSourceMap: false,
      lintOnSave: false
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    router配置

    src/router/index.js文件

    import Router from 'vue-router'
    import store from "../store/index";
    
    //菜单根页面
    const menuRoot={
        path:'/home',
        name:'home',
        component:()=>import('@/views/home/Home'),
        redirect:'/home/index',
        meta:{
            title: '首页',
        }
    }
    
    //最基本的路由
    const commonRouter=[
        {
            path: "/",
            redirect: "/login"
        },
        {
            path:'/login',
            name:'login',
            component:()=>import('@/views/login/Login.vue'),
            meta:{
                title:'登录'
            }
        },
        {
            path:'/404',
            name:'404',
            component:()=>import('@/views/error/404'),
            meta:{
                title:'404'
            }
        }
    ]
    
    //这个路由在接口返回之后再添加,不然会出现页面刷新之后直接重定向到404,没机会添加动态路由
    const Router_404={
        path:'*',
        redirect: '/404'
    }
    
    
    //将的菜单树结构转换成路由,直接滑到下面看一下接口返回值格式更容易理解
    //menus:登录成功时返回的menus
    //res:接口返回值
    //parentPath:父级路径多级拼接而成,分隔符为 /
    //parentName:父级路径名,多级拼接而成,分隔符为 -
    const menuRouter=function(menus,res,parentPath,parentName){
        res=res||[];
        menus&&menus.forEach(elem=>{
            const path=!parentPath?elem.path:parentPath+'/'+elem.path;
            const name=!parentName?elem.name:parentName+'-'+elem.name;
            if(!elem.children){
                let obj={...elem};
                obj.path=path;
                obj.name=name;
                obj.component=resolve=>(require([`components/${elem.component}`],resolve));
                //注意这里的components是上面配置的alias路径别名,是路径./src/components的别名,新增的组件我都放在了该路径下,可以自己修改alias里面映射的路径
                res.push(obj);
            }else{
                menuRouter(elem.children,res,path,name);
            }
        })
        return res;
    }
    
    
    //创建最基本的路由
    const createRouter = () => {
        return new Router({
            // mode: 'history', // require service support
            mode:'history',
            scrollBehavior: () => ({ y: 0 }),
            routes: commonRouter
        })
    }
    
    let router=createRouter();
    
    //重置路由
    export function resetRouter() {
        const newRouter = createRouter()
        router.matcher = newRouter.matcher // reset router
    }
    
    //根据用户菜单创建存在登录状态的用户路由,menus:登录接口返回的菜单
    export async function createMenuRouter(menus){
        //重置路由
        resetRouter();
        return new Promise((resolve)=>{
            
            const menuRouterTemp={...menuRoot};
            
            menuRouterTemp.children=menuRouter(menus);
            
            //添加新增的路由
            router.options.routes.push(menuRouterTemp);
            
            //添加新增的路由
            router.addRoute(menuRouterTemp);
            
            //添加path匹配不到时重定向到404路由
            router.addRoute(Router_404);
            
            /*
            	如果要添加根路由:
            				1.router.options.routes.push(新增的根路由)
            				2.router.addRoute(新增的根路由)
            				
            	如果要往一个指定路由中添加子路由,需要进行两步操作:
            				1.router.options.routes中找到该指定路由(dfs或bfs)
            				2.往该路由的children里面push你要添加的子路由
            				3.找到该指定路由的根路由,添加新增的子路由,使用router.addRoute重新载入该路由
            */
            resolve();
        })
    }
    
    router.beforeEach((to,from,next)=>{
        if(to.path==='/login'||to.path==='/404') {
            next();
        }else{
            if(store.getters.token){
                let menuRouterIsExist=false,item=router.options.routes;
                for(let index in item){
                    if(item[index].name==='home'){
                        menuRouterIsExist=true;
                        break;
                    }
                }
                //如果Cookie中有新增的路由
                if(!menuRouterIsExist){
                    //重新载入新增的路由
                    createMenuRouter(store.getters.menus).then(()=>{
                        //载入成功,替换页面,放行
                        next({...to, replace:true})
                    }).catch(()=>{
                        //载入失败
                        next('/login');
                    })
                }else{
                    //有新增的路由,放行
                    next();
                }
            }else{
                next('/login');
            }
        }
    })
    
    router.afterEach((to)=>{
        if(!to.meta.title){
            document.title = to.meta.title //修改网页的title
        }
    })
    
    export default router;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    main.js中导入路由

    ...
    import router from "./router";
    ...
    new Vue({
      render: h => h(App),
      router,
      store,
      beforeCreate() {
        Vue.prototype.$bus=this;
      }
    }).$mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    路由持久化

    安装js-cookie

    npm install --save js-cookie
    
    • 1

    将接口返回的路由信息存到Vuexcookie

    src/store/moudles/user.js文件:

    import request from "../../http/request";
    import Cookies from 'js-cookie'
    import {createMenuRouter} from "@/router";
    
    const state={
        token:Cookies.get('Authorization')||null,
        name:'',
        account:'',
        roles:[],
        menus:Cookies.get('menus')?JSON.parse(Cookies.get('menus')):[]
    }
    
    const mutations={
        setToken(state,token){
            state.token=token;
        },
        setName(state,name){
            state.name=name;
        },
        setAccount(state,account){
            state.account=account
        },
        setRoles(state,roles){
            state.roles=roles
        },
        setMenus(state,menus){
            state.menus=menus;
        }
    }
    
    const actions={
    	//登录
        async login({commit},{account,password}){
            return new Promise((resolve,reject)=>{
                request.post('user/login',{
                    account,
                    password
                }).then(res=>{
                    if(res.code===200){
                        Cookies.set('Authorization',res.data.token,{expires:1});
                        Cookies.set('menus',JSON.stringify(res.data.menus),{expires:1});
                        commit('setToken',res.data.token);
                        commit('setMenus',res.data.menus);
                        //更新路由
                        createMenuRouter(res.data.menus)
                    }
                    resolve({
                        code:res.code,
                        msg:res.msg
                    })
                }).catch(error=>{
                    reject(error)
                })
            })
        }
    }
    
    export default {
        namespaced:true,
        state,
        mutations,
        actions
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    src/store/getters.js文件:

    const getters={
        token:state=>state.user.token,
        name:state=>state.user.name,
        account:state=>state.user.account,
        roles:state=>state.user.roles,
        menus:state=>state.user.menus
    }
    export default getters
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    src/store/index.js文件:

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    
    import getters from "./getters";
    
    import user from './moudules/user'
    
    
    
    const modules= {
        user
    }
    
    export default new Vuex.Store({
        modules,
        getters
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    登录模块

    	  this.loginLoading=true;
          this.$store.dispatch('user/login',this.loginForm).then(res=>{
            if(res.code===200){
              this.$message.success(res.msg);
              setTimeout(()=>{
                router.push('/home');
              },200);
            } else{
              this.$message.error(res.msg);
            }
            this.loginLoading=false;
          }).catch(()=>{
            this.$message.error("登录失败");
            this.loginLoading=false;
          })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    接口返回值

    user/login接口返回值如下
    menus数据里面只有叶子节点有component属性,最后会将多级菜单压缩成一层路由,并将该层路由赋值给指定路由的children属性
    menus数据还用于渲染管理页面的左侧菜单

    {
      code: 200,
      msg: "登录成功",
      data: {
        token:'abcdefg',
        menus: [
                {
                    "path": "index",
                    "name": "indexxx",
                    "component": "Index",
                    "meta": {
                        "title": "首页",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "menu1",
                    "name": "menu1",
                    "meta": {
                        "title": "多级菜单1",
                        "icon": "el-icon-news"
                    },
                    "children": [
                        {
                            "path": "subMenu1",
                            "name": "subMenu1",
                            "component": "TestOne",
                            "meta": {
                                "name": "测试名1",
                                "title": "测试标题",
                                "icon": "el-icon-location"
                            }
                        },
                        {
                            "path": "subMenu2",
                            "name": "subMenu2",
                            "meta": {
                                "name": "测试名2",
                                "title": "测试标题2",
                                "icon": "el-icon-location"
                            },
                            "children": [
                                {
                                    "path": "subMenu1",
                                    "name": "subMenu1",
                                    "component": "TestOne",
                                    "meta": {
                                        "name": "测试名1",
                                        "title": "测试标题",
                                        "icon": "el-icon-location"
                                    }
                                },
                                {
                                    "path": "subMenu2",
                                    "name": "subMenu2",
                                    "component": "TestTwo",
                                    "meta": {
                                        "name": "测试名2",
                                        "title": "测试标题2",
                                        "icon": "el-icon-location"
                                    }
                                }
                            ]
                        }
                    ]
                },
                {
                    "path": "index12",
                    "name": "index12",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index122",
                    "name": "index122",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index123",
                    "name": "index123",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index124",
                    "name": "index124",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index125",
                    "name": "index125",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index126",
                    "name": "index126",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index127",
                    "name": "index127",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index128",
                    "name": "index128",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index129",
                    "name": "index129",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index1211",
                    "name": "index1211",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index1212",
                    "name": "index1212",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                },
                {
                    "path": "index1213",
                    "name": "index1213",
                    "component": "Index",
                    "meta": {
                        "title": "首页2",
                        "icon": "el-icon-location"
                    }
                }
            ]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177

    菜单组件

    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    
          
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    效果演示

    该项目存在一定的保密性,所以这里只能放出动态路由代码,这里给出效果演示
    如图所示:login界面有三个根路由,这个404路由基本上用不到,也可以登录成功时再加上去
    在这里插入图片描述
    如图所示:这是登录成功之后的页面
    在这里插入图片描述

  • 相关阅读:
    python基础 --- 爬虫前篇
    6个步骤强化 CI/CD 安全
    OneFlow v0.9.0正式发布
    C与CPP常见编译工具链与构建系统简介
    Python学习小组课程P6-Python办公(3)邮件与钉钉消息通知
    质数(素数)prime :只能被 1 和 它本身整除的自然数,不可再分,(三种方式求出质数)
    Spring @FeignClient
    Linux 防火墙 firewalld 常用命令
    java基础巩固14
    CPP的uint32_t类型简介
  • 原文地址:https://blog.csdn.net/qq_16525829/article/details/126790339