先看效果:
在权限管理中,可更改任意菜单的路由路径(path)、组件(component)、重定向(redirect) 等属性,也可自定义权限(菜单) 的层级关系(在本文中权限与菜单的意义是一样的)
如何实现该方案:
将所有的路由对象都存放在数据库中,在每次加载页面前去获取菜单数组,菜单数组需要赋值给两个组件,一个是页面侧栏 el-menu ,一个则是 vue-router;在第一张图的权限管理页面中又是一次独立的查询,不管菜单隐藏或禁用与否,全部查出来。
el-menu 负责展示(其中el-menu需进行递归);在赋值 vue-router 之前也需要递归加载 component,如果component 不存在,则赋一个
<router-view/>
,如果某个菜单下有子菜单,则该菜单的 component 一定为<router-view/>
demo git地址,分支为 release-1.0
代码细节部分:
router.js 在该js中定义了获取菜单与递归加载 component 的方法
// 定义初始的路由对象,systemFrame页面中定义了侧栏与其他控件
// 不能被用户改变的路由对象都应放在内
const defaultRouter = [{
_id: 'systemFrame',
component: () => import('../components/systemFrame'),
hidden: true,
name: 'systemFrame',
path: '/',
iconCls: 'el-icon-s-tools',
}];
const getRouter = () => new Promise((resolve) => {
axiosUtil.postPromise('/system/menu/getMenu', {查询条件})
.then((response) => {
resolve(recursionSetTemplate(response.data, []));
})
.catch((error) => {
throw error;
});
});
const template = { template: '<router-view/>' };
/**
* 递归给路由对象赋予 component
* 并组装新路由数组array,array传入时为空数组
* 方法走完后array数据结构会与router一致
*/
const recursionSetTemplate = (router, array) => {
router.forEach((item) => {
const newRouter = {
_id: item._id,
name: item.name,
path: item.path,
redirect: item.redirect,
hidden: item.hidden,
menuStatus: item.menuStatus,
iconCls: item.iconCls,
noDropdown: item.noDropdown,
};
if (!item.children && item.component) {
newRouter.component = () => import(`@/components/${item.component}`);
} else {
newRouter.component = template;
}
array.push(newRouter);
if (item.children) {
newRouter.children = [];
recursionSetTemplate(item.children, newRouter.children);
}
});
return array;
};
export { getRouter, defaultRouter };
main.js 在该 js 中将路由挂载到 vue-router 中
import Router from 'vue-router';
import Vue from 'vue';
import App from './App.vue';
import { getRouter, defaultRouter } from './router/router';
Vue.use(Router);
Vue.config.productionTip = false;
const router = new Router();
// 添加默认路由规则
router.addRoute(defaultRouter[0]);
getRouter().then((vueRouters) => {
for (const item of vueRouters) {
// 将item作为systemFrame的子路由
router.addRoute('systemFrame', item);
}
});
router.beforeEach((to, from, next) => {
next();
});
new Vue({
router,
render: (h) => h(App),
}).$mount('#app');
systemFrame.vue 在页面中将路由数组赋值给侧栏
<template>
<el-container style="height: 700px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-active="$route.path" router>
<!-- menu-list为该页面子组件,递归展示菜单 -->
<menu-list v-if="vueRouters.length > 0" :routers="vueRouters"></menu-list>
</el-menu>
</el-aside>
<el-container>
...
<router-view/>
</el-container>
</el-container>
</template>
<script>
import menuList from './menuList';
import axiosUtil from '../utils/axiosUtil';
export default {
name: 'container',
components: {
menuList,
},
data() {
return {
vueRouters: [],
};
},
mounted() {
axiosUtil.postPromise('/system/menu/getMenu', {查询对象})
.then((response) => {
this.vueRouters = response.data;
});
},
};
</script>
menuList.vue
<template>
<div>
<template v-for="(element) of routers">
<el-submenu v-if="element.children && !element.hidden && element.menuStatus"
:index="element._id" :key="element._id">
<template slot="title">
<i :class="element.iconCls"/>
{{ element.name }}
</template>
<menu-list :routers="element.children"></menu-list>
</el-submenu>
<el-menu-item v-else-if="!element.hidden && element.menuStatus"
:key="element._id" :index="element.path">
<template slot="title">
<i :class="element.iconCls"/>
{{ element.name }}
</template>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name: 'menuList',
props: {
routers: {
type: Array,
default: () => [],
},
},
};
</script>