diff --git a/web/apps/web-ele/src/router/guard.ts b/web/apps/web-ele/src/router/guard.ts index 63861b9..285009e 100644 --- a/web/apps/web-ele/src/router/guard.ts +++ b/web/apps/web-ele/src/router/guard.ts @@ -13,6 +13,7 @@ import { normalizeHomePath } from '#/utils/legacy-home'; import { generateAccess } from './access'; import { isLightTabRoute } from './light-menu'; +import { prefetchAiPlatformPages } from './prefetch'; function filterLightTabHistory(history: T) { const tabs = Array.isArray(history.tabs) ? history.tabs : []; @@ -197,6 +198,7 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); + prefetchAiPlatformPages(); // 子应用根路径重定向(/app/hr -> /app/hr/xxx) if (appContextStore.isSubApp) { diff --git a/web/apps/web-ele/src/router/prefetch.ts b/web/apps/web-ele/src/router/prefetch.ts new file mode 100644 index 0000000..d32c330 --- /dev/null +++ b/web/apps/web-ele/src/router/prefetch.ts @@ -0,0 +1,47 @@ +type IdleCallbackHandle = number; + +type IdleCallbackOptions = { + timeout?: number; +}; + +type WindowWithIdleCallback = Window & { + requestIdleCallback?: ( + callback: () => void, + options?: IdleCallbackOptions, + ) => IdleCallbackHandle; +}; + +let prefetched = false; + +const prefetchTasks = [ + () => import('#/views/ai-platform/agent/index.vue'), + () => import('#/views/_core/agent-chat/index.vue'), + () => import('#/views/ai-platform/workflow/index.vue'), + () => import('#/views/ai-platform/workflow-runs/index.vue'), + () => import('#/views/ai-platform/model/index.vue'), + () => import('#/views/ai-platform/knowledge/index.vue'), +]; + +function scheduleIdle(callback: () => void) { + const browserWindow = window as WindowWithIdleCallback; + + if (browserWindow.requestIdleCallback) { + browserWindow.requestIdleCallback(callback, { timeout: 3000 }); + return; + } + + window.setTimeout(callback, 1200); +} + +function prefetchAiPlatformPages() { + if (prefetched || typeof window === 'undefined') { + return; + } + + prefetched = true; + scheduleIdle(() => { + void Promise.allSettled(prefetchTasks.map((task) => task())); + }); +} + +export { prefetchAiPlatformPages }; diff --git a/web/packages/effects/layouts/src/basic/content/use-content-spinner.ts b/web/packages/effects/layouts/src/basic/content/use-content-spinner.ts index dfe3c53..2eb69e4 100644 --- a/web/packages/effects/layouts/src/basic/content/use-content-spinner.ts +++ b/web/packages/effects/layouts/src/basic/content/use-content-spinner.ts @@ -5,41 +5,73 @@ import { preferences } from '@vben/preferences'; function useContentSpinner() { const spinning = ref(false); - const startTime = ref(0); + const visibleStartTime = ref(0); const router = useRouter(); - const minShowTime = 500; // 最小显示时间 + const showDelay = 220; + const minVisibleTime = 160; const enableLoading = computed(() => preferences.transition.loading); + let pending = false; + let showTimer: ReturnType | undefined; + let hideTimer: ReturnType | undefined; - // 结束加载动画 - const onEnd = () => { + const clearTimers = () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + showTimer = undefined; + hideTimer = undefined; + }; + + const onStart = () => { if (!enableLoading.value) { return; } - const processTime = performance.now() - startTime.value; - if (processTime < minShowTime) { - setTimeout(() => { + + pending = true; + clearTimers(); + showTimer = setTimeout(() => { + if (!pending) { + return; + } + + visibleStartTime.value = performance.now(); + spinning.value = true; + }, showDelay); + }; + + const onEnd = () => { + pending = false; + clearTimeout(showTimer); + showTimer = undefined; + + if (!enableLoading.value || !spinning.value) { + return; + } + + const visibleTime = performance.now() - visibleStartTime.value; + if (visibleTime < minVisibleTime) { + hideTimer = setTimeout(() => { spinning.value = false; - }, minShowTime - processTime); + hideTimer = undefined; + }, minVisibleTime - visibleTime); } else { spinning.value = false; } }; - // 路由前置守卫 router.beforeEach((to) => { if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) { return true; } - startTime.value = performance.now(); - spinning.value = true; + + onStart(); return true; }); - // 路由后置守卫 router.afterEach((to) => { if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) { return true; } + onEnd(); return true; });