This commit is contained in:
xingyu4j
2025-06-28 12:04:15 +08:00
8 changed files with 183 additions and 65 deletions

View File

@@ -76,6 +76,12 @@ const keyword = ref('');
const keywordDebounce = refDebounced(keyword, 300);
const innerIcons = ref<string[]>([]);
/* 当检索关键词变化时,重置分页 */
watch(keywordDebounce, () => {
currentPage.value = 1;
setCurrentPage(1);
});
watchDebounced(
() => props.prefix,
async (prefix) => {

View File

@@ -3,6 +3,8 @@ import type { AuthenticationProps } from './types';
import { computed, watch } from 'vue';
import { $t } from '@vben/locales';
import { useVbenModal } from '@vben-core/popup-ui';
import { Slot, VbenAvatar } from '@vben-core/shadcn-ui';

View File

@@ -2,7 +2,7 @@ import type { Arrayable, MaybeElementRef } from '@vueuse/core';
import type { Ref } from 'vue';
import { computed, onUnmounted, ref, unref, watch } from 'vue';
import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue';
import { isFunction } from '@vben/utils';
@@ -20,12 +20,12 @@ const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0
/**
* 监测鼠标是否在元素内部,如果在元素内部则返回 true否则返回 false
* @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true
* @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true
* @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象
* @returns 返回一个数组,第一个元素是一个 ref表示鼠标是否在元素内部第二个元素是一个控制器可以通过 enable 和 disable 方法来控制监听器的启用和禁用
*/
export function useHoverToggle(
refElement: Arrayable<MaybeElementRef>,
refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>,
delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY,
) {
// 兼容旧版本API
@@ -38,20 +38,58 @@ export function useHoverToggle(
...delay,
};
const isHovers: Array<Ref<boolean>> = [];
const value = ref(false);
const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const refs = Array.isArray(refElement) ? refElement : [refElement];
refs.forEach((refEle) => {
const eleRef = computed(() => {
const ele = unref(refEle);
return ele instanceof Element ? ele : (ele?.$el as Element);
});
const isHover = useElementHover(eleRef);
isHovers.push(isHover);
const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]);
// 使用计算属性包装 refElement使其响应式变化
const refs = computed(() => {
const raw = unref(refElement);
if (raw === null) return [];
return Array.isArray(raw) ? raw : [raw];
});
const isOutsideAll = computed(() => isHovers.every((v) => !v.value));
// 存储所有 hover 状态
const isHovers = ref<Array<Ref<boolean>>>([]);
// 更新 hover 监听的函数
function updateHovers() {
// 停止并清理之前的作用域
hoverScopes.value.forEach((scope) => scope.stop());
hoverScopes.value = [];
isHovers.value = refs.value.map((refEle) => {
if (!refEle) {
return ref(false);
}
const eleRef = computed(() => {
const ele = unref(refEle);
return ele instanceof Element ? ele : (ele?.$el as Element);
});
// 为每个元素创建独立的作用域
const scope = effectScope();
const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false);
hoverScopes.value.push(scope);
return hoverRef;
});
}
// 监听元素数量变化,避免过度执行
const elementsCount = computed(() => {
const raw = unref(refElement);
if (raw === null) return 0;
return Array.isArray(raw) ? raw.length : 1;
});
// 初始设置
updateHovers();
// 只在元素数量变化时重新设置监听器
const stopWatcher = watch(elementsCount, updateHovers, { deep: false });
const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value));
function clearTimers() {
if (enterTimer.value) {
@@ -96,7 +134,7 @@ export function useHoverToggle(
}
}
const watcher = watch(
const hoverWatcher = watch(
isOutsideAll,
(val) => {
setValueDelay(!val);
@@ -106,15 +144,19 @@ export function useHoverToggle(
const controller = {
enable() {
watcher.resume();
hoverWatcher.resume();
},
disable() {
watcher.pause();
hoverWatcher.pause();
},
};
onUnmounted(() => {
clearTimers();
// 停止监听器
stopWatcher();
// 停止所有剩余的作用域
hoverScopes.value.forEach((scope) => scope.stop());
});
return [value, controller] as [typeof value, typeof controller];

View File

@@ -62,21 +62,23 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
</template>
</AuthenticationFormView>
<!-- 头部 Logo 和应用名称 -->
<div
v-if="logo || appName"
class="absolute left-0 top-0 z-10 flex flex-1"
@click="clickLogo"
>
<slot name="logo">
<!-- 头部 Logo 和应用名称 -->
<div
class="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
v-if="logo || appName"
class="absolute left-0 top-0 z-10 flex flex-1"
@click="clickLogo"
>
<img v-if="logo" :alt="appName" :src="logo" class="mr-2" width="42" />
<p v-if="appName" class="m-0 text-xl font-medium">
{{ appName }}
</p>
<div
class="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
>
<img v-if="logo" :alt="appName" :src="logo" class="mr-2" width="42" />
<p v-if="appName" class="m-0 text-xl font-medium">
{{ appName }}
</p>
</div>
</div>
</div>
</slot>
<!-- 系统介绍 -->
<div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block">