sidebar(侧边栏原理vue admin)
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
源码位置
- sidebar 引用自 layout 组件layout 组件位于 src/layout/index.vue
- sidebar 组件源码位于 src/layout/components/Sidebar/index.vue
el-menu 用法解析
- 侧边栏的核心是将根据权限过滤后的 router 和 el-menu 组件进行映射所以熟悉 el-menu 是理解 sidebar 的起点
<template>
<el-row class="tac">
<el-col :span="12">
<el-menu // el-menu本身是一个菜单容器
default-active="1-1" // 默认菜单几高亮显示
background-color="#545c64" // 背景色
text-color="#fff" // 文本颜色
active-text-color="#ffd04b" // 激活后文本颜色
mode="vertical" // 模式 vertical垂直模式 horizontal 水平模式
unique-opened // 是不是之能同时打开一个菜单 :unique-opened:"false" 允许俩者同时被打开
:collapse="isCollapse"// 默认的折叠状态
:collapse-transition="false" // 是否需要展示一个collapse动画
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
@select="handleSelect"
>
<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-submenu index="2">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">导航二</span>
</template>
<el-menu-item index="2-1">选项2-1</el-menu-item>
</el-submenu>
<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-col>
<el-button @click="isCollapse = !isCollapse">折叠</el-button>
</el-col>
</el-row>
</template>
<script>
export default {
data() {
return {
isCollapse: false
}
},
methods: {
handleSelect(key, keyPath) {
console.log('handleSelect', key, keyPath)
},
handleOpen(key, keyPath) {
console.log('handleOpen', key, keyPath)
},
handleClose(key, keyPath) {
console.log('handleClose', key, keyPath)
}
}
}
</script>
el-menu
el-menu 表示菜单容器组件
- default-active激活的菜单注意如果存在子菜单需要填入子菜单 ID
- unique-opened是否保持一个菜单打开
- mode枚举值分为 vertical 和 horizontal 两种
- collapse是否水平折叠收起菜单仅在 mode 为 vertical 时可用
- collapse-transition是否显示折叠动画
- @select点击菜单事件keyPath 代表菜单的访问路径如1-4-1 菜单的点击日志为
handleSelect 1-4-1 (3) ["1", "1-4", "1-4-1"]
获取 keyPath 我们可以获取 1-4-1 菜单的所有父级菜单的 ID
- @open父菜单打开时触发事件
- @close父菜单关闭时触发事件
el-submenu
子菜单容器el-submenu 与 el-menu 不同el-menu 表示整个菜单而 el-submenu 表示一个具体菜单只是该菜单还包含了子菜单
el-submenu 可以通过定制 slot 的 title 来自定义菜单样式
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
</el-submenu>
el-submenu 容器内 default 的 slot 用来存放子菜单可以包含三种子菜单组件
- el-menu-item-group菜单分组为一组菜单添加一个标题el-menu-item-group 容器内容需要存放 el-menu-item 组件支持通过 title 的 slot 来定制标题样式
- el-submenuel-submenu 支持循环嵌套 el-submenu这使得超过两级子组件得以实现
- el-menu-item子菜单组件
sidebar 源码分析
sidebar 源码如下
<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, Logo },
computed: {
...mapGetters([
'permission_routes',
'sidebar'
]),
// 你进入那个路由我就把哪个路由返回给你
activeMenu() {
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
- activeMenu通过 meta.activeMenu 属性指定路由对应的高亮菜单meta.activeMenu 需要提供一个合法的路由否则将不能生效
- isCollapseNavBar 中点击按钮会修改 Cookie 中的 sidebarStatus从 vuex 取值时会将 sidebarStatus 转为 Boolean并判断默认是否需要收缩左侧菜单栏
- showLogo判断 settings.js 中的配置项是否需要展示 Logo
- variables从 @/styles/variables.scss 中获取 scss 对象从而获取样式
sidebar 中通过 sidebar-item 实现子菜单下面我们来分析 sidebar-item 组件
sidebar-item 源码分析
side-item 组件源码如下
<template>
<div v-if="!item.hidden" class="menu-wrapper"> //hidden为true时不进行展示
//是否只有一个子组件
<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'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
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: 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>
side-item props 分析
side-item 的 props 如下
- item路由对象
- basePath路由路径
sidebar-item 展示逻辑分析
sidebar-item 最重要是展示逻辑主要分为以下几步
-
通过 item.hidden 控制菜单是否展示
-
通过 hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow 逻辑判断 template 菜单是否展示template 代表单一菜单
+ hasOneShowingChild判断是否只有一个需要展示的子路由 + !onlyOneChild.children||onlyOneChild.noShowingChildren判断需要展示的子菜单是否包含 children 属性如果包含则说明子菜单可能存在孙子菜单此时则需要再判断 noShowingChildren 属性 + !item.alwaysShow判断路由中是否存在 alwaysShow 属性如果存在则返回 false不展示 template 菜单也就说只要配置了 alwaysShow 属性就会直接进入 el-submenu 组件
hasOneShowingChild 方法源码详解
入参
- childrenrouter 对象的 children 属性
- itemrouter 对象
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
// 如果 children 中的路由包含 hidden 属性则返回 false
if (item.hidden) {
return false
} else {
// 将子路由赋值给 onlyOneChild用于只包含一个路由时展示
this.onlyOneChild = item
return true
}
})
// 如果过滤后只包含展示一个路由则返回 true
if (showingChildren.length === 1) {
return true
}
// 如果没有子路由需要展示则将 onlyOneChild 的 path 设置空路由并添加 noShowingChildren 属性表示虽然有子路由但是不需要展示子路由
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
// 返回 false表示不需要展示子路由或者超过一个需要展示的子路由
return false
}
- 如果展示 template 组件首先会展示 app-link 组件然后是 el-menu-item最里面嵌套的是 item 组件
item 组件需要路由 meta 中包含 title 和 icon 属性否则将渲染内容为空的 vnode 对象
<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 菜单el-submenu 逻辑中采用了嵌套组件的做法将 sidebar-item 嵌套在 el-submenu 中
<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>
el-submenu 中的 sidebar-item 有两点区别
- 第一是传入 is-nest 参数
- 第二是传入 base-path 参数
app-link 源码分析
app-link 是一个动态组件通过解析 to 参数如果包含 http 前缀则变成一个 a 标签否则变成一个 router-link 组件
<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>
isExternal 函数通过一个正则表达式匹配 http 链接
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
item 组件源码分析
item 组件通过定义 render 函数完成组件渲染
<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>
总结
+ sidebarsidebar 主要包含 el-menu 容器组件el-menu 中遍历 vuex 中的 routes生成 sidebar-item 组件。sidebar 主要配置项如下
activeMenu根据当前路由的 meta.activeMenu 属性控制侧边栏中高亮菜单
isCollapse根据 Cookie 的 sidebarStatus 控制侧边栏是否折叠
variables通过 @/styles/variables.scss 填充 el-menu 的基本样式
+ sidebar-itemsidebar-item 分为两部分
第一部分是当只需要展示一个 children 或者没有 children 时进行展示展示的组件包括
+ app-link动态组件path 为链接时显示为 a 标签path 为路由时显示为 router-link 组件
+ el-menu-item菜单项当 sidebar-item 为非 nest 组件时el-menu-item 会增加 submenu-title-noDropdown 的 class
+ itemel-menu-item 里的内容主要是 icon 和 title当 title 为空时整个菜单项将不会展示
第二部分是当 children 超过两项时进行展示展示的组件包括
+ el-submenu子菜单组件容器用于嵌套子菜单组件
+ sidebar-itemel-submenu 迭代嵌套了 sidebar-item 组件在 sidebar-item 组件中有两点变化
+ 设置 is-nest 属性为 true
+ 根据 child.path 生成了 base-path 属性传入 sidebar-item 组件
Vue admin
登陆流程
界面精简及路由处理实现了权限控制
路由和权限校验原理
侧边栏
重定向
面包屑导航
requst库 axios拦截
登录组件实现细节