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 { 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 <SplashScreen onFinish={handleSplashFinish} duration={1500} />;
if (showSplash) {
return (
<SplashScreen
fadeOut={splashReadyToFade && authReady}
onFinish={handleSplashFinish}
/>
);
}
return (

View File

@@ -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 (
<div className={`splash-screen ${fadeOut ? 'fade-out' : ''}`}>

View File

@@ -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 <Navigate to={redirect} replace />;
}
const handleUseCurrent = () => {
setApiBase(detectedBase);