文章目录
前言
在vue的后台管理系统中,侧边多级菜单无疑是最常见的场景,在有的时候我们还需要根据不同用户角色权限进行控制来显示不同的菜单,今天我就来讲讲用element ui实现的思路
一、实现多级菜单
先来看一下代码
sidebarItem.vue
<template>
<div class="menu-wrapper">
<template v-for="item in menu">
<!-- 这里相当于是最后一级的子菜单 -->
<el-menu-item
v-if="!item.children && !item.hidden"
:key="item.path"
:index="parent ? parent + '/' + item.path : item.path"
>
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</el-menu-item>
<!-- 此菜单下还有子菜单 -->
<el-submenu
v-if="item.children && !item.hidden"
:key="item.path"
:index="parent ? parent + '/' + item.path : item.path"
>
<template slot="title">
<i :class="item.meta.icon"></i>
<span>{{ item.meta.title }}</span>
</template>
<!-- 这里使用了递归去实现多级嵌套菜单,理论上可以嵌套三级四级甚至五级 -->
<sidebar-item
:menu="item.children"
:parent="parent ? parent + '/' + item.path : item.path"
/>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: "SidebarItem",
props: ["menu", "parent"],
data() {
return {};
},
};
</script>
<style></style>
Index.vue
<template>
<div>
<el-menu
:default-active="$route.path"
background-color="#304156"
text-color="#fff"
active-text-color="#ffd04b"
mode="vertical"
router
:unique-opened="false"
:collapse-transition="false"
:collapse="isCollapse"
>
<sidebar-item :menu="getrouters" />
</el-menu>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import SidebarItem from './sidebarItem'
export default {
components: { SidebarItem },
computed: {
...mapGetters(["getrouters", "getsidebar"]),
isCollapse() {
return !this.getsidebar.opened;
},
}
};
</script>
<style></style>
简单讲一下这里其实就是用了el-menu然后多级循环生成多级的菜单,里面用了一个组件递归来进行循环嵌套生成多级的菜单,其它的就没啥可讲了.
二、实现动态路由
router/Index.js
import Vue from 'vue'
import Router from 'vue-router'
import layout from '../components/layout'
Vue.use(Router)
// 动态路由
export const asyncRouterMap = [
{
path: '/home',
component: layout,
name: 'home',
meta: {
title: '首页',
icon: 'el-icon-tickets',
roles: ['admin','user','people']
},
},
{
path: '/order',
component: layout,
name: 'order',
meta: {
title: '订单管理',
icon:'el-icon-news',
roles: ['admin','user']
},
children:[
{
path: 'edit',
component: () => import('@/views/order/views/edit'),
name: 'edit',
meta:{
title: '订单编辑'
}
},
{
path: 'check',
component: () => import('@/views/order/views/check'),
name: 'ckeck',
meta: {
title: '订单查看',
roles: ['admin']
},
children: [
{
path: 'history',
component: () => import('@/views/order/views/history'),
name: 'history',
meta: {
title: '历史订单'
}
},
]
}
]
},
{
path: '/user',
component: layout,
name: '用户管理',
meta: {
title:"用户管理",
icon: 'el-icon-news',
roles: ['admin']
},
},
{
path: '/feedback',
component: layout,
name: '反馈管理',
meta: {
title: "反馈管理",
icon: 'el-icon-news',
roles: ['admin','people']
},
},
{
path: '/employee',
component: layout,
name: 'employee',
meta: {
title: '营销管理',
icon:'el-icon-news',
roles: ['admin','user','people']
},
children: [
{
path: 'ad',
component:() => import('@/views/marketing/views/ad'),
name: '广告管理',
meta: {
title: '广告管理'
},
children:[
{
path: 'find',
component: () => import('@/views/marketing/views/find'),
name: 'find',
meta:{
title: "发现页"
}
},
{
path: 'start',
component: () => import('@/views/marketing/views/start'),
name: 'start',
meta:{
title: '启动页'
}
}
]
},
{
path: 'visit',
component: () => import('@/views/marketing/views/visit'),
name: 'visit',
meta: {
title: '访问管理',
}
}
]
},
]
// 静态路由
export const commontRouterMap = [{
path: "/login",
name: '登录页',
hidden: true, //不在slider显示
component: () =>
import("@/views/login/Index")
},
{
path:'/404',
name: '404',
component: () => import('@/views/error/404.vue'),
hidden: true
},
{
path: '/',
component:layout,
name: 'layout',
redirect: '/home',
hidden: true,
children: [
{
path: 'home',
component: () => import('@/views/home/Index')
},
{
path: 'user',
name: '用户管理',
component: () => import('@/views/user/Index')
},
{
path: 'feedback',
name: '反馈管理',
component: () => import('@/views/feedback/Index')
},
]
},
];
//实例化vue的时候只挂载commontRouterMap
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: commontRouterMap
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router;
上面是路由的配置信息,这里就分成了两块,一个是静态路由,另一个是动态的路由,方便后续做权限的控制,多级菜单也是根据上面的动态路由循环遍历生成的
三、页面权限控制
store/Index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import { asyncRouterMap,commontRouterMap } from "@/router";
console.log(asyncRouterMap);
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return route.meta.roles.includes(roles)
} else {
return true
}
}
function filterAsyncRoutes(routes, roles) {
let res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
console.log(res)
return res
}
export default new Vuex.Store({
state: {
routers: commontRouterMap,
sidebar: {
opened: true,
withoutAnimation: false,
},
addRouters:[]
},
mutations: {
setRoutes(state,routers) {
state.addRouters = routers
state.routers = commontRouterMap.concat(routers);
},
//slider切换
TOGGLE_SIDEBAR(state) {
state.sidebar.opened = !state.sidebar.opened;
state.sidebar.withoutAnimation = false;
}
},
actions: {
ToggleSideBar: ({ commit }) => {
commit("TOGGLE_SIDEBAR");
},
generateRouters({commit},data) {
return new Promise((resolve) => {
const {roles} = data
let accessRouters = filterAsyncRoutes(asyncRouterMap,roles)
console.log(accessRouters);
commit('setRoutes',accessRouters)
resolve(accessRouters)
})
}
},
modules: {
},
getters: {
getsidebar: state => state.sidebar,
getrouters: state => state.routers,
getaddRouters: state => state.addRouters,
}
})
用户角色通常是通过后端返回,然后与路由里面事先添加好的元信息meta里面的角色进行对比,然后进行筛选处理以后的路由就是该用户可以访问的动态路由,接着一般再通过全局的路由前置守卫动态添加路由.
utils/permission.js
import router from "./router/";
import store from "./store/index";
let res = {
roles: null
}
router.beforeEach(async(to, from, next) => {
// 这里省略了token校验用户是否登录的逻辑,可以根据需求进行合理的修改和添加
if(to.path === '/login') {
next({path: '/'})
}
else {
if(!res.roles) {
// 实际用户角色应该是由后端返回,这里面因为没有去做登录这块逻辑,所以给了一个假定的角色
res.roles = "admin"
const routes = await store.dispatch('generateRouters', res)
console.log(routes);
router.addRoutes([...store.getters.getaddRouters,{ path: '*', redirect: '/404', hidden: true }])
next({...to,replace: true})
}
else {
next()
}
}
})
这里路由守卫我简化了实际登录前校验的一些逻辑,根据后台不同需求可以再合理进行添加,但是实际业务情景大同小异,拿到用户角色以后然后通过vuex封装的方法获取处理过的动态路由菜单,然后通过vue-router官方的方法addRoutes动态添加路由,这里next记得要走两次守卫,便于能确保动态路由添加进去有数据显示.
总结
这里面路由权限控制参考了vue-element-admin的实现方法,大部分情景都可以通用,技术的实现往往有着不同的实现方法,也有一些别的方法可以选择,根据不同的需求可以进行调整和修改.