源码下载
大家可以直接微信扫描上面的二维码关注我的公众号,然后回复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": "林泽锋 <282245889@qq.com>",
"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,我们会看到如下的页面则说明我们的动态路由已经搞定了。
转载:https://blog.csdn.net/linzhefeng89/article/details/103953902