> index.vue
<template>
<!-- 判断是否显示logo -->
<div :class="{'has-logo':showLogo}">
<!-- 判断是否隐藏侧边栏 -->
<logo v-if="showLogo" :collapse="isCollapse" />
<!-- 主侧边栏标签 -->
<el-scrollbar wrap-class="scrollbar-wrapper">
<!--
default-active:活动菜单颜色
collapse:侧边栏隐藏
background-color: 背景颜色
text-color:菜单中文本颜色
unique-opened: 是否唯一打开
active-text-colo: 活动文本颜色
collapse-transition: 隐藏过渡
mode="vertical":绑定变量
-->
<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, Logo },
computed: {
// 获取vuex中getter中的属性的值
...mapGetters([
'permission_routes',
'sidebar'
]),
// 活动菜单
activeMenu() {
// 获取当前路由对象
const route = this.$route
// 获取meta对象 和路径对象
const { meta, path } = route
// 如果设置路径,侧边栏将突出显示您设置的路径
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
// 显示logo
showLogo() {
return this.$store.state.settings.sidebarLogo
},
// scss颜色变量
variables() {
return variables
},
// 是否关闭侧边栏
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
> 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) {
// 判断图标是否element-ui中的图标
if (icon.includes('el-icon')) {
// 使用i标签添加图标
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
// 不是element-ui直接添加外部icon路径
vnodes.push(<svg-icon icon-class={icon}/>)
}
}
// 判断是否存在标题头
if (title) {
// 添加标题头
vnodes.push(<span slot='title'>{(title)}</span>)
}
// 返回拼接后的数组标签
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>
> 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="onlyOneChild.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 { isExternal } from '@/utils/validate' // 用于判断是外部的路径
import Item from './Item' // 迭代icon与title
import AppLink from './Link' // 应用关联 用于切换外部链接显示方法
import FixiOSBug from './FixiOSBug' // 用于修复IOS设备鼠标离开bug
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
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: 渲染函数重构
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
}
})
// 当只有一个子路由器时,默认显示子路由器
if (showingChildren.length === 1) {
return true
}
// 如果没有要显示的子路由器,则显示父级
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>
> Link.vue
<template>
<!-- is指定这个component渲染成为指定的标签类型 -->
<!-- 绑定链接地址 -->
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate' // 判断是否是外部path
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
// 判断是否是外部路径https
isExternal() {
return isExternal(this.to)
},
type() {
// 是外部HTTPS返回a标签
if (this.isExternal) {
return 'a'
}
// 是本地路径返回一个router-link,它最终会被渲染成一个a标签
return 'router-link'
}
},
methods: {
// 链接方法
linkProps(to) {
// 判断是否是外部HTTPS路径
if (this.isExternal) {
return {
// 链接地址
href: to,
target: '_blank',
rel: 'noopener'
}
}
// 不是外部路径直接返回
return {
to: to
}
}
}
}
</script>
> FixiOSBug.js
export default {
computed: {
// 获取应用设备
device() {
return this.$store.state.app.device
}
},
mounted() {
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
// 为了修复ios设备上点击菜单会触发鼠标离开的bug
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
this.fixBugIniOS()
},
methods: {
// 修复IOSbug函数
fixBugIniOS() {
// 获取子菜单对象
const $subMenu = this.$refs.subMenu
if ($subMenu) {
// 处理鼠标离开
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = (e) => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
}
}
}