fix(splash): prevent login flicker on startup

This commit is contained in:
Supra4E8C
2025-12-21 20:22:22 +08:00
parent dea106cf47
commit f8ed787f92
3 changed files with 35 additions and 23 deletions

View File

@@ -17,18 +17,24 @@ import { MainLayout } from '@/components/layout/MainLayout';
import { ProtectedRoute } from '@/router/ProtectedRoute'; import { ProtectedRoute } from '@/router/ProtectedRoute';
import { useAuthStore, useLanguageStore, useThemeStore } from '@/stores'; import { useAuthStore, useLanguageStore, useThemeStore } from '@/stores';
const SPLASH_DURATION = 1500;
const SPLASH_FADE_DURATION = 400;
function App() { function App() {
const initializeTheme = useThemeStore((state) => state.initializeTheme); const initializeTheme = useThemeStore((state) => state.initializeTheme);
const language = useLanguageStore((state) => state.language); const language = useLanguageStore((state) => state.language);
const setLanguage = useLanguageStore((state) => state.setLanguage); const setLanguage = useLanguageStore((state) => state.setLanguage);
const restoreSession = useAuthStore((state) => state.restoreSession); const restoreSession = useAuthStore((state) => state.restoreSession);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const [splashReadyToFade, setSplashReadyToFade] = useState(false);
const [showSplash, setShowSplash] = useState(true); const [showSplash, setShowSplash] = useState(true);
const [authReady, setAuthReady] = useState(false);
useEffect(() => { useEffect(() => {
initializeTheme(); initializeTheme();
restoreSession(); void restoreSession().finally(() => {
setAuthReady(true);
});
}, [initializeTheme, restoreSession]); }, [initializeTheme, restoreSession]);
useEffect(() => { useEffect(() => {
@@ -36,13 +42,25 @@ function App() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // 仅用于首屏同步 i18n 语言 }, []); // 仅用于首屏同步 i18n 语言
useEffect(() => {
const timer = setTimeout(() => {
setSplashReadyToFade(true);
}, SPLASH_DURATION - SPLASH_FADE_DURATION);
return () => clearTimeout(timer);
}, []);
const handleSplashFinish = useCallback(() => { const handleSplashFinish = useCallback(() => {
setShowSplash(false); setShowSplash(false);
}, []); }, []);
// 仅在已认证时显示闪屏 if (showSplash) {
if (showSplash && isAuthenticated) { return (
return <SplashScreen onFinish={handleSplashFinish} duration={1500} />; <SplashScreen
fadeOut={splashReadyToFade && authReady}
onFinish={handleSplashFinish}
/>
);
} }
return ( return (

View File

@@ -1,29 +1,25 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline';
import './SplashScreen.scss'; import './SplashScreen.scss';
interface SplashScreenProps { interface SplashScreenProps {
onFinish: () => void; onFinish: () => void;
duration?: number; fadeOut?: boolean;
} }
export function SplashScreen({ onFinish, duration = 1500 }: SplashScreenProps) { const FADE_OUT_DURATION = 400;
const [fadeOut, setFadeOut] = useState(false);
export function SplashScreen({ onFinish, fadeOut = false }: SplashScreenProps) {
useEffect(() => { useEffect(() => {
const fadeTimer = setTimeout(() => { if (!fadeOut) return;
setFadeOut(true);
}, duration - 400);
const finishTimer = setTimeout(() => { const finishTimer = setTimeout(() => {
onFinish(); onFinish();
}, duration); }, FADE_OUT_DURATION);
return () => { return () => {
clearTimeout(fadeTimer);
clearTimeout(finishTimer); clearTimeout(finishTimer);
}; };
}, [duration, onFinish]); }, [fadeOut, onFinish]);
return ( return (
<div className={`splash-screen ${fadeOut ? 'fade-out' : ''}`}> <div className={`splash-screen ${fadeOut ? 'fade-out' : ''}`}>

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react'; 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 { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
@@ -44,12 +44,10 @@ export function LoginPage() {
init(); init();
}, [detectedBase, restoreSession, storedBase, storedKey]); }, [detectedBase, restoreSession, storedBase, storedKey]);
useEffect(() => {
if (isAuthenticated) { if (isAuthenticated) {
const redirect = (location.state as any)?.from?.pathname || '/'; const redirect = (location.state as any)?.from?.pathname || '/';
navigate(redirect, { replace: true }); return <Navigate to={redirect} replace />;
} }
}, [isAuthenticated, navigate, location.state]);
const handleUseCurrent = () => { const handleUseCurrent = () => {
setApiBase(detectedBase); setApiBase(detectedBase);