From f8ed787f920a9c847a85f18f04b9e1c9b77b2712 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 21 Dec 2025 20:22:22 +0800 Subject: [PATCH] fix(splash): prevent login flicker on startup --- src/App.tsx | 28 +++++++++++++++++++++----- src/components/common/SplashScreen.tsx | 18 +++++++---------- src/pages/LoginPage.tsx | 12 +++++------ 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 97f2e10..0d1f1df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,18 +17,24 @@ import { MainLayout } from '@/components/layout/MainLayout'; import { ProtectedRoute } from '@/router/ProtectedRoute'; import { useAuthStore, useLanguageStore, useThemeStore } from '@/stores'; +const SPLASH_DURATION = 1500; +const SPLASH_FADE_DURATION = 400; + function App() { const initializeTheme = useThemeStore((state) => state.initializeTheme); const language = useLanguageStore((state) => state.language); const setLanguage = useLanguageStore((state) => state.setLanguage); const restoreSession = useAuthStore((state) => state.restoreSession); - const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + const [splashReadyToFade, setSplashReadyToFade] = useState(false); const [showSplash, setShowSplash] = useState(true); + const [authReady, setAuthReady] = useState(false); useEffect(() => { initializeTheme(); - restoreSession(); + void restoreSession().finally(() => { + setAuthReady(true); + }); }, [initializeTheme, restoreSession]); useEffect(() => { @@ -36,13 +42,25 @@ function App() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 仅用于首屏同步 i18n 语言 + useEffect(() => { + const timer = setTimeout(() => { + setSplashReadyToFade(true); + }, SPLASH_DURATION - SPLASH_FADE_DURATION); + + return () => clearTimeout(timer); + }, []); + const handleSplashFinish = useCallback(() => { setShowSplash(false); }, []); - // 仅在已认证时显示闪屏 - if (showSplash && isAuthenticated) { - return ; + if (showSplash) { + return ( + + ); } return ( diff --git a/src/components/common/SplashScreen.tsx b/src/components/common/SplashScreen.tsx index 1233d8e..ffee1d3 100644 --- a/src/components/common/SplashScreen.tsx +++ b/src/components/common/SplashScreen.tsx @@ -1,29 +1,25 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; import './SplashScreen.scss'; interface SplashScreenProps { onFinish: () => void; - duration?: number; + fadeOut?: boolean; } -export function SplashScreen({ onFinish, duration = 1500 }: SplashScreenProps) { - const [fadeOut, setFadeOut] = useState(false); +const FADE_OUT_DURATION = 400; +export function SplashScreen({ onFinish, fadeOut = false }: SplashScreenProps) { useEffect(() => { - const fadeTimer = setTimeout(() => { - setFadeOut(true); - }, duration - 400); - + if (!fadeOut) return; const finishTimer = setTimeout(() => { onFinish(); - }, duration); + }, FADE_OUT_DURATION); return () => { - clearTimeout(fadeTimer); clearTimeout(finishTimer); }; - }, [duration, onFinish]); + }, [fadeOut, onFinish]); return (
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index b66e6a8..7b4bca4 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { Navigate, useNavigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; @@ -44,12 +44,10 @@ export function LoginPage() { init(); }, [detectedBase, restoreSession, storedBase, storedKey]); - useEffect(() => { - if (isAuthenticated) { - const redirect = (location.state as any)?.from?.pathname || '/'; - navigate(redirect, { replace: true }); - } - }, [isAuthenticated, navigate, location.state]); + if (isAuthenticated) { + const redirect = (location.state as any)?.from?.pathname || '/'; + return ; + } const handleUseCurrent = () => { setApiBase(detectedBase);