Vue 利用 addRoutes 动态创建路由,并在页面刷新后保留动态路由的完整 Demo
这个包含登录之后对于路由的处理,动态添加,以及刷新后的为了防止动态路由丢失做的操作。这只是个小 demo,因为路由这块儿牵扯到的内容还挺多的,比如权限限制,Token 验证等等操作,这些都需要根据具体项目具体处理。当然这个 Demo 我个人觉得结构还算清晰,不会影响项目中其他业务逻辑的处理,该单独封装的,都封装了。
一、部分目录结构
src
components
content.vue
home.vue
login.vue
menu.vue
page-1.vue
page-2.vue
page-3.vue
router
index.js
async.js
store
index.js
二、初始状态下的代码
1. 部分组件
<template>
<div id="app">
<router-view />
div>
template>
...
<template>
<div class="login">
<button @click="login()">登录button>
div>
template>
<script>
export default {
name: "login",
methods: {
login() {
}
}
}
script>
...
<template>
<div class="home">
<Menu />
<Content />
div>
template>
<script>
import Menu from './menu'
import Content from './content'
export default {
name: "home",
components: {
Menu, Content
}
}
script>
...
<template>
<div class="menu">
菜单列表
div>
template>
<template>
<div class="content">
<router-view />
div>
template>
...
至于 page-1
到 page-3
属于动态路由的测试界面,随便写一些测试内容就可以了。
2. 路由
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);
export const defaultRoutes = [{
path: '/login',
name: 'login',
component: () => import('@/components/login.vue')
}];
const router = new Router({
routes: [
...defaultRoutes
]
});
export default router
三、根据登录后获取的用户路由列表动态创建路由和菜单
- 处理菜单的前一个步骤是登录或者获取菜单的
http
请求,这里假设已经请求成功并拿到了菜单,菜单也可能不是这个格式,如果不是,最终要处理成这种格式;
- 注意要提前约定好返回的数据格式,尽量和路由格式保持一致;
- 当然如果因为某些因素,格式和路由默认格式不统一,那就要单独将路由处理成我们想要的格式;
- 路由对应的组件不可能以组件的形式存在于数据库中,所以获取到的路由列表中,
component
的呈现形式是一个路径,如果路径只是文件名或其他格式,在处理菜单时,统一拼接成我们需要的格式;
- 这个 menu 格式我只写了一部分简单的必须内容,具体包含的内容需要根据项目需求确定,很大可能在
meta
中包含权限列表等数据;
- 如果项目嵌套层级复杂,不能确定层级数,需要用递归去处理;
...
methods: {
login() {
const menu = [
{
path: '/',
name: 'index',
component: 'components/home',
meta: {
require: false
},
children: [
{
path: '/page-1',
name: 'page1',
meta: {
title: '页面一'
},
component: 'components/page-1'
}, {
path: '/page-2',
name: 'page2',
meta: {
title: '页面二'
},
component: 'components/page-2'
}, {
path: '/page-3',
name: 'page3',
meta: {
title: '页面三'
},
component: 'components/page-3'
}
]
}
];
this.$store.commit('SET_ROUTES', menu);
this.$router.push('/');
}
}
...
import Vue from 'vue'
import Vuex from 'vuex'
import {setAsyncRoutes} from "../router/async"
Vue.use(Vuex);
export default new Vuex.Store({
state: {
routes: []
},
mutations: {
SET_ROUTES(state, routes) {
setAsyncRoutes(routes);
sessionStorage.setItem('menu', JSON.stringify(routes));
state.routes = routes;
}
}
});
import router from "./index";
import { defaultRoutes } from "./index"
export const getAsyncRoutes = arr => {
return arr.map(({path, name, component, meta, children}) => {
const route = {
path,
name,
meta: {
...meta,
require: true
},
component: () => import(`@/${component}.vue`)
};
if(children) {
route.redirect = children[0].path;
route.children = getAsyncRoutes(children);
}
return route;
});
};
export const setAsyncRoutes = menu => {
const _menu = getAsyncRoutes(menu);
router.addRoutes(_menu);
router.options.routes = defaultRoutes.concat(_menu);
};
<template>
<div class="menu">
<router-link
v-for="item in menuList"
:key="item.name"
:to="item.path"
>
{{item.meta.title}}
router-link>
div>
template>
<script>
export default {
name: "menu",
computed: {
menuList() {
return this.$store.state.routes[0].children || [];
}
},
created() {
const menu = JSON.parse(sessionStorage.getItem('menu'));
if(menu) this.$store.commit('SET_ROUTES', menu);
}
}
script>
其实这个时候路由已经可以用了,但是为了防止用户刷新之后动态创建的路由丢失,需要在 router 中做一次处理
import {setAsyncRoutes} from "./async"
...
router.beforeEach((to, from ,next) => {
if(!to.meta.require && to.path !== '/login') {
const menu = JSON.parse(sessionStorage.getItem('menu'));
setAsyncRoutes(menu);
router.replace(to.path);
} else next();
});
...