From f446cbf9e5895205317ead63e81c5b599227fa1c Mon Sep 17 00:00:00 2001 From: Netfan Date: Sun, 15 Dec 2024 18:24:22 +0800 Subject: [PATCH] feat: user-dropdown support `hover` trigger (#5143) * feat: user-dropdown support `hover` trigger * fix: modified type declaration --- packages/effects/hooks/package.json | 1 + packages/effects/hooks/src/index.ts | 1 + .../effects/hooks/src/use-hover-toggle.ts | 63 ++++++++ .../widgets/user-dropdown/user-dropdown.vue | 151 +++++++++++------- playground/src/layouts/basic.vue | 1 + pnpm-lock.yaml | 5 +- 6 files changed, 160 insertions(+), 62 deletions(-) create mode 100644 packages/effects/hooks/src/use-hover-toggle.ts diff --git a/packages/effects/hooks/package.json b/packages/effects/hooks/package.json index 85e54bc1..a531cf9c 100644 --- a/packages/effects/hooks/package.json +++ b/packages/effects/hooks/package.json @@ -25,6 +25,7 @@ "@vben/stores": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", + "@vueuse/core": "catalog:", "vue": "catalog:", "vue-router": "catalog:", "watermark-js-plus": "catalog:" diff --git a/packages/effects/hooks/src/index.ts b/packages/effects/hooks/src/index.ts index 51f64060..c189fd77 100644 --- a/packages/effects/hooks/src/index.ts +++ b/packages/effects/hooks/src/index.ts @@ -1,6 +1,7 @@ export * from './use-app-config'; export * from './use-content-maximize'; export * from './use-design-tokens'; +export * from './use-hover-toggle'; export * from './use-pagination'; export * from './use-refresh'; export * from './use-tabs'; diff --git a/packages/effects/hooks/src/use-hover-toggle.ts b/packages/effects/hooks/src/use-hover-toggle.ts new file mode 100644 index 00000000..becb1bee --- /dev/null +++ b/packages/effects/hooks/src/use-hover-toggle.ts @@ -0,0 +1,63 @@ +import type { Arrayable, MaybeElementRef } from '@vueuse/core'; + +import { computed, onUnmounted, ref, watch } from 'vue'; +import type { Ref } from 'vue'; + +import { isFunction } from '@vben/utils'; + +import { useMouseInElement } from '@vueuse/core'; + +/** + * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false + * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true + * @param delay 延迟更新状态的时间 + * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 + */ +export function useHoverToggle( + refElement: Arrayable, + delay: (() => number) | number = 500, +) { + const isOutsides: Array> = []; + const value = ref(false); + const timer = ref | undefined>(); + const refs = Array.isArray(refElement) ? refElement : [refElement]; + refs.forEach((refEle) => { + const listener = useMouseInElement(refEle, { handleOutside: true }); + isOutsides.push(listener.isOutside); + }); + const isOutsideAll = computed(() => isOutsides.every((v) => v.value)); + + function setValueDelay(val: boolean) { + timer.value && clearTimeout(timer.value); + timer.value = setTimeout( + () => { + value.value = val; + timer.value = undefined; + }, + isFunction(delay) ? delay() : delay, + ); + } + + const watcher = watch( + isOutsideAll, + (val) => { + setValueDelay(!val); + }, + { immediate: true }, + ); + + const controller = { + enable() { + watcher.resume(); + }, + disable() { + watcher.pause(); + }, + }; + + onUnmounted(() => { + timer.value && clearTimeout(timer.value); + }); + + return [value, controller] as [typeof value, typeof controller]; +} diff --git a/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue b/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue index 475ba6f6..c05ab57c 100644 --- a/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue +++ b/packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue @@ -2,8 +2,9 @@ import type { AnyFunction } from '@vben/types'; import type { Component } from 'vue'; -import { computed, ref } from 'vue'; +import { computed, useTemplateRef, watch } from 'vue'; +import { useHoverToggle } from '@vben/hooks'; import { LockKeyhole, LogOut } from '@vben/icons'; import { $t } from '@vben/locales'; import { preferences, usePreferences } from '@vben/preferences'; @@ -53,6 +54,10 @@ interface Props { * 文本 */ text?: string; + /** 触发方式 */ + trigger?: 'both' | 'click' | 'hover'; + /** hover触发时,延迟响应的时间 */ + hoverDelay?: number; } defineOptions({ @@ -67,10 +72,11 @@ const props = withDefaults(defineProps(), { showShortcutKey: true, tagText: '', text: '', + trigger: 'click', + hoverDelay: 500, }); const emit = defineEmits<{ logout: [] }>(); -const openPopover = ref(false); const { globalLockScreenShortcutKey, globalLogoutShortcutKey } = usePreferences(); @@ -84,6 +90,27 @@ const [LogoutModal, logoutModalApi] = useVbenModal({ }, }); +const refTrigger = useTemplateRef('refTrigger'); +const refContent = useTemplateRef('refContent'); +const [openPopover, hoverWatcher] = useHoverToggle( + [refTrigger, refContent], + () => props.hoverDelay, +); + +watch( + () => props.trigger === 'hover' || props.trigger === 'both', + (val) => { + if (val) { + hoverWatcher.enable(); + } else { + hoverWatcher.disable(); + } + }, + { + immediate: true, + }, +); + const altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥')); const enableLogoutShortcutKey = computed(() => { @@ -155,8 +182,8 @@ if (enableShortcutKey.value) { {{ $t('ui.widgets.logoutTip') }} - - + +
@@ -164,64 +191,66 @@ if (enableShortcutKey.value) {
- - -
-
- {{ text }} - - - {{ tagText }} - - +
+ + +
+
+ {{ text }} + + + {{ tagText }} + + +
+
+ {{ description }} +
-
- {{ description }} -
-
- - - - - {{ menu.text }} - - - - - {{ $t('ui.widgets.lockScreen.title') }} - - {{ altView }} L - - - - - - {{ $t('common.logout') }} - - {{ altView }} Q - - + + + + + {{ menu.text }} + + + + + {{ $t('ui.widgets.lockScreen.title') }} + + {{ altView }} L + + + + + + {{ $t('common.logout') }} + + {{ altView }} Q + + +
diff --git a/playground/src/layouts/basic.vue b/playground/src/layouts/basic.vue index f75b3ddc..3fa423af 100644 --- a/playground/src/layouts/basic.vue +++ b/playground/src/layouts/basic.vue @@ -132,6 +132,7 @@ watch( :text="userStore.userInfo?.realName" description="ann.vben@gmail.com" tag-text="Pro" + trigger="both" @logout="handleLogout" /> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe343c09..ce181fe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1548,6 +1548,9 @@ importers: '@vben/utils': specifier: workspace:* version: link:../../utils + '@vueuse/core': + specifier: 'catalog:' + version: 12.0.0(typescript@5.7.2) vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.7.2) @@ -10732,7 +10735,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.0 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: