mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-25 07:57:18 +08:00
320 lines
7.9 KiB
Vue
320 lines
7.9 KiB
Vue
<script setup lang="ts">
|
||
import path from "path";
|
||
import { getConfig } from "@/config";
|
||
import extraIcon from "./extraIcon.vue";
|
||
import { childrenType } from "../../types";
|
||
import { useNav } from "@/layout/hooks/useNav";
|
||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
|
||
|
||
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
|
||
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
|
||
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
|
||
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
|
||
|
||
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
|
||
|
||
const props = defineProps({
|
||
item: {
|
||
type: Object as PropType<childrenType>
|
||
},
|
||
isNest: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
basePath: {
|
||
type: String,
|
||
default: ""
|
||
}
|
||
});
|
||
|
||
const getSpanStyle = computed((): CSSProperties => {
|
||
return {
|
||
width: "100%",
|
||
textAlign: "center"
|
||
};
|
||
});
|
||
|
||
const getNoDropdownStyle = computed((): CSSProperties => {
|
||
return {
|
||
display: "flex",
|
||
alignItems: "center"
|
||
};
|
||
});
|
||
|
||
const getMenuTextStyle = computed(() => {
|
||
return {
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
outline: "none"
|
||
};
|
||
});
|
||
|
||
const getsubMenuIconStyle = computed((): CSSProperties => {
|
||
return {
|
||
display: "flex",
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
margin:
|
||
layout.value === "horizontal"
|
||
? "0 5px 0 0"
|
||
: isCollapse.value
|
||
? "0 auto"
|
||
: "0 5px 0 0"
|
||
};
|
||
});
|
||
|
||
const getSubTextStyle = computed((): CSSProperties => {
|
||
if (!isCollapse.value) {
|
||
return {
|
||
width: "210px",
|
||
display: "inline-block",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis"
|
||
};
|
||
} else {
|
||
return {
|
||
width: ""
|
||
};
|
||
}
|
||
});
|
||
|
||
const getSubMenuDivStyle = computed((): any => {
|
||
return item => {
|
||
return !isCollapse.value
|
||
? {
|
||
width: "100%",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "space-between",
|
||
overflow: "hidden"
|
||
}
|
||
: {
|
||
width: "100%",
|
||
textAlign:
|
||
item?.parentId === null
|
||
? "center"
|
||
: layout.value === "mix" && item?.pathList?.length === 2
|
||
? "center"
|
||
: ""
|
||
};
|
||
};
|
||
});
|
||
|
||
const expandCloseIcon = computed(() => {
|
||
if (!getConfig()?.MenuArrowIconNoTransition) return "";
|
||
return {
|
||
"expand-close-icon": useRenderIcon(EpArrowDown),
|
||
"expand-open-icon": useRenderIcon(ArrowUp),
|
||
"collapse-close-icon": useRenderIcon(ArrowRight),
|
||
"collapse-open-icon": useRenderIcon(ArrowLeft)
|
||
};
|
||
});
|
||
|
||
const onlyOneChild: childrenType = ref(null);
|
||
// 存放菜单是否存在showTooltip属性标识
|
||
const hoverMenuMap = new WeakMap();
|
||
// 存储菜单文本dom元素
|
||
const menuTextRef = ref(null);
|
||
|
||
function hoverMenu(key) {
|
||
// 如果当前菜单showTooltip属性已存在,退出计算
|
||
if (hoverMenuMap.get(key)) return;
|
||
|
||
nextTick(() => {
|
||
// 如果文本内容的整体宽度大于其可视宽度,则文本溢出
|
||
menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
|
||
? Object.assign(key, {
|
||
showTooltip: true
|
||
})
|
||
: Object.assign(key, {
|
||
showTooltip: false
|
||
});
|
||
hoverMenuMap.set(key, true);
|
||
});
|
||
}
|
||
|
||
// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
|
||
function overflowSlice(text, item?: any) {
|
||
const newText =
|
||
(text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
|
||
if (item && !(isCollapse.value && item?.parentId === null)) {
|
||
return layout.value === "mix" &&
|
||
item?.pathList?.length === 2 &&
|
||
isCollapse.value
|
||
? newText
|
||
: text;
|
||
}
|
||
return newText;
|
||
}
|
||
|
||
function hasOneShowingChild(
|
||
children: childrenType[] = [],
|
||
parent: childrenType
|
||
) {
|
||
const showingChildren = children.filter((item: any) => {
|
||
onlyOneChild.value = item;
|
||
return true;
|
||
});
|
||
|
||
if (showingChildren[0]?.meta?.showParent) {
|
||
return false;
|
||
}
|
||
|
||
if (showingChildren.length === 1) {
|
||
return true;
|
||
}
|
||
|
||
if (showingChildren.length === 0) {
|
||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function resolvePath(routePath) {
|
||
const httpReg = /^http(s?):\/\//;
|
||
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
|
||
return routePath || props.basePath;
|
||
} else {
|
||
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
|
||
return path.posix.resolve(props.basePath, routePath);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<el-menu-item
|
||
v-if="
|
||
hasOneShowingChild(props.item.children, props.item) &&
|
||
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
|
||
"
|
||
:index="resolvePath(onlyOneChild.path)"
|
||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||
:style="getNoDropdownStyle"
|
||
>
|
||
<div
|
||
v-if="toRaw(props.item.meta.icon)"
|
||
class="sub-menu-icon"
|
||
:style="getsubMenuIconStyle"
|
||
>
|
||
<component
|
||
:is="
|
||
useRenderIcon(
|
||
toRaw(onlyOneChild.meta.icon) ||
|
||
(props.item.meta && toRaw(props.item.meta.icon))
|
||
)
|
||
"
|
||
/>
|
||
</div>
|
||
<span
|
||
v-if="
|
||
!props.item?.meta.icon &&
|
||
isCollapse &&
|
||
layout === 'vertical' &&
|
||
props.item?.pathList?.length === 1
|
||
"
|
||
:style="getSpanStyle"
|
||
>
|
||
{{ overflowSlice(onlyOneChild.meta.title) }}
|
||
</span>
|
||
<span
|
||
v-if="
|
||
!onlyOneChild.meta.icon &&
|
||
isCollapse &&
|
||
layout === 'mix' &&
|
||
props.item?.pathList?.length === 2
|
||
"
|
||
:style="getSpanStyle"
|
||
>
|
||
{{ overflowSlice(onlyOneChild.meta.title) }}
|
||
</span>
|
||
<template #title>
|
||
<div :style="getDivStyle">
|
||
<span v-if="layout === 'horizontal'">
|
||
{{ onlyOneChild.meta.title }}
|
||
</span>
|
||
<el-tooltip
|
||
v-else
|
||
placement="top"
|
||
:effect="tooltipEffect"
|
||
:offset="-10"
|
||
:disabled="!onlyOneChild.showTooltip"
|
||
>
|
||
<template #content>
|
||
{{ onlyOneChild.meta.title }}
|
||
</template>
|
||
<span
|
||
ref="menuTextRef"
|
||
:style="getMenuTextStyle"
|
||
@mouseover="hoverMenu(onlyOneChild)"
|
||
>
|
||
{{ onlyOneChild.meta.title }}
|
||
</span>
|
||
</el-tooltip>
|
||
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
|
||
</div>
|
||
</template>
|
||
</el-menu-item>
|
||
|
||
<el-sub-menu
|
||
v-else
|
||
ref="subMenu"
|
||
v-bind="expandCloseIcon"
|
||
:index="resolvePath(props.item.path)"
|
||
>
|
||
<template #title>
|
||
<div
|
||
v-if="toRaw(props.item.meta.icon)"
|
||
:style="getsubMenuIconStyle"
|
||
class="sub-menu-icon"
|
||
>
|
||
<component
|
||
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
|
||
/>
|
||
</div>
|
||
<span v-if="layout === 'horizontal'">
|
||
{{ props.item.meta.title }}
|
||
</span>
|
||
<div
|
||
:style="getSubMenuDivStyle(props.item)"
|
||
v-if="
|
||
!(
|
||
isCollapse &&
|
||
toRaw(props.item.meta.icon) &&
|
||
props.item.parentId === null
|
||
)
|
||
"
|
||
>
|
||
<el-tooltip
|
||
v-if="layout !== 'horizontal'"
|
||
placement="top"
|
||
:effect="tooltipEffect"
|
||
:offset="-10"
|
||
:disabled="!props.item.showTooltip"
|
||
>
|
||
<template #content>
|
||
{{ props.item.meta.title }}
|
||
</template>
|
||
<span
|
||
ref="menuTextRef"
|
||
:style="getSubTextStyle"
|
||
@mouseover="hoverMenu(props.item)"
|
||
>
|
||
{{ overflowSlice(props.item.meta.title, props.item) }}
|
||
</span>
|
||
</el-tooltip>
|
||
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
|
||
</div>
|
||
</template>
|
||
<sidebar-item
|
||
v-for="child in props.item.children"
|
||
:key="child.path"
|
||
:is-nest="true"
|
||
:item="child"
|
||
:base-path="resolvePath(child.path)"
|
||
class="nest-menu"
|
||
/>
|
||
</el-sub-menu>
|
||
</template>
|