(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"> <script setup lang="ts">
import { computed, ComputedRef, provide, Ref, ref, watch } from "vue"; import { computed, ComputedRef, provide, Ref, ref, watch } from "vue";
import { provideLevel} from "./useLevel"
import "./index.less"; import "./index.less";
export interface LayMenuProps { export interface LayMenuProps {
@ -37,8 +38,8 @@ const props = withDefaults(defineProps<LayMenuProps>(), {
collapseTransition: true, collapseTransition: true,
}); });
const isTree: ComputedRef = computed(() => props.tree); const isTree: ComputedRef<boolean> = computed(() => props.tree);
const isCollapse: ComputedRef = computed(() => props.collapse); const isCollapse: ComputedRef<boolean | string> = computed(() => props.collapse);
const isCollapseTransition: ComputedRef = computed( const isCollapseTransition: ComputedRef = computed(
() => props.collapseTransition () => props.collapseTransition
); );
@ -77,6 +78,7 @@ watch(
{ immediate: true } { immediate: true }
); );
provideLevel(1);
provide("isTree", isTree); provide("isTree", isTree);
provide("selectedKey", selectedKey); provide("selectedKey", selectedKey);
provide("openKeys", openKeys); 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>
<script setup lang="ts"> <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 { export interface LayMenuItemProps {
id: string; id: string;
@ -14,18 +15,35 @@ export interface LayMenuItemProps {
const slots = useSlots(); const slots = useSlots();
const props = defineProps<LayMenuItemProps>(); const props = defineProps<LayMenuItemProps>();
const { level } = useLevel();
const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>; 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 () { const selectHandle = function () {
selectedKey.value = props.id; selectedKey.value = props.id;
}; };
const needTooltip = computed(() => isTree.value && (isCollapse.value === true || isCollapse.value === "true") && level.value === 1);
</script> </script>
<template> <template>
<li <li class="layui-nav-item" :class="[selectedKey === id ? 'layui-this' : '']" @click="selectHandle()">
class="layui-nav-item" <template v-if="needTooltip">
:class="[selectedKey === id ? 'layui-this' : '']" <lay-tooltip position="right">
@click="selectHandle()" <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)"> <a href="javascript:void(0)">
<i v-if="slots.icon"> <i v-if="slots.icon">
<slot name="icon"></slot> <slot name="icon"></slot>
@ -37,5 +55,6 @@ const selectHandle = function () {
<slot></slot> <slot></slot>
</span> </span>
</a> </a>
</template>
</li> </li>
</template> </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"; } from "vue";
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from "@vueuse/core";
import LayTransition from "../transition/index.vue"; import LayTransition from "../transition/index.vue";
import SubMenuPopup from "./subMenuPopup.vue";
import { provideLevel, default as useLevel } from "../menu/useLevel";
export interface LaySubMenuProps { export interface LaySubMenuProps {
id: string; id: string;
@ -25,10 +28,11 @@ export interface LaySubMenuProps {
const slots = useSlots(); const slots = useSlots();
const props = defineProps<LaySubMenuProps>(); const props = defineProps<LaySubMenuProps>();
const { level } = useLevel()
const isTree: Ref<boolean> = inject("isTree") as Ref<boolean>; const isTree: Ref<boolean> = inject("isTree") as Ref<boolean>;
const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>; const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>;
const openKeys: Ref<string[]> = inject("openKeys") 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( const isCollapseTransition: Ref<boolean> = inject(
"isCollapseTransition" "isCollapseTransition"
) as Ref<boolean>; ) as Ref<boolean>;
@ -39,6 +43,13 @@ const isOpen = computed(() => {
const subMenuRef = ref<HTMLElement>(); const subMenuRef = ref<HTMLElement>();
const position = ref<String>(); 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, () => { watch(isOpen, () => {
if (isOpen.value && position.value !== "left-nav") { if (isOpen.value && position.value !== "left-nav") {
@ -87,6 +98,7 @@ onBeforeUnmount(() => window.removeEventListener("resize", setPosition));
</script> </script>
<template> <template>
<template v-if="!computedPopup">
<li class="layui-nav-item"> <li class="layui-nav-item">
<a href="javascript:void(0)" @click="openHandle()"> <a href="javascript:void(0)" @click="openHandle()">
<!-- 图标 --> <!-- 图标 -->
@ -101,14 +113,11 @@ onBeforeUnmount(() => window.removeEventListener("resize", setPosition));
<i v-if="slots.expandIcon" class="layui-nav-more"> <i v-if="slots.expandIcon" class="layui-nav-more">
<slot name="expandIcon" :isExpand="isOpen"></slot> <slot name="expandIcon" :isExpand="isOpen"></slot>
</i> </i>
<i <i v-else :class="[
v-else
:class="[
isOpen ? 'layui-nav-mored' : '', isOpen ? 'layui-nav-mored' : '',
'layui-icon layui-icon-down', 'layui-icon layui-icon-down',
'layui-nav-more', 'layui-nav-more',
]" ]"></i>
></i>
</a> </a>
<template v-if="isTree"> <template v-if="isTree">
<lay-transition :enable="isCollapseTransition"> <lay-transition :enable="isCollapseTransition">
@ -120,13 +129,27 @@ onBeforeUnmount(() => window.removeEventListener("resize", setPosition));
</lay-transition> </lay-transition>
</template> </template>
<template v-else> <template v-else>
<dl <dl ref="subMenuRef" class="layui-nav-child layui-anim layui-anim-upbit"
ref="subMenuRef" :class="[{ 'layui-show': isOpen }, position]">
class="layui-nav-child layui-anim layui-anim-upbit"
:class="[{ 'layui-show': isOpen }, position]"
>
<slot></slot> <slot></slot>
</dl> </dl>
</template> </template>
</li> </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> </template>

View File

@ -3,7 +3,9 @@
<span><slot></slot></span> <span><slot></slot></span>
</div> </div>
<slot v-else></slot> <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> </template>
<script lang="ts"> <script lang="ts">
@ -18,7 +20,7 @@ export default defineComponent({
props: { props: {
content: { content: {
type: [Number, String], type: [Number, String],
required: true, required: false,
}, },
position: { position: {
type: String, type: String,