效果图:鼠标放在一级菜单,后面显示一个二级和三级菜单面板
实现思路:
左边菜单的显示和浮动就不说了,就是一个很常规的ul - li 标签。
鼠标在左边浮动的时候,右边显示一个固定top和高度的div,宽度是自动的,如下图所示
参考代码
<div @mouseover="showToggle(item, index)" @mouseleave="handleHide" style="top:49px;position:absolute;border:solid 1px #E6EBF5;border-radius: 5px;" :class="[
'dynamic-menu-child',
{ 'dynamic-active': currentIndex === index },
{ 'space-left': isCollapse },
]">
<i class="el-icon-close arrow-close" @click="handleHide" title="点击关闭"></i>
<div class="conten-list" style="border:none;">
<CustomLeftMenu :menuList="item.children" :colCount="11" @change-child="handleGrandIitem" />
</div>
</div>
内部的分列显示,采用了常规的分页模式,根据菜单的数量,根据一列显示几条自动分成几列来实现,也就是CustomLeftMenu组件,代码如下,传一个数据List和页大小进来即可:
<template>
<div class="menuPage" style="height:300px;">
<div class="page" v-for="(item, i) in pageList" :key="i">
<div v-for="(menu, j) in menuArray" :key="j">
<div
v-if="
j + 1 >= i * colCount + 1 && j + 1 < i * colCount + 1 + colCount
"
>
<h3
class="title" style="margin:0px"
v-if="menu.type == 'title'"
>
{{ menu.data.meta.title }}
</h3>
<div class="menu" v-if="menu.type == 'menu'">
<span @click="handleChild(menu.pdata, menu.data)">{{
menu.data.meta.title
}}</span>
</div>
<div class="kong" v-if="menu.type == 'kong'">
<span> </span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "CustomLeftMenu",
props: {
//一列显示几个
colCount: {
type: Number,
require: true,
default: () => {
return 6;
},
},
// 菜单数据
menuList: {
type: Array,
require: true,
default:()=>[]
},
},
mounted() {
//重新组装数据
try{
for (var rootMenu of this.menuList) {
this.menuArray.push({ type: "title", data: rootMenu });
for (var menu of rootMenu.children) {
if(menu.hidden==true){
continue;
}
this.menuArray.push({ type: "menu", data: menu, pdata: rootMenu });
}
//补空
this.menuArray.push({ type: "kong", data: null });
}
//分页
var page = Math.ceil(this.menuArray.length / this.colCount);
for (var i = 0; i < page; i++) {
this.pageList.push(i + 1);
}
}catch{
}
},
data() {
return {
//重新组装的数据
menuArray: [],
//可以分几页
pageList: [],
};
},
methods: {
handleChild(root, child) {
this.$emit("change-child", root, child);
},
},
};
</script>
<style lang="scss" scoped>
.menuPage {
display: flex;
justify-content: center;
align-items: flex-start;
margin: 10px 0px 10px 10px;
}
.page {
margin-right: 10px;
}
.title {
border-left: 4px solid #304156;
padding-left: 10px;
height: 20px;
line-height: 20px;
font-weight: bold;
font-size: 16px;
text-align: left;
}
.menu {
width: 120px;
height: 25px;
color: #676767;
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 3px;
margin-bottom: 3px;
white-space:nowrap; /*不换行 */
overflow:hidden; /*内容超出宽度时隐藏超出部分的内容 */
text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
}
.menu:hover{
background-color: #304156;
color: white;
border-radius: 2px;
}
.kong {
height: 10px;
color: #676767;
padding-left: 5px;
padding-top: 2px;
margin-top: 5px;
margin-bottom: 5px;
}
</style>
方式2:用top+height 和 页面的高度对比,调整top的方式显示
showToggle(item, index) {
this.currentIndex = index;
this.childrenMenu = item;
this.$nextTick(() => {
console.log(this.$refs.menuItem[index].offsetTop);
console.log(this.$refs.menuPanel[index].offsetHeight);
console.log(window.innerHeight);
console.log("----------");
var height = this.$refs.menuItem[index].offsetTop + this.$refs.menuPanel[index].offsetHeight;
if (height > window.innerHeight) {
this.itemTop = window.innerHeight - height;
} else {
this.itemTop = 0;
}
});
// this.isShowChildren = true
},
第二种代码方式如下:
<template>
<div class="custom-menu-container">
<logo v-if="showLogo" :collapse="isCollapse" />
<div class="custom-menu-wrapper">
<div ref="menuItem" :class="[
'custom-menu-item',
{ 'small-coll': isCollapse },
{ 't-active': currentIndex === index },
]" v-for="(item, index) in menuList" :key="index" @mouseleave="handleHide" @mouseover="showToggle(item, index)">
<h3 :class="['custom-menu-title']">
<template v-if="isCollapse">
<item-icon v-if="item.meta" :icon="item.meta && item.meta.icon" />
</template>
<template v-else>
<item-icon v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
</h3>
<i class="el-icon-caret-right arrow-icon" v-if="!isCollapse" @mouseover="showToggle(item, index)"></i>
<div ref="menuPanel" :style="'top:'+itemTop+'px'" @mouseover="showToggle(item, index)" @mouseleave="handleHide" :class="[
'dynamic-menu-child',
{ 'dynamic-active': currentIndex === index },
{ 'space-left': isCollapse },
]">
<i class="el-icon-close arrow-close" @click="handleHide" title="点击关闭"></i>
<div class="conten-list">
<CustomLeftMenu :menuList="item.children" :colCount="10" @change-child="handleGrandIitem" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import path from "path";
import { mapGetters, mapState } from "vuex";
import CustomLeftMenu from "./CustomLeftMenu";
import Empty from "./Empty";
import MenuItem from "./MenuItem";
import ItemIcon from "./ItemIcon";
import Logo from "./Logo";
export default {
components: { CustomLeftMenu, Empty, MenuItem, ItemIcon, Logo },
data() {
return {
itemTop: 0,
tempList: [],
isShowChildren: false,
currentIndex: -1,
childrenMenu: null, // 当前子菜单
};
},
props: {
menuList: {
type: Array,
default: () => [],
},
},
computed: {
...mapState(["settings"]),
...mapGetters(["sidebarRouters", "sidebar"]),
showLogo() {
return this.$store.state.settings.sidebarLogo;
},
isCollapse() {
return !this.sidebar.opened;
},
},
methods: {
handleChildIitem(value) {
try {
this.$router.push(path.resolve(this.childrenMenu.path, value.path));
setTimeout(() => {
this.handleHide();
}, 500);
} catch (error) {
console.log(error);
}
},
handleGrandIitem(root, node) {
try {
if (node.query == undefined) {
node.query = "{}";
}
var url = path.resolve(this.childrenMenu.path, `${root.path}/${node.path}`);
var query = JSON.parse(node.query);
this.$router.push({ path: url, query: query });
setTimeout(() => {
this.handleHide();
}, 1000);
} catch (error) {
console.log(error);
}
},
showToggle(item, index) {
this.currentIndex = index;
this.childrenMenu = item;
this.$nextTick(() => {
console.log(this.$refs.menuItem[index].offsetTop);
console.log(this.$refs.menuPanel[index].offsetHeight);
console.log(window.innerHeight);
console.log("----------");
var height = this.$refs.menuItem[index].offsetTop + this.$refs.menuPanel[index].offsetHeight;
if (height > window.innerHeight) {
this.itemTop = window.innerHeight - height;
} else {
this.itemTop = 0;
}
});
// this.isShowChildren = true
},
handleHide() {
// this.isShowChildren = false
// console.log('handleHide')
this.currentIndex = -1;
},
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/styles/custom-menu.scss";
@import "./style.scss";
.menu-items {
display: block;
}
::v-deep .custom-sub-menu-item .sub-menu-item-title {
display: block;
}
::v-deep .custom-sub-menu-item {
color: #bfcbd9;
}
.custom-menu-container .custom-menu-item .dynamic-menu-child .conten-list {
min-height: 0px;
padding-left: 10px;
}
@media screen and (max-height: 900px) {
.custom-menu-item {
max-height: 30px;
}
}
@media screen and (max-height: 800px) {
.custom-menu-item {
max-height: 27px;
}
}
</style>