(component): [menu]悬浮菜单

This commit is contained in:
sight 2022-06-27 18:24:57 +08:00
parent aea1936da4
commit 1aaaa83c36
6 changed files with 245 additions and 61 deletions

View File

@ -6,6 +6,7 @@ export default {
<script setup lang="ts">
import { computed, ComputedRef, provide, Ref, ref, watch } from "vue";
import { provideLevel} from "./useLevel"
import "./index.less";
export interface LayMenuProps {
@ -37,8 +38,8 @@ const props = withDefaults(defineProps<LayMenuProps>(), {
collapseTransition: true,
});
const isTree: ComputedRef = computed(() => props.tree);
const isCollapse: ComputedRef = computed(() => props.collapse);
const isTree: ComputedRef<boolean> = computed(() => props.tree);
const isCollapse: ComputedRef<boolean | string> = computed(() => props.collapse);
const isCollapseTransition: ComputedRef = computed(
() => props.collapseTransition
);
@ -77,6 +78,7 @@ watch(
{ immediate: true }
);
provideLevel(1);
provide("isTree", isTree);
provide("selectedKey", selectedKey);
provide("openKeys", openKeys);

View File

@ -0,0 +1,30 @@
import { computed, inject, provide, reactive, Ref, isRef, ComputedRef, UnwrapNestedRefs } from "vue";
export const LevelInjectionKey = Symbol("menuLevelKey")
export function provideLevel(level: Ref<number> | number) {
const computedLevel = computed(() => (isRef(level) ? level.value : level));
provide(
LevelInjectionKey,
reactive({
level: computedLevel,
})
);
}
export default function useLevel(props?: { provideNextLevel?: boolean }) {
const { provideNextLevel } = props || {};
const levelContext = inject(LevelInjectionKey) as UnwrapNestedRefs<{
level: ComputedRef<number>;
}>;
const level = computed(() => levelContext.level || 1);
if (provideNextLevel) {
const nextLevel = computed(() => level.value + 1);
provideLevel(nextLevel);
}
return {
level,
};
}

View File

@ -5,7 +5,8 @@ export default {
</script>
<script setup lang="ts">
import { inject, Ref, useSlots } from "vue";
import { computed, ComputedRef, inject, ref, Ref, useSlots } from "vue";
import useLevel from '../menu/useLevel';
export interface LayMenuItemProps {
id: string;
@ -14,28 +15,46 @@ export interface LayMenuItemProps {
const slots = useSlots();
const props = defineProps<LayMenuItemProps>();
const { level } = useLevel();
const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>;
const isTree = inject("isTree") as ComputedRef<boolean>;
const isCollapse = inject("isCollapse") as ComputedRef<boolean | string>;
const selectHandle = function () {
selectedKey.value = props.id;
};
const needTooltip = computed(() => isTree.value && (isCollapse.value === true || isCollapse.value === "true") && level.value === 1);
</script>
<template>
<li
class="layui-nav-item"
:class="[selectedKey === id ? 'layui-this' : '']"
@click="selectHandle()"
>
<a href="javascript:void(0)">
<i v-if="slots.icon">
<slot name="icon"></slot>
</i>
<span v-if="slots.title">
<slot name="title"></slot>
</span>
<span v-else>
<slot></slot>
</span>
</a>
<li class="layui-nav-item" :class="[selectedKey === id ? 'layui-this' : '']" @click="selectHandle()">
<template v-if="needTooltip">
<lay-tooltip position="right">
<a href="javascript:void(0)">
<i v-if="slots.icon">
<slot name="icon"></slot>
</i>
</a>
<template #content>
<span v-if="slots.title">
<slot name="title"></slot>
</span>
</template>
</lay-tooltip>
</template>
<template v-else>
<a href="javascript:void(0)">
<i v-if="slots.icon">
<slot name="icon"></slot>
</i>
<span v-if="slots.title">
<slot name="title"></slot>
</span>
<span v-else>
<slot></slot>
</span>
</a>
</template>
</li>
</template>

View File

@ -0,0 +1,108 @@
<script lang="ts">
export default {
name: "SubMenuPopup",
};
</script>
<script setup lang="ts">
import {
computed,
inject,
Ref,
useSlots,
} from "vue";
import LayDropdown from "../dropdown/index.vue";
import useLevel from "../menu/useLevel";
export interface LaySubMenuPopupProps {
id: string;
title?: string;
}
const slots = useSlots();
const props = defineProps<LaySubMenuPopupProps>();
const openKeys: Ref<string[]> = inject("openKeys") as Ref<string[]>;
const { level } = useLevel();
const isOpen = computed(() => {
return openKeys.value.includes(props.id);
});
</script>
<template>
<li :class="[
'layui-nav-item sub-menu-popup',
level > 2 ? 'original': '' ,
]">
<lay-dropdown trigger="hover" placement="right-top">
<a href="javascript:void(0)">
<!-- 图标 -->
<i>
<slot v-if="slots.icon" name="icon"></slot>
</i>
<!-- 标题 -->
<span>
<slot v-if="slots.title" name="title"></slot>
</span>
<!-- 扩展 -->
<i v-if="slots.expandIcon" class="layui-nav-more">
<slot name="expandIcon" :isExpand="isOpen">
</slot>
</i>
<i v-else class="layui-icon layui-icon-down layui-nav-more"></i>
</a>
<template #content>
<slot></slot>
</template>
</lay-dropdown>
</li>
</template>
<style lang="less">
.sub-menu-popup>.layui-dropdown>div>a {
display: block;
text-overflow: clip;
height: 40px;
line-height: 40px;
position: relative;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 5px 23px 5px 23px;
}
.sub-menu-popup>.layui-dropdown dl {
background-color: #393d49;
}
.sub-menu-popup>.layui-dropdown dl>li>a {
span {
display: inline;
}
.layui-nav-item>a {
text-overflow: clip;
}
.layui-nav-more {
display: inline-block;
}
}
.layui-nav.layui-nav-collapse .sub-menu-popup.original {
width: 100%;
span {
display: inline;
}
.layui-nav-item>a {
text-overflow: clip;
}
.layui-nav-more {
display: inline-block;
}
}
</style>

View File

@ -16,6 +16,9 @@ import {
} from "vue";
import { onClickOutside } from "@vueuse/core";
import LayTransition from "../transition/index.vue";
import SubMenuPopup from "./subMenuPopup.vue";
import { provideLevel, default as useLevel } from "../menu/useLevel";
export interface LaySubMenuProps {
id: string;
@ -25,10 +28,11 @@ export interface LaySubMenuProps {
const slots = useSlots();
const props = defineProps<LaySubMenuProps>();
const { level } = useLevel()
const isTree: Ref<boolean> = inject("isTree") as Ref<boolean>;
const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>;
const openKeys: Ref<string[]> = inject("openKeys") as Ref<string[]>;
const isCollapse: Ref<boolean> = inject("isCollapse") as Ref<boolean>;
const isCollapse: Ref<boolean | string> = inject("isCollapse") as Ref<boolean | string>;
const isCollapseTransition: Ref<boolean> = inject(
"isCollapseTransition"
) as Ref<boolean>;
@ -39,6 +43,13 @@ const isOpen = computed(() => {
const subMenuRef = ref<HTMLElement>();
const position = ref<String>();
const nextLevel = computed(() =>
level.value + 1
);
provideLevel(nextLevel);
const computedPopup = computed(() => isTree.value && (isCollapse.value === true || isCollapse.value === "true"));
watch(isOpen, () => {
if (isOpen.value && position.value !== "left-nav") {
@ -87,46 +98,58 @@ onBeforeUnmount(() => window.removeEventListener("resize", setPosition));
</script>
<template>
<li class="layui-nav-item">
<a href="javascript:void(0)" @click="openHandle()">
<!-- 图标 -->
<i>
<slot v-if="slots.icon" name="icon"></slot>
</i>
<!-- 标题 -->
<span>
<slot v-if="slots.title" name="title"></slot>
</span>
<!-- 扩展 -->
<i v-if="slots.expandIcon" class="layui-nav-more">
<slot name="expandIcon" :isExpand="isOpen"></slot>
</i>
<i
v-else
:class="[
<template v-if="!computedPopup">
<li class="layui-nav-item">
<a href="javascript:void(0)" @click="openHandle()">
<!-- 图标 -->
<i>
<slot v-if="slots.icon" name="icon"></slot>
</i>
<!-- 标题 -->
<span>
<slot v-if="slots.title" name="title"></slot>
</span>
<!-- 扩展 -->
<i v-if="slots.expandIcon" class="layui-nav-more">
<slot name="expandIcon" :isExpand="isOpen"></slot>
</i>
<i v-else :class="[
isOpen ? 'layui-nav-mored' : '',
'layui-icon layui-icon-down',
'layui-nav-more',
]"
></i>
</a>
<template v-if="isTree">
<lay-transition :enable="isCollapseTransition">
<div v-if="isOpen">
<dl class="layui-nav-child">
<slot></slot>
</dl>
</div>
</lay-transition>
</template>
<template v-else>
<dl
ref="subMenuRef"
class="layui-nav-child layui-anim layui-anim-upbit"
:class="[{ 'layui-show': isOpen }, position]"
>
<slot></slot>
</dl>
</template>
</li>
]"></i>
</a>
<template v-if="isTree">
<lay-transition :enable="isCollapseTransition">
<div v-if="isOpen">
<dl class="layui-nav-child">
<slot></slot>
</dl>
</div>
</lay-transition>
</template>
<template v-else>
<dl ref="subMenuRef" class="layui-nav-child layui-anim layui-anim-upbit"
:class="[{ 'layui-show': isOpen }, position]">
<slot></slot>
</dl>
</template>
</li>
</template>
<template v-else>
<SubMenuPopup :id="id">
<template #icon>
<slot v-if="slots.icon" name="icon"></slot>
</template>
<template #title>
<slot v-if="slots.title" name="title"></slot>
</template>
<template #expandIcon>
<slot v-if="slots.expandIcon" name="expandIcon"></slot>
</template>
<template #default>
<slot name="default"></slot>
</template>
</SubMenuPopup>
</template>
</template>

View File

@ -3,7 +3,9 @@
<span><slot></slot></span>
</div>
<slot v-else></slot>
<lay-popper v-if="isMounted" v-bind="innerProps"></lay-popper>
<lay-popper v-if="isMounted" v-bind="innerProps">
<slot name="content"></slot>
</lay-popper>
</template>
<script lang="ts">
@ -18,7 +20,7 @@ export default defineComponent({
props: {
content: {
type: [Number, String],
required: true,
required: false,
},
position: {
type: String,