From 54410aaac541d806f7a01368b41b0e8736bd7b90 Mon Sep 17 00:00:00 2001 From: JiaJun <2394389886@qq.com> Date: Fri, 29 May 2026 17:43:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(app):=20=E6=B7=BB=E5=8A=A0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=90=AF=E5=8A=A8=E8=B5=84=E6=BA=90=E9=A2=84=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了 AppBootResourceGate 组件用于管理应用启动时的资源加载 - 集成了图片和字体资源的预加载机制 - 添加了启动时的加载进度指示器和动画效果 - 创建了 DataLoadingIndicator 组件用于显示数据加载状态 - 在多个组件中替换原有的加载提示为新的 DataLoadingIndicator - 更新了样式文件以支持新的加载动画和视觉效果 --- AGENTS.md | 2 +- CLAUDE.md | 2 +- src/components/app-boot-resource-gate.tsx | 159 +++++++++++++++++ src/components/ui/data-loading-indicator.tsx | 53 ++++++ .../desktop/desktop-game-history.tsx | 130 ++++++++++---- .../game/components/desktop/desktop-topup.tsx | 8 +- .../shared/entry-notice-gate-modal.tsx | 8 +- .../desktop/desktop-finance-records-tab.tsx | 17 +- .../modal/desktop/desktop-notice-modal.tsx | 13 +- .../desktop/desktop-wallet-records-tab.tsx | 17 +- src/main.tsx | 13 +- src/style/index.css | 163 ++++++++++++++++++ 12 files changed, 516 insertions(+), 69 deletions(-) create mode 100644 src/components/app-boot-resource-gate.tsx create mode 100644 src/components/ui/data-loading-indicator.tsx diff --git a/AGENTS.md b/AGENTS.md index 76270a7..9d24f43 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **36-character-flower** (2566 symbols, 4898 relationships, 220 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/CLAUDE.md b/CLAUDE.md index 76270a7..9d24f43 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **36-character-flower** (2566 symbols, 4898 relationships, 220 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/src/components/app-boot-resource-gate.tsx b/src/components/app-boot-resource-gate.tsx new file mode 100644 index 0000000..6d2fc88 --- /dev/null +++ b/src/components/app-boot-resource-gate.tsx @@ -0,0 +1,159 @@ +import { type PropsWithChildren, useEffect, useMemo, useState } from 'react' + +const bootImageModules = import.meta.glob( + '../assets/**/*.{jpg,jpeg,png,svg,webp}', + { + eager: true, + import: 'default', + }, +) as Record + +const BOOT_MIN_VISIBLE_MS = 900 +const particleLayout = Array.from({ length: 36 }, (_, index) => ({ + delayMs: (index % 9) * 180, + durationMs: 3200 + (index % 7) * 260, + left: `${(index * 17 + 9) % 100}%`, + size: 2 + (index % 4), + top: `${(index * 29 + 13) % 100}%`, +})) + +function preloadImage(url: string) { + return new Promise((resolve) => { + const image = new Image() + + image.onload = () => resolve() + image.onerror = () => resolve() + image.src = url + }) +} + +function waitForFonts() { + if (typeof document === 'undefined' || !('fonts' in document)) { + return Promise.resolve() + } + + return document.fonts.ready.then(() => undefined) +} + +function useBootResourceLoader() { + const imageUrls = useMemo( + () => Array.from(new Set(Object.values(bootImageModules))).filter(Boolean), + [], + ) + const [progress, setProgress] = useState(0) + const [isReady, setIsReady] = useState(false) + + useEffect(() => { + let cancelled = false + const startedAt = performance.now() + const totalSteps = imageUrls.length + 1 + let completedSteps = 0 + + const syncProgress = () => { + completedSteps += 1 + if (!cancelled) { + setProgress( + Math.min(100, Math.round((completedSteps / totalSteps) * 100)), + ) + } + } + + const waitForMinimumDuration = async () => { + const elapsedMs = performance.now() - startedAt + const remainingMs = Math.max(0, BOOT_MIN_VISIBLE_MS - elapsedMs) + + if (remainingMs <= 0) { + return + } + + await new Promise((resolve) => window.setTimeout(resolve, remainingMs)) + } + + void Promise.all([ + waitForFonts().finally(syncProgress), + ...imageUrls.map((url) => preloadImage(url).finally(syncProgress)), + ]) + .then(waitForMinimumDuration) + .then(() => { + if (!cancelled) { + setProgress(100) + setIsReady(true) + } + }) + + return () => { + cancelled = true + } + }, [imageUrls]) + + return { isReady, progress } +} + +function AppLoadingOverlay({ progress }: { progress: number }) { + return ( +
+
+
+
+ +
+ {particleLayout.map((particle) => ( + + ))} +
+ +
+
+
+
+
+ + {progress}% + +
+ +
+
+
+ +
+
+ 资源加载中 +
+
+ 正在同步字花图鉴与游戏界面 +
+
+
+
+ ) +} + +export function AppBootResourceGate({ children }: PropsWithChildren) { + const { isReady, progress } = useBootResourceLoader() + + if (!isReady) { + return + } + + return children +} diff --git a/src/components/ui/data-loading-indicator.tsx b/src/components/ui/data-loading-indicator.tsx new file mode 100644 index 0000000..34db1cb --- /dev/null +++ b/src/components/ui/data-loading-indicator.tsx @@ -0,0 +1,53 @@ +import type { ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface DataLoadingIndicatorProps { + className?: string + compact?: boolean + label?: ReactNode +} + +export function DataLoadingIndicator({ + className, + compact = false, + label, +}: DataLoadingIndicatorProps) { + return ( +
+
+ + + +
+ + {label ? ( +
+ {label} +
+ ) : null} +
+ ) +} diff --git a/src/features/game/components/desktop/desktop-game-history.tsx b/src/features/game/components/desktop/desktop-game-history.tsx index e43acbf..8ae57e1 100644 --- a/src/features/game/components/desktop/desktop-game-history.tsx +++ b/src/features/game/components/desktop/desktop-game-history.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import historyBg from '@/assets/system/history-bg.png' import { SmartBackground } from '@/components/smart-background.tsx' import { SmartImage } from '@/components/smart-image' +import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator' import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts' import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared' @@ -17,12 +18,18 @@ function HistoryRewardNumber({ const label = String(number).padStart(2, '0') if (!image?.rewardUrl) { - return {label} + return ( + + {label} + + ) } return (
{t('gameDesktop.history.title')} @@ -87,13 +94,10 @@ export function DesktopGameHistory() { } > {isInitialLoading ? ( -
- {loadingText} -
+ ) : isEmpty ? (
-