想必开发过后端管理项目的同学都了解过vue-element-admin这个框架吧,那么我们今天将告诉大家如何把该框架的路由修改成动态的。
话不多说 直接开始!!!!
1.先到GitHub上下载该框架:github.com/PanJiaChen/…
注意:这是基础模板的下载路径。
2.在项目根目录下面输入 npm i 安装依赖,然后 npm run dev 把项目跑起来
3.我们先缕清思路,所谓的动态路由就是:从后端获取到路由数据=>处理成router能识别的格式=>然后通过 router.addRoutes方法将其添加到路由中即可。
我们需要修改的页面有store文件夹下的user.js getters.js、router文件夹下的index.js 、src下的permission.js、layout下的index.vue。
一、修改user.js成下面这样
import {login,logout,getInfo} from '@/api/user'
import {getToken,setToken,removeToken} from '@/utils/auth'
import {resetRouter} from '@/router' // 引用重置路由的方法 非常重要!!!
const getDefaultState = () => {
return {
token: getToken(),
menus: [], // 菜单权限
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_MENUS: (state, menus) => {
state.menus = menus // 菜单权限
}
}
const actions = {
// user login 调用登录接口获取到token 我这里先是写死的 具体看个人情况
login({
commit
}, userInfo) {
const {
username,
password
} = userInfo
return new Promise((resolve, reject) => {
// login({
// username: username.trim(),
// password: password
// }).then(response => {
// const {
// data
// } = response
// commit('SET_TOKEN', data.token)
// setToken(data.token)
// resolve()
// }).catch(error => {
// reject(error)
// })
const toekn = 'Bearer 928|vjqDa6oj3pFAk0LPUKUvajTITUkCa7IdRBdfE6gK'
commit('SET_TOKEN', toekn)
setToken(toekn)
resolve()
})
},
// get user info 关键部分在这里:通过后端接口把路由数据获取到,并且存到store里面,方便其他文件读取。
getInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
console.log(response);
const data = response.data
if (!data) {
return reject('Verification failed, please Login again.')
}
const menus = data.routers;
commit('SET_MENUS', menus) // 获取到路由信息并且存进去
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({
commit,
state
}) {
return new Promise((resolve, reject) => {
// logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
// }).catch(error => {
// reject(error)
// })
})
},
// remove token
resetToken({
commit
}) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
二、修改getters.js成下面这样
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
menus: state => state.user.menus, // 菜单权限
}
export default getters
三、修改router下的index.js成下面这样
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
// 404 page must be placed at the end !!!
// { path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export const error404= { path: '*', redirect: '/404', hidden: true };
export default router
把多余的路由删掉,只保留login、404和首页部分就可以,因为其他的页面我们是动态加进去的。
注意:这里把404的路由导出去:export const error404= { path: '*', redirect: '/404', hidden: true }; 后面permission.js文件要用到
三、修改src下的permission.js成下面这样
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress"; // progress bar
import "nprogress/nprogress.css"; // progress bar style
import { getToken } from "@/utils/auth"; // get token from cookie
import getPageTitle from "@/utils/get-page-title";
import {error404} from './router'
import Layout from "@/layout"; // 引入Layout
const _import = require('@/router/_import_development'); // 引入获取组件的方法
NProgress.configure({ showSpinner: false }); // NProgress Configuration
const whiteList = ["/login"]; // no redirect whitelist
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start();
// set page title
document.title = getPageTitle(to.meta.title);
// determine whether the user has logged in
const hasToken = getToken();
if (hasToken) {
if (to.path === "/login") {
// if is logged in, redirect to the home page
next({ path: "/" });
NProgress.done();
} else {
const hasGetUserInfo = store.getters.name;
if (hasGetUserInfo) {
next();
} else {
try {
// get user info
await store.dispatch("user/getInfo");
// **在这里做动态路由**
if (store.getters.menus.length < 1) {
global.antRouter = [];
next();
}
const menus = filterAsyncRouter(store.getters.menus); // 过滤路由
router.addRoutes([...menus,error404]); // 动态添加路由 把404放到最后面 防止刷新页面的时候跳转404页面
global.antRouter = menus; // 将路由数据传递给全局变量,做侧边栏菜单渲染工作
next({ ...to, replace: true });
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch("user/resetToken");
Message.error(error || "Has Error");
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next();
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
function filterAsyncRouter(asyncRouterMap) {
let arr = [];
let father = {};
let children = {};
asyncRouterMap.forEach((item) => {
// 判断是否有子级
if (item.children && item.children.length) {
// 处理父级
father = {
path: "/" + item.path,
component: Layout,
redirect: "/" + item.path + "/" + item.children[0].path,
name: item.meta.title,
meta: { title: item.meta.title, icon: item.meta.icon },
children: [],
};
// 处理子级
item.children.forEach((ele) => {
children = {
path: ele.path,
name: ele.meta.title,
component: _import(ele.component), // 动态写法 避免无法进入到页面的情况
meta: { title: ele.meta.title, icon: ele.meta.icon },
};
father.children.push(children);
});
} else {
// 如果没有子级 直接处理父级
father = {
path: "/" + item.path,
component: Layout,
children: [
{
path: "",
name: item.meta.title,
// component: () => import('@/views/' + item.path + '/index'),
component: _import(item.path), // 动态写法 避免无法进入到页面的情况
meta: { title: item.meta.title, icon: item.meta.icon },
},
],
};
}
arr.push(father);
});
return arr;
}
router.afterEach(() => {
// finish progress bar
NProgress.done();
});
注意:在该文件夹下面引入了两个东西:
import Layout from "@/layout"; const _import = require('@/router/_import_development');
看到这里想必会有小伙伴疑惑这个_import是什么东东?
答:因为在循环处理路由数据的时候,路由的格式只能是动态的,如果我们使用常规的字符串拼接路由,那么将导致router识别不出来,继而报错。所以我们在router文件夹下面创建一个文件并且命名为:_import_development.js。循环处理路由的时候会调用该文件。该文件内容如下:
module.exports = file => {
return (resolve) => require([`@/views/${file}`], resolve)
}
filterAsyncRouter()这个函数就是用来处理路由格式的,你们可以根据后端给的路由数据做调整
四、修改layout下的index.vue改成下面这样
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'sidebar'
]),
routes() {
return this.$router.options.routes.concat(global.antRouter);
},
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
其实这个文件只修改了return this.$router.options.routes.concat(global.antRouter); 一行代码。通过concat方法把我们在permission.js文件里面处理好的路由数组合并到一起