基于vue-router的从后端动态加载菜单的实现

源码下载

在这里插入图片描述
大家可以直接微信扫描上面的二维码关注我的公众号,然后回复20200113 里面就会给到源代码的下载地址同时会附上相应的视频教程,并定期的与大家分享相关的技术文章。

前言

在前后端分离架构的开发过程中,我们有两种方式来管理的我们的路由,最常见的一种方式就是直接将配置写在前端的代码中,这种方式写起来十分的方便,但是当我们需要做到一些动态菜单的时候就没办法实现了【比如动态的配置相关的报表页面,也许有人会说我们的报表每个都是定制化开发的,那你就用第一种实现方式吧】。,这时候我们就需要用到我们的另外一种的路由实现方式,就是从后端直接读取当前用户的路由信息进行前端的动态加载。

后端模拟加载菜单的例子实现

后端我们直接使用intellij 创建一个spring-boot工程,非常的简单就一个【VueRouterController.java】路由请求的controller、【CorsConfig.java】放开跨域请求的配置以及最后一个application.yml的配置。

VueRouterController.java

package com.vue.router.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author linzf
 * @since 2020/1/13
 * 类描述:
 */
@RestController
@RequestMapping("menu")
public class VueRouterController {

    /**
     * 功能描述: 加载菜单
     * @return
     */
    @PostMapping("loadMenu")
    public Map<String,Object> loadTree(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Map<String,Object> result = new HashMap<>(3);
        result.put("code",200);
        result.put("msg","菜单加载成功!");
        List<Map<String,Object>> obj = new ArrayList<>();
        Map<String,Object> tree = new HashMap<>(3);
        tree.put("routerPath","views/1.vue");
        tree.put("name","hello1");
        tree.put("path","/hello1");
        obj.add(tree);
        tree = new HashMap<>(3);
        tree.put("routerPath","views/2.vue");
        tree.put("name","hello2");
        tree.put("path","/hello2");
        obj.add(tree);
        tree = new HashMap<>(3);
        tree.put("routerPath","views/3.vue");
        tree.put("name","hello3");
        tree.put("path","/hello3");
        obj.add(tree);
        result.put("obj",obj);
        return result;
    }

}

CorsConfig.java

package com.vue.router.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author linzf
 * @since 2019/4/25
 * 类描述:
 */
@Configuration
public class CorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        // 它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,也可以设置为*号支持全部
                        .allowedHeaders("*")
                        // 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。
                        .allowedMethods("*")
                        // 本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求
                        .allowedOrigins("*")
                        // 该字段可选,用来指定本次预检请求的有效期,单位为秒
                        .maxAge(1728000);
            }
        };
    }

}

application.yml

spring:
  application:
    name: router-demo

server:
  port: 8989

验证结果

我们直接运行当前的项目,然后我们使用我们的postman工具直接访问:http://127.0.0.1:8989/menu/loadMenu我们会看到如下页面则说明我们的后端工程就构建完成了:在这里插入图片描述

前端例子的实现

首先我们需要创建一个基于vue的前端工程,如果不懂的如何创建大家可以直接点击【spring boot +iview 前后端分离架构之前端工程的构建【CMD版】】去创建前端工程,创建过程如下:
在这里插入图片描述
创建完成以后我们直接使用我们的开发工具打开我们的前端工程,然后修改我们的package.json文件,修改完成以后代码如下:

{
  "name": "vue-router-demo-front",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "林泽锋 <[email protected]>",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js"
  },
  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "axios": "^0.15.3",
    "axios-mock-adapter": "^1.16.0",
    "mockjs": "^1.0.1-beta3"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

然后我们在前端工程的package.json目录执行以下命令:

cnpm install

编写axios工具

与后端建交互我们直接使用的是axios,直接在我们的前端工程的src目录底下创建一个lib目录,同时创建我们的通讯封装js【axios.js】代码如下:

import Axios from 'axios';

class httpRequest {

  constructor() {
    this.options = {
      method: '',
      url: ''
    };
    // 存储请求队列
    this.queue = [];
  }


  // 销毁请求实例
  destroy(url) {
    delete this.queue[url];
    const queue = Object.keys(this.queue);
    return queue.length;
  }

  // 请求拦截
  interceptors(instance, url) {
    // 添加请求拦截器
    instance.interceptors.request.use(config => {
      return config
    }, error => {
      // 对请求错误做些什么
      return Promise.reject(error);
    });

    // 添加响应拦截器
    instance.interceptors.response.use((res) => {
      let {data} = res;
      return data;
    }, (error) => {
      message.error('服务内部错误');
      // 对响应错误做点什么
      return Promise.reject(error);
    })
  }

