前言
后台项目需要根据router生成sidebar,本来以为使用element-plus不会遇到什么问题,但是动手做还是有很多bug,小小记录一下
根据路由生成sidebar
index.vue部分的代码很简单,配置一下el-menu的属性,然后把routes传给sidebar-item。这里的传给sidebar-item的数据都是有用的。
<el-menu :collapse="isCollapse"
:unique-opened="false"
:collapse-transition="false"
mode="vertical">
<sidebar-item v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path" />
</el-menu>
sidebar-item部分的代码稍微有点复杂。首先我们先思考传给sidebar-item的路由共有哪几种。
- 无子路由的普通路由
- 有一个子路由的嵌套路由
- 有多个子路由的嵌套路由
但是其实不止这三种,当传入路由无子路由时你需要额外一个变量is-nest去判断它是否已经是一个子路由,所以还有一种。
- 无子路由的嵌套路由
我们把有一个子路由的嵌套路由与无子路由的普通路由合并,就有下面的生成代码
<template v-if='onlyHasOneShowing(item)&&(!onlyOneChild.date.children||onlyOneChild.date.noShowChild)'>
<component :is="isHttp(onlyOneChild.date.path)"
v-bind="attrObj(onlyOneChild.date.path)">
<el-menu-item :index='pathResolve(onlyOneChild.date.path)'
:class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.date.meta.icon"
:title="onlyOneChild.date.meta.title" />
</el-menu-item>
</component>
</template>
<el-sub-menu v-else
:index="pathResolve(item.path)"
popper-append-to-body>
<template #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="pathResolve(child.path)"
class="nest-menu" />
</el-sub-menu>
function onlyHasOneShowing (item) {
if (item.children) {
const children = item.children
let showChild = []
children.map(item => {
if (item.hidden) {
return false
} else {
showChild.push(item)
}
})
if (showChild.length == 0) {
onlyOneChild.date = { ...item, path: '', noShowChild: true }
return true
}
if (showChild.length == 1) {
onlyOneChild.date = showChild[0]
return true
}
} else {
onlyOneChild.date = { ...item, path: '', noShowChild: true }
return true
}
return false
}
function pathResolve (routePath) {
return path.resolve(props.basePath, routePath)
}
function isHttp (path) {
if (path.includes('http')) {
return 'a'
} else {
return 'router-link'
}
}
function attrObj (path) {
if (path.includes('http')) {
return {
href: path,
target: '_blank',
}
}
return {
to: pathResolve(path)
}
}
- pathResolve函数是为了将子路由与目前的路由连接起来
- isHttp是为了判断路由是否是外链,如果是就返回a标签否则返回router-link
- attrObj是和isHttp一起使用,赋予对应的a或者router-link 相应的属性
- onlyHasOneShowing函数判断是否有一个显示子路由,如果仅有一个那就显示子路由,没有就显示本身,多于一个那就继续递归
稍微有点难理解,console.log几下关键处就清楚了
修复因为递归生成菜单导致el-menu无法折叠的问题
如果问我学前端这么久最怕什么,那我的回答一定是写css。好在接触了elementui等工具之后,css写的少了,但是这仍是绕不开的东西。css最令人讨厌的莫过于其牵一发而动全身的性质,如果没有一个很清晰的结构,写css就是折磨自己。
所以,当我发现折叠出现问题的时候,我内心是崩溃的,不过好在是解决了。
bug原因
产生bug的原因很简单,el-menu希望它下面包裹的组件是el-menu-item或者el-sub-menu。但是我们的递归组件里包裹了一层div,所以bug就出现了。
解决
解决方法也不算困难,我们自己弄一个折叠功能不就好了吗。根据源码可以知道,element是采用修改sidebar-item的宽度来实现折叠的,这里我们采用修改sidebar-container来实现折叠。
在解决这个问题的过程中,我深刻体会到清晰的html结构是多么的重要。html结构如下
<div>
<sidebar-container>
<el-menu>
<submenu-title-noDropdown>
<item/>
</submenu-title-noDropdown>
<el-sub-menu>
<item/>
</el-sub-menu>
</el-menu>
</sidebar-container>
<main-container>
</main-container>
</div>
我们想要实现折叠效果需要修改sidebar-container和main-container的宽度。实现方式就是,当我们点击折叠按钮时,给外层div附上新的class,进而修改两者的宽度。
//css
//折叠前
.main-container {
transition: margin-left .28s;
margin-left: 210px;
position: relative;
}
.sidebar-container {
transition: width 0.28s;
width: 210px !important;
}
//折叠后
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
}
//html
<div :class="classObj">
<sidebar class="sidebar-container" />
<div class="main-container">
<navbar />
<app-main />
</div>
</div>
//js
let classObj = computed(() => {
return {
hideSidebar: store.getters.isCollapse,
openSidebar: !store.getters.isCollapse,
}
})
之后我们需要把标题部分隐藏,只留下图标部分,同时统一一下两种状态下图标的位置即可
//折叠前的样式
.sidebar-container {
.el-menu {
border: none;
height: 100%;
width: 100%;
span {
position: absolute;
left: 54px;
}
}
}
//折叠后的样式
.hideSidebar {
.el-sub-menu {
overflow: hidden;
&>.el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-sub-menu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-sub-menu {
&>.el-sub-menu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
至此,sidebar功能完成