接着昨天的笔记,整个布局和权限的结合基本搞定。后续开始看下Navbar等其他布局的设计。
Sidebar
根据element-vue-admin中的说明,这里的Sidebar是基于element-ui的NavMenu来实现的。于是先看了下element-ui的NavMenu。在官网找了个例子,主要代码如下:
<div id="app">
<el-row class="tac">
<el-col :span="12">
<h5>默认颜色</h5>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose">
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item-group>
<template slot="title">分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="分组2">
<el-menu-item index="1-3">选项3</el-menu-item>
</el-menu-item-group>
<el-submenu index="1-4">
<template slot="title">选项4</template>
<el-menu-item index="1-4-1">选项1</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<span slot="title">导航二</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<i class="el-icon-document"></i>
<span slot="title">导航三</span>
</el-menu-item>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<span slot="title">导航四</span>
</el-menu-item>
</el-menu>
</el-col>
</el-row>
</div>
层级结构主要为el-row>el-col>el-menu>el-submenu>el-menu-item。如果是多层级菜单使用了el-menu-item-group。其他属性可以参看element-ui的API。然后看下目前项目中的Sidebar。
这里Sidebar使用了index.vue/SidebarItem.vue/Item.vue/Link.vue
index.vue
在此组件中主要初始化菜单的el-menu,同时将用户的路由传到子组件SidebarItem.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 permission_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 },
computed: {
...mapGetters(["permission_routes"]),
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 false;
},
variables() {
return variables;
},
isCollapse() {
return false;
}
}
};
</script>
SidebarItem.vue/Item.vue/Link.vue
SidebarItem主要是路由的递归,然后加载相应的菜单。这里的Item.vue采用了函数式组件的方式,并通过render函数返回创建的节点。Link.vue则是判断是否外链。在SidebarItem中还有个逻辑,如果一个路由只有一级子节点,在没有配置alwaysShow属性时,默认就会合并为一个菜单。其它的逻辑如:判断菜单(router)是否是隐藏的,每个菜单都有icon、path、name等。
SidebarItem.vue
<template>
<div v-if="!item.hidden">
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<item
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:title="item.meta.title"
/>
</el-menu-item>
</app-link>
</template>
<el-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
popper-append-to-body
>
<template slot="title">
<item
v-if="item.meta"
:icon="item.meta && item.meta.icon"
:title="item.meta.title"
/>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from "path";
import Item from "./Item";
import AppLink from "./Link";
import { isExternal } from "@/utils/validate";
export default {
name: "SidebarItem",
components: { Item, AppLink },
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ""
}
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null;
return {};
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
if (item.hidden) {
return false;
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item;
return true;
}
});
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true;
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
return true;
}
return false;
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath;
}
if (isExternal(this.basePath)) {
return this.basePath;
}
return path.resolve(this.basePath, routePath);
}
}
};
</script>
<style scoped>
.svg-icon {
margin-right: 16px;
}
</style>
Item.vue
<script>
export default {
name: "MenuItem",
functional: true,
props: {
icon: {
type: String,
default: ""
},
title: {
type: String,
default: ""
}
},
render(h, context) {
const { icon, title } = context.props;
const vnodes = [];
if (icon) {
vnodes.push(<svg-icon icon-class={icon} />);
}
if (title) {
vnodes.push(<span slot="title">{title}</span>);
}
return vnodes;
}
};
</script>
Link.vue
<template>
<!-- eslint-disable vue/require-component-is -->
<component v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from "@/utils/validate";
export default {
props: {
to: {
type: String,
required: true
}
},
methods: {
linkProps(url) {
if (isExternal(url)) {
return {
is: "a",
href: url,
target: "_blank",
rel: "noopener"
};
}
return {
is: "router-link",
to: url
};
}
}
};
</script>
初次登陆不加载菜单
最后测试的时候发现第一次登录后不加载菜单,必须刷新页面后才加载。排查了下,发现是当初测试时,登陆后将用户的role存放到了store中,所以导致权限逻辑加载时逻辑路径错误,没有去动态的加载菜单。修改用户登录中的submitLogin方法,将返回值中的SET_ROLE去掉即可。如下所示:
submitlogin({ commit }, { payload }) {
const { username, password } = payload;
return new Promise((resolve, reject) => {
login(username.trim(), password)
.then(response => {
if (response.data.userInfo.token != "error") {
//commit("SET_ROLES", response.data.userInfo.roles);//去掉此行
commit("SET_NAME", response.data.userInfo.name);
commit("SET_TOKEN", response.data.userInfo.token);
setToken(response.data.userInfo.token);
resolve();
router.push("/");
} else {
console.log(response.data.userInfo.token);
resolve();
router.push("/404");
}
})
.catch(error => {
reject(error);
});
});
}