  // 创建实例
  create() {
    let conf = {
      baseURL: "http://127.0.0.1:8989/",
      timeout: 5000,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'X-URL-PATH': location.pathname
      }
    };
    return Axios.create(conf);
  }

  // 请求实例
  request(options) {
    let instance = this.create();
    this.interceptors(instance, options.url);
    options = Object.assign({}, options);
    this.queue[options.url] = instance;
    return instance(options);
  }
}

export default httpRequest;

接着封装【api.request.js】代码如下:

import HttpRequest from './axios';
const axios = new HttpRequest();
export default axios;

最后编写接口调用通用工具【base.js】代码如下:

import axios from './api.request';
import qs from 'qs';
let Axios;

if (process.env.NODE_ENV === 'mock') {
  Axios = require('axios');
  Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
}


export function fetch(url, params = {}) {
  if (process.env.NODE_ENV === 'mock') {
    return new Promise((resolve, reject) => {
      Axios.post(url, params)
        .then(response => {
          resolve(response.data);
        })
        .catch((error) => {
          reject(error);
        })
    })
  } else {
    return new Promise((resolve, reject) => {
      axiosPost(url,params,resolve)
    });
  }
}

// 递归调用,保证在token过期刷新token的时候可以实现请求的二次发送
function axiosPost(url,params,resolve){
  axios.request({
    url: url,
    data: qs.stringify(params),
    method: 'post'
  }).then(res => {
      resolve(res);
  })
}

编写API接口

在src目录底下创建一个api/sys/menu目录,然后创建一个api.menu.js代码如下:

import {fetch} from '../../../lib/base';

// 获取菜单数据
export const loadMenu = params => {
  return fetch('/menu/loadMenu',params);
};

编写测试页面

接着我们需要在src底下创建一个iview文件夹,同时编写三个测试页面,分别是1.vue、2.vue和3.vue,代码如下所示:

1.vue

<template>
  <div>
    <div>
      这是第一个页面
    </div>
    <div>
      <button @click="handleClick('hello2')">第二个页面</button>
      <button @click="handleClick('hello3')">第三个页面</button>
    </div>
  </div>
</template>
<script>
    export default {
        name: 'hello1',
        methods:{
            handleClick(key) {
                this.$router.push({
                    name: key
                }).catch(err => {
                    console.log('err=>', err);
                })
            }
        }
    }
</script>

2.vue

<template>
  <div>
    <div>
      这是第二个页面
    </div>
    <div>
      <button @click="handleClick('hello1')">第一个页面</button>
      <button @click="handleClick('hello3')">第三个页面</button>
    </div>
  </div>
</template>
<script>
    export default {
        name: 'hello2',
        methods:{
            handleClick(key) {
                this.$router.push({
                    name: key
                }).catch(err => {
                    console.log('err=>', err);
                })
            }
        }
    }
</script>

3.vue

<template>
  <div>
    <div>
      这是第三个页面
    </div>
    <div>
      <button @click="handleClick('hello1')">第一个页面</button>
      <button @click="handleClick('hello2')">第二个页面</button>
    </div>
  </div>

</template>
<script>
    export default {
        name: 'hello3',
        methods:{
            handleClick(key) {
                this.$router.push({
                    name: key
                }).catch(err => {
                    console.log('err=>', err);
                })
            }
        }
    }
</script>

改造App.vue实现动态加载路由

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
    import {loadMenu} from './api/sys/menu/menu.api';

    export default {
        name: 'App',
        methods: {
            init() {
                loadMenu().then(res => {
                    let router = [];
                    for (let i = 0; i < res.obj.length; i++) {
                        let item = res.obj[i];
                        console.log('这是请求返回以后的数据', item.routerPath);
                        item.component = resolve => require([`@/` + item.routerPath], resolve);
                        router.push(item);
                    }
                    this.$router.options.routes.push(...router);
                    this.$router.addRoutes(this.$router.options.routes);
                    console.log("this.$router.options.routes=>", this.$router.options.routes);
                })
            },
            generateRoutesFromMenu (menu = [], routes = []) {

            }
        },
        mounted() {
            this.init();
        }
    }
</script>

<style>
  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>

验证动态路由

最后我们就需要验证我们的动态路由是否已经OK了,启动我们的后端工程和前端工程,然后我们直接在浏览器中输入以下的地址:http://localhost:8080/#/hello1,我们会看到如下的页面则说明我们的动态路由已经搞定了。
在这里插入图片描述

发布了128 篇原创文章 · 获赞 72 · 访问量 113万+

猜你喜欢

转载自blog.csdn.net/linzhefeng89/article/details/103953902