<template>
<div
class="concise-menu"
ref="conciseMenu"
:style="menuStyle"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<div class="menu-search">
<div class="menu-title">
<div class="title">功能搜索:</div>
</div>
<div class="menu-search-box">
<input
type="text"
class="menu-search-input"
placeholder="支持拼音首字母搜索"
v-model.trim="searchWord"
/>
<i class="iconfont icon-search"></i>
</div>
</div>
<div class="menu-content">
<div class="level1List" v-if="searchWord == ''">
<!-- 一级菜单列表 -->
<ul class="area">
<li v-for="item in menus" :key="item.resId">
<div class="menu-item">
<div
class="level first-level"
:class="{
active: curresId == item.resId,
noChild: !item.children || !item.children.length,
}"
@click="handleFirstLevelClick(item)"
@mouseenter="handleFirstLevelMouseEnter(item)"
>
<span class="title">{{ item.name }}</span>
<i
v-if="item.children && item.children.length"
class="iconfont icon-arrow-right"
></i>
</div>
</div>
</li>
</ul>
<!-- 二级及以下菜单列表 -->
<ul class="area child-menu">
<li v-for="item in childrenData" :key="item.resId">
<div class="menu-item menu-item2">
<div>
<div
class="level second-level"
:class="{
active:
curresId == item.parentId &&
item.children &&
item.children.length,
noChild: !item.children || !item.children.length,
}"
@click="handleSecondLevelClick(item)"
>
<span class="title">{{ item.name }}</span>
<!-- <i
v-if="item.children && item.children.length"
class="iconfont icon-arrow-right"
></i> -->
</div>
</div>
<!-- 三级菜单 -->
<ul
class="child-area"
v-if="item.children && item.children.length"
>
<template v-for="child in item.children">
<!-- 普通三级菜单项 -->
<li
:key="child.resId"
v-if="
child.isShow && !(child.children && child.children.length)
"
>
<div class="menu-item">
<div class="third-level" @click="handleOpenFunc(child)">
<span class="title">{{ child.name }}</span>
<i
v-if="child.children && child.children.length"
class="iconfont icon-arrow-right"
></i>
</div>
</div>
</li>
<!-- 嵌套子菜单组件 -->
<nav-sub-menus
:level="0"
:menuitem="child"
:parent="conciseMenu"
v-if="
child.isShow && child.children && child.children.length
"
:key="child.resId"
>
</nav-sub-menus>
</template>
</ul>
</div>
</li>
</ul>
</div>
<div class="searchResultList" v-else>
<ul>
<li
v-for="(searchItem, index) in searchList"
:key="'search-' + index"
:data-index="index"
:title="searchItem.name"
@click="handleOpenFunc(searchItem)"
>
<itemIcon :item="searchItem"></itemIcon>
<span>{{ searchItem.name }}</span>
</li>
<li v-show="searchList.length == 0">
{{ $t("hivuiMain_nodata") }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import itemIcon from "@main/views/layout/components/allFuncMenu/itemIcon";
import NavSubMenus from "./menus.vue";
import pinyin from "js-pinyin";
import cloneDeep from "lodash/cloneDeep";
export default {
name: "ConciseMenu",
inject: ["addTab"],
provide() {
let me = this;
return {
hideMenus() {
me.handleMouseLeave();
},
};
},
components: { NavSubMenus, itemIcon },
props: {
targetElRect: {
type: Object,
required: true,
},
placement: {
type: String,
default: "right",
},
},
data() {
return {
searchWord: "",
searchList: [],
isVisible: false,
isMouseOverMenu: false,
curresId: "",
childrenData: [],
};
},
watch: {
targetElRect: {
handler() {
this.updatePosition();
},
deep: true,
},
searchWord(newVal, oldVal) {
this.seldIndex = 0;
this.doSearch(newVal);
},
},
computed: {
menuStyle() {
return {
position: "absolute",
top: this.isVisible ? this.position.top + "px" : "-9999px",
left: this.position.left + "px",
opacity: this.isVisible ? 1 : 0,
visibility: this.isVisible ? "visible" : "hidden",
width: this.isVisible ? "700px" : "0",
transition: " opacity 0.3s ease 0.1s, visibility 0.3s ease 0.1s",
backgroundColor: "rgba(255, 255, 255, 1)",
border: "1px solid rgba(0, 102, 204, 1)",
boxShadow: "0px 0px 6px rgba(0, 0, 0, 0.25)",
boxSizing: "border-box",
overflow: "hidden",
zIndex: 1000,
};
},
position() {
return {
top: 0,
left: 0,
};
},
menus() {
const menus = this.$store.getters.menus.filter(item => item.isShow);
return menus;
},
},
methods: {
/**
* 显示菜单
*/
show() {
this.isVisible = true;
this.$nextTick(() => {
this.updatePosition();
});
},
/**
* 隐藏菜单
*/
hide() {
this.isVisible = false;
},
/**
* 更新菜单位置
*/
updatePosition() {
if (!this.isVisible) return;
this.$nextTick(() => {
const menuEl = this.$el;
if (!menuEl) return;
const rect = this.targetElRect;
if (!rect) return;
let top = rect.top;
let left = rect.right;
if(this.placement != "right"){
top = top + rect.height;
left = 0;
}
// 应用位置
menuEl.style.top = top + "px";
menuEl.style.left = left + "px";
});
},
/**
* 鼠标进入菜单区域处理
*/
handleMouseEnter(e) {
if (e) {
const target = e.target || e.srcElement;
if (target.closest && target.closest(".pl-menus-item")) {
this.isMouseOverMenu = true;
return;
}
}
this.isMouseOverMenu = true;
},
/**
* 鼠标离开菜单区域处理
*/
handleMouseLeave(e) {
if (e) {
// 检查鼠标是否移向.pl-menus-item类元素
const relatedTarget = e.relatedTarget || e.toElement;
if (
relatedTarget &&
relatedTarget.closest &&
relatedTarget.closest(".pl-menus-item")
) {
this.isMouseOverMenu = true;
return;
}
}
this.isMouseOverMenu = false;
this.hide();
},
/**
* 处理一级菜单点击事件
*/
handleFirstLevelClick(item) {
if (!item.children || !item.children.length) {
this.handleOpenFunc(item);
}
},
/**
* 处理一级菜单鼠标悬停事件
*/
handleFirstLevelMouseEnter(item) {
if (item.children && item.children.length) {
this.handleOpenFunc(item, true);
}
},
/**
* 处理二级菜单点击事件
*/
handleSecondLevelClick(item) {
if (!item.children || !item.children.length) {
this.handleOpenFunc(item);
}
},
/**
* 打开功能页面
* @param {Object} item - 菜单项
* @param {Boolean} isTabClick - 是否是标签页点击(仅设置子菜单数据)
*/
async handleOpenFunc(item, isTabClick) {
let me = this;
if (isTabClick) {
this.setChildrenData(item.resId);
return;
}
if (item.type == "link") {
window.open(item.resUrl, item.name);
return;
} else if (item.type == "sso") {
item.ssoUrl = await me.$store.dispatch("user/openSSOFuncPage", {
serviceUrl: item.resUrl,
});
}
// 添加标签页
me.addTab(item);
setTimeout(() => {
me.handleMouseLeave && me.handleMouseLeave();
}, 100);
},
/**
* 设置子菜单数据
* @param {String} resId - 菜单资源ID
*/
setChildrenData(resId) {
this.curresId = resId;
const menu = this.menus.find((m) => m.resId === resId);
if (menu && menu.children) {
this.childrenData = menu.children;
} else {
this.childrenData = [];
}
},
//获取搜索列表数据
doSearch(cnKey) {
let me = this;
let list = [],
records = me.$store.getters.menusList,
hideMenuItemList = me.$store.getters.hideMenuItemList;
let py = pinyin.getCamelChars(cnKey).toLocaleLowerCase();
let re = new RegExp("^[a-zA-Z]+$");
for (let i = 0, l = records.length; i < l; i++) {
let item = records[i];
if (!(!item.isShow || hideMenuItemList.indexOf(item.parentId) != -1)) {
if (item.parentId == -1 && item.type == "root") {
continue;
}
if (re.test(cnKey)) {
let y = pinyin.getCamelChars(item.name).toLocaleLowerCase();
if (y.indexOf(py) > -1 && item.type != "dir") {
list.push(item);
}
} else {
if (item.name.indexOf(cnKey) > -1 && item.type != "dir") {
list.push(item);
}
}
}
}
me.$set(me, "searchList", list);
// me.$nextTick(() => {
// me.countHeight();
// me.$refs.listBox.scrollTo({
// top: 0,
// });
// });
},
},
mounted() {
if (this.menus.length > 0) {
const menu = this.menus[0];
this.setChildrenData(menu.resId);
}
},
};
</script>
<style lang="less" scoped>
.concise-menu {
// border-radius: 5px;
.title{
font-weight: 400;
}
ul,
li {
padding: 0;
margin: 0;
list-style: none;
}
* {
box-sizing: border-box;
}
.menu-search {
display: flex;
background-color: rgba(235, 241, 247, 1);
border-bottom: 1px solid rgba(206, 215, 224, 1);
height: 48px;
padding-left: 15px;
align-items: center;
.menu-title {
// width: 110px;
text-align: right;
padding-right: 10px;
}
.menu-search-box {
width: 240px;
display: flex;
align-items: center;
background-color: #fff;
padding-right: 10px;
border-radius: 5px;
input {
flex: 1;
border: none;
outline: none;
line-height: 28px;
padding: 0 15px;
border-radius: 5px;
}
i {
font-size: 20px;
cursor: pointer;
}
}
}
.menu-content {
position: relative;
.level1List{
min-height: 100px;
max-height: calc(100vh - 200px);
overflow: auto;
}
.area {
display: flex;
flex-direction: column;
li{
&:first-child{
.menu-item{
.first-level{
border-top: none;
}
}
}
&:last-child{
.menu-item2{
border-bottom: none;
}
}
}
&.child-menu {
width: calc(100% - 168px);
height: 100%;
position: absolute;
top: 0;
right: 0;
background-color: #fff;
overflow: auto;
padding-left: 24px;
padding-right: 20px;
.menu-item2{
padding: 12px 0;
border-bottom: 1px dashed rgba(184, 184, 184, 1);
}
}
.menu-item {
display: flex;
flex-direction: column;
.level {
position: relative;
width: 168px;
text-align: left;
padding-left: 15px;
i {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
}
&.noChild {
cursor: pointer;
}
&.first-level {
cursor: pointer;
background-color: rgba(247, 247, 247, 1);
// border: 1px solid #ced7e0;
border-bottom: none;
line-height: 40px;
&.active {
color: rgba(0, 102, 204, 1);
background-color: #fff;
border-right-color: transparent;
cursor: default;
}
&:hover {
color: rgba(0, 102, 204, 1);
}
}
&.second-level {
width: 100%;
// padding-top: 5px;
padding-left: 5px;
// padding-bottom: 5px;
&.active {
color: rgba(161, 161, 182, 1);
background-color: #fff;
border-color: transparent;
&:hover {
color: rgba(161, 161, 182, 1);
}
}
&:hover {
color: rgba(0, 102, 204, 1);
}
}
}
}
.child-area {
width: 100%;
display: flex;
flex-wrap: wrap;
padding-top: 3px;
// gap: 8px;
// border-bottom: 1px solid #ced7e0;
li{
width: 33%;
// margin-bottom: 12px;
padding: 5px 6px;
color:rgba(51, 51, 51, 1);
}
.third-level {
// padding: 0 8px;
// padding-left: 0;
// border-left: 1px solid #ced7e0;
cursor: pointer;
&:hover {
color: rgba(0, 102, 204, 1);
}
}
::v-deep .pl-menus-item {
// padding: 0 8px;
// padding-left: 0;
// border-left: 1px solid #ced7e0;
&.pl-menus-itemSeld {
color: rgba(0, 102, 204, 1);
}
.title {
position: relative;
font-weight: 400;
color: #333333;
.txt {
display: inline-block;
i {
position: initial;
transform: rotate(90deg);
// right: -5px;
// top: 50%;
// transform: translateY(-50%);
}
}
}
}
}
}
.searchResultList {
width: 100%;
min-height: 100px;
max-height: calc(100vh - 200px);
overflow: auto;
padding: 5px 10px;
ul {
display: flex;
flex-wrap: wrap;
padding-bottom: 10px;
li {
flex: 0 0 33.3%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 32px;
font-size: 14px;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
color: #06c;
}
}
.itemSeld {
color: #f00;
}
}
}
}
}
</style>