渲染左侧菜单
<template>
<div class="sidebar">
<el-menu
ref="sideMenu"
class="sidebar_menu"
:default-active="activeNav"
unique-opened
>
<div
class="sidebar_item"
v-for="sidebar in sidebarList"
:key="sidebar.id"
>
<!-- 有子级 -->
<el-submenu
:index="sidebar.path"
v-if="
sidebar.children &&
sidebar.children.length > 0 &&
sidebar.name !== sidebar.children[0].name
"
>
<template slot="title">
<span>{{ sidebar.meta.title }}</span>
</template>
<div
v-for="child1 in sidebar.children"
:key="child1.name"
class="sidebar_child"
>
<el-submenu
:index="child1.path"
v-if="child1.children && child1.children.length > 0"
>
<template slot="title">
<span>{{ child1.meta.title }}</span>
</template>
<div
v-for="child in child1.children"
:key="child.name"
class="sidebar_child"
>
<el-menu-item
:index="child.path"
:disabled="!child.path"
@click="handleClickMenu(child1.path, child.path)"
>
<span slot="title">{{ child.name }}</span>
</el-menu-item>
</div>
</el-submenu>
<!-- 无子级 -->
<el-menu-item
v-else
:index="child1.path"
:disabled="!child1.path"
@click="handleClickMenu(sidebar.path, child1.path)"
>
<span slot="title">{{ child1.name }}</span>
</el-menu-item>
</div>
</el-submenu>
<!-- 无子级 -->
<el-menu-item
v-else
:index="sidebar.path + '/index'"
:disabled="!sidebar.path"
@click="handleClickMenu(sidebar.path)"
>
<span slot="title">{{ sidebar.name }}</span>
</el-menu-item>
</div>
</el-menu>
</div>
</template>
<script>
import { getUserFuncPerm } from '@/api/user'
export default {
name: 'SideNavigation',
props: {
activePath: {
type: String,
default: '',
},
},
data() {
return {
defaultActive: '/',
}
},
computed: {
sidebarList(){
return this.$store.getter.sidebarList
},
activeNav() {
return this.activePath ? this.activePath : this.defaultActive
},
},
mounted() {
this.getMenuList()
},
methods: {
// 菜单
getMenuList() {
//
},
handleClickMenu(path, subPath) {
const p = subPath ? subPath : `${path}/index`
this.$router.push(p)
this.defaultActive = p
},
// 跳转刷新
handleClickMenuAndRefresh(path, subPath) {
const p = subPath ? subPath : `${path}/index`
// 先跳转至空白页,再跳转至目标页,实现刷新目标页
this.$router.push({
path: '/redirect',
query: {
path: p,
},
})
this.defaultActive = p
},
},
}
</script>
首先需要渲染左侧菜单
vuex内部会存贮一个变量sidebarList, 这个变量的结构就是类似于权限接口的返回值,是一个层级结构。
如何存储sidebarList呢,
路由跳转之前会经过 router.beforeEach方法
当进入一个路由之前需要判断是否有能力进入
数据准备sidebarList
有一个获取左侧菜单权限的接口,得到数据
{
"data": {
"funcPerm": [
{
"children": [
{
"id": "10010",
"name": "日程管理",
"path": "/schedule/index", // 路由地址
"component": "meeting/meetingRoom/index", // 页面地址
}
],
"component": "Layout",
"id": "10000",
"meta": {
"title": "会议日程"
},
"name": "会议日程",
"path": "/index",
}
]
},
"errors": [],
"msg": "操作成功",
"status": "0"
}
上图是一个基本的权限接口的返回值
控制权限展示
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: false });
const whiteList = ['/login'];
router.beforeEach(async (to, from, next) => {
NProgress.start();
let userInfo = {};
const storedUserInfo = localStorage.getItem("userInfo");
if (storedUserInfo) {
userInfo = JSON.parse(storedUserInfo);
}
const userId = userInfo ? userInfo.userId : "";
// 如果拉取到了登录人信息
if (userId) {
if (whiteList.includes(to.path)) {
next();
NProgress.done();
} else {
// 如果本地已经拉取过路由信息了
const routerLength = store.getters.router_length;
if (routerLength) {
// 这里其实应该需要判断下目标路由是否在权限列表里面
next();
NProgress.done();
} else {
try {
let accessRoutes = "";
const params = {
userId: userId,
};
const res = await store.dispatch("user/getUserFuncPerm", params);
accessRoutes = await store.dispatch(
"permission/generateRouter",
res.funcPerm
);
router.addRoutes(accessRoutes);
next({ ...to, replace: true });
} catch (error) {
Message.error(error || "Has Error");
next(`/uim-view/#/login`);
NProgress.done();
}
}
}
} else {
if (whiteList.includes(to.path)) {
next();
NProgress.done();
}
// 这里应该还有登出操作
}
});
router.afterEach(() => {
NProgress.done();
});
上面代码中有个whiteList常量,这里是一个白名单,当路由进入白名单里面的的时候,直接渲染页面,保证登录页面一直保持着能登录的权限
当进入非白名单时,判断用户是否登录userId,没有登录进入登录页
如果登录过,则判断是否获取过用户的菜单权限,这里的菜单权限使用的时vuex保存的,router_length代表这用户菜单的长度,如果有值则表示获取过权限,直接进入对应页面。(其实,这里需要做一个权限判断,如果用户手动输入路由,且不在路由权限里,这里有问题)
动态路由
如果我们不写一个全静态的路由,全由后端的权限给的路径拼上路由列表
accessRoutes = await store.dispatch(
"permission/generateRouter",
res.funcPerm
);
上面就是实现此功能的代码
generateRouter({ commit }, data = []) {
return new Promise(resolve => {
let serverRouterMap = []
serverRouterMap = data
if (Array.isArray(data) && data.length === 0) {
Message({
message: '无权限, 请配置权限',
type: 'warning',
duration: 3 * 1000,
})
return
}
try {
constantRoutes[0].redirect = data[0].children[0].path
} catch (err) {
console.log(err)
}
const createRouter = () =>
new Router({
scrollBehavior: () => ({
y: 0,
}),
routes: constantRoutes,
})
router.matcher = createRouter().matcher
serverRouterMap.push({ path: '*', redirect: '/404', hidden: true })
const asyncRouterMap = filterAsyncRouter(serverRouterMap)
commit('SET_ROUTES', asyncRouterMap)
resolve(asyncRouterMap)
})
},
function filterAsyncRouter(asyncRouterMap) {
// 遍历后台传来的路由字符串,转换为组件对象
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') {
// Layout组件特殊处理
route.component = Layout
} else {
route.component = _import(route.component)
}
}
if (route.children && route.children.length > 0) {
route.children = filterAsyncRouter(route.children)
} else {
delete route.children
}
return true
})
return accessedRouters
}