最近在用antd
的框架,框架的具体样式如下:
由于此菜单比较常规,现有设计图如下:
给出的菜单触发要求:
左菜单实现规则:
显示:
鼠标点击相应的一级菜单,右边浮动面板出现
注:鼠标hover其他一级菜单,右边浮动面板
不会切换
关闭:
1、鼠标点击其他一级菜单,右边浮动面板
切换到相应的内容
2、点击面板右上角关闭图标也可消失
跳转:
点击三级菜单跳转到相应页面
因此需要对antd
框架的菜单部分进行相应的改动。
1.antd
的菜单组件位置——src/components/menu/SideMenu.vue
2.原有菜单使用的是menu.js
——是dom
拼接的结构
原有部分的内容:
2.1 html
部分内容
<i-menu
:theme="theme"
:collapsed="collapsed"
:options="menuData"
@select="onSelect"
class="menu"
/>
2.2 script
部分内容
import IMenu from "./menu";
export default{
name:'SideMenu',
components: { IMenu },
}
3.分析菜单中的参数——theme
collapsed
menuData
1. theme:主题:跟随系统的主题改变而改变
2. collapsed:菜单是否折叠,改变的是内容区距离浏览器左边的位置
3. menuData:菜单的数据,是路由的数组形式
menuData
菜单的过滤:将meta
中的invisible
为true的菜单要隐藏掉。由于可以有多级菜单,因此需要通过递归的方式来处理。
created(){
this.menuData.map((menu) => {
if (menu.meta && menu.meta.invisible) {
return;
} else if (menu.children && menu.children.length) {
this.firstMenu.push({
...menu,
children: this.filterMenu(menu.children),
});
} else {
this.firstMenu.push(menu);
}
});
console.log("firstMenu", this.firstMenu);
},
methods:{
filterMenu(arr) {
var list =
arr &&
arr.filter((menu) => {
if (menu.meta && menu.meta.invisible) {
return;
} else if (menu.children && menu.children.length) {
return this.filterMenu(menu.children);
} else {
return menu;
}
});
return list;
},
}
4.自定义菜单
菜单的布局如下:
4.1 一级菜单可以通过a-menu
组件来搭建
注意这个组件的用法:
如果没有二级目录,则直接使用a-menu-item
,如果有二级目录,则需要用a-sub-menu
标签包裹,里面再使用a-menu-item
等标签
<a-menu mode="vertical" :theme="theme">
<template v-for="(menu, mindex) in firstMenu">
<a-menu-item
@click="turnTo(menu)"
:key="menu.path"
v-if="!menu.children || menu.children.length == 0"
:style="{
color:
currentMenu.indexOf(menu.fullPath || menu.path) > -1
? '#f90'
: '#666',
}"
>
<a-icon
:type="menu.meta && menu.meta.icon"
v-if="menu.meta && menu.meta.icon"
/>
<span>{{ menu.name }}</span>
</a-menu-item>
<a-sub-menu
:key="menu.path"
:ref="'subRef' + mindex"
:style="{
color:
currentMenu.indexOf(menu.fullPath || menu.path) > -1
? '#f90'
: '#666',
}"
v-else
@titleClick="mouseover($event, menu, mindex)"
overflowedIndicator='<a-icon type="right" />'
>
<span slot="title"
><a-icon
:type="menu.meta && menu.meta.icon"
v-if="menu.meta && menu.meta.icon"
/><span>{{ menu.name }}</span></span
>
</a-sub-menu>
</template>
</a-menu>
分析一下上面的代码:
- theme:主题色,组件的主题色跟框架本身的主题色保持一致
- mode:模式,菜单类型,现在支持垂直、水平、和内嵌模式三种,
string: vertical vertical-right horizontal inline
,影响到菜单的整体布局。 - 根据设计图,我们可以看到菜单要求是向右的箭头,则可以使用
mode="vertical"
的垂直菜单方式。 - 图标与菜单标题展示:
key
要取唯一值,唯一值可以是menu.path
路由路径 - 如果没有二级菜单和有二级菜单使用的组件不同,则需要根据是否有
children
参数来判断层级。一级菜单
如果也要有图标,则需要添加a-icon
组件,单独的一级菜单,则需要监听click
点击事件。
<a-menu-item
@click="turnTo(menu)"
:key="menu.path"
v-if="!menu.children || menu.children.length == 0"
:style="{
color:
currentMenu.indexOf(menu.fullPath || menu.path) > -1
? '#f90'
: '#666',
}"
>
<a-icon
:type="menu.meta && menu.meta.icon"
v-if="menu.meta && menu.meta.icon"
/>
<span>{{ menu.name }}</span>
</a-menu-item>
单独的一级菜单的点击事件——也就是上面代码的turnTo方法如下
turnTo(menu){
this.visibleFlag = false;
this.$router.push({ path: menu.fullPath });
}
更改后的函数如下:
turnTo(menu){
this.visibleFlag = false;
if (this.currentMenu != menu.fullPath) {
this.$router.push({ path: menu.fullPath });
}
}
4.2 二级菜单需要自定义dom
html
部分的代码:
<a-sub-menu
:key="menu.path"
:ref="'subRef' + mindex"
:style="{
color:
currentMenu.indexOf(menu.fullPath || menu.path) > -1
? '#f90'
: '#666',
}"
v-else
@titleClick="mouseover($event, menu, mindex)"
>
<span slot="title"
><a-icon
:type="menu.meta && menu.meta.icon"
v-if="menu.meta && menu.meta.icon"
/><span>{{ menu.name }}</span></span
>
</a-sub-menu>
二级菜单需要实现以下几个功能:
4.2.1 根据点击内容,右侧相应位置出现弹层
这个功能最主要就是获取点击元素对应的一级菜单的位置,可以通过给一级菜单绑定ref
属性来进行处理。
subMenu
组件有个titleClick
的点击事件,可以获取到当前元素的位置。
右侧弹层是否显示是通过visibleFlag
参数来控制的。secondMenu
右侧菜单数据根据menu.children
来展示。
右侧弹层的位置,特别是top
值,需要根据this.$refs[ "subRef" + index][0].$el.getBoundingClientRect().top
来实现,这行代码花了我好久的时间。。。
根据点击区域获取菜单的top—this.$refs["subRef" + index][0].$el.getBoundingClientRect().top
通过ref属性获取的dom元素,打印出来是vueComponent的类数组形式,则需要通过[0]选取数组的第一项,然后通过其中的$el.getBoundingClientRect().top的方式来获取top值
vue根据ref获取dom的top值的方法!!!
还有就是右侧弹层的宽度,我这边的基数是170px,根据二级菜单的数量来换列展示,因此宽度跟菜单的数量相关。
mouseover(e,menu,index){
this.visibleFlag = true;
this.secondMenu = menu.children;
this.top = this.$refs[ "subRef" + index][0].$el.getBoundingClientRect().top;
// console.log("top", this.top);
var n = this.secondMenu.length / 5;
this.width = Math.ceil(n) * 170;
}
4.2.2 右侧弹层的内容根据点击的内容展示
<div class="menuBgCls" :style="{ left: left + 'px' }" v-if="visibleFlag" @click="turnToPage">
<div
class="secondMenuCls"
@click.stop
:style="{ top: top + 'px', width: width + 'px' }"
>
<div class="item" v-for="(item, index) in secondMenu" :key="index">
<div class="routerCls" @click="turnToPage">
<router-link :to="item.fullPath">{{ item.name }}</router-link>
<a-icon
@click.stop
type="star"
class="starCls"
:theme="index % 2 == 0 ? 'filled' : 'outlined'"
/>
</div>
</div>
<div class="closeCls" @click="closeSecondMenu">
<a-icon type="close" />
</div>
</div>
</div>
分析一下上面的代码:
首先要知道,右侧部分有两部分,底层是一个半透明的蒙层,上面是菜单展示区域。
- visibleFlag:控制弹层的显示与隐藏
- click:点击蒙层时,弹层部分要隐藏
- 蒙层的left:根据折叠与非折叠状态,left值不同。
- 菜单展示区域的left和top值:根据点击区域获取left和top值,主要是top值,left值还是跟折叠与非折叠有关系。
- 点击右上角的X,也可以关闭弹层部分
4.2.3 右侧弹层的样式
.menuBgCls {
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.3);
position: fixed;
right: 0;
top: 0;
}
.secondMenuCls {
position: absolute;
left: 5px;
padding: 20px 40px 10px 5px;
background: #fff;
z-index: 11111;
min-width: 170px;
width: auto;
max-height: 240px;
border-radius: 10px;
box-shadow: 0 0 80px 5px rgba(0, 0, 0, 0.08);
display: flex;
// flex-wrap: wrap;
flex-direction: column;
flex-flow: column wrap;
.closeCls {
position: absolute;
right: 2px;
top: 8px;
padding: 0 5px;
cursor: pointer;
i {
font-size: 12px;
&:hover {
color: #f90;
}
}
}
.item {
padding: 5px 0px;
// border-right: 1px solid #f90;
&:last-child {
border: none;
}
.routerCls {
display: flex;
align-items: center;
padding: 2px 15px;
box-sizing: border-box;
&:hover {
background: #fff4e7;
// a {
// color: #f90;
// }
}
a {
color: #666;
cursor: pointer;
padding: 2px 10px 2px 0;
width: 100px;
display: inline-block;
overflow: hidden;
text-overflow: -o-ellipsis-lastline;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
.starCls {
flex-shrink: 0;
color: #f90;
}
}
}
}
5.监听路由改变,右侧内容区域与左侧菜单相关联
5.1 路由参数如下:
{
path: 'messageManage',
name: '消息管理',
component: PageView,
meta: {
icon: 'message'
},
children: [{
path: 'message',
name: '消息列表',
meta: {
icon: 'message',
authority: 'Message.Look'
},
component: () =>
import ('@/pages/message/message')
}]
}
消息列表对应的路由是:/messageManage/message
,则右侧如果是消息列表的页面,则左侧需要指定的是消息管理
的菜单,比如颜色是主题色,表示当前是此菜单。也就是需要监听当前页面的路由,然后判断是否含有当前页面的路由参数。
监听当前页面的路由——立即监听且深度监听
watch: {
$route: {
handler: function(val, oldVal) {
this.currentMenu = val.fullPath || val.path;
},
deep: true,
immediate: true,
},
},
currentMenu
就是当前内容区域的路由路径。currentMenu.indexOf(menu.fullPath || menu.path) > -1
:如果满足条件,则表示是当前一级菜单里面的菜单,否则颜色不改变。
:style="{color:currentMenu.indexOf(menu.fullPath || menu.path) > -1? '#f90': '#666'}"
完成!!!
上效果图:
菜单展开
菜单展开时弹层显示
菜单折叠
菜单折叠时弹层展示
评论区有小朋友问横向菜单布局+竖向菜单布局怎么排布
如果是我理解的:一级菜单横向展示,二级及以下的菜单竖向排布的话,可以在src/config/default/setting.config.js
中的layout
设置为mix
即可。