import React, { useEffect, useMemo, useState, useCallback } from 'react';
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';
import { IconEye, IconEyeOff } from '@/components/ui/icons';
import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores';
import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection';
import { INLINE_LOGO_JPEG } from '@/assets/logoInline';
import type { ApiError } from '@/types';
import styles from './LoginPage.module.scss';
/**
* 将 API 错误转换为本地化的用户友好消息
*/
function getLocalizedErrorMessage(error: any, t: (key: string) => string): string {
const apiError = error as ApiError;
const status = apiError?.status;
const code = apiError?.code;
const message = apiError?.message || '';
// 根据 HTTP 状态码判断
if (status === 401) {
return t('login.error_unauthorized');
}
if (status === 403) {
return t('login.error_forbidden');
}
if (status === 404) {
return t('login.error_not_found');
}
if (status && status >= 500) {
return t('login.error_server');
}
// 根据 axios 错误码判断
if (code === 'ECONNABORTED' || message.toLowerCase().includes('timeout')) {
return t('login.error_timeout');
}
if (code === 'ERR_NETWORK' || message.toLowerCase().includes('network error')) {
return t('login.error_network');
}
if (code === 'ERR_CERT_AUTHORITY_INVALID' || message.toLowerCase().includes('certificate')) {
return t('login.error_ssl');
}
// 检查 CORS 错误
if (message.toLowerCase().includes('cors') || message.toLowerCase().includes('cross-origin')) {
return t('login.error_cors');
}
// 默认错误消息
return t('login.error_invalid');
}
export function LoginPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const { showNotification } = useNotificationStore();
const language = useLanguageStore((state) => state.language);
const toggleLanguage = useLanguageStore((state) => state.toggleLanguage);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const login = useAuthStore((state) => state.login);
const restoreSession = useAuthStore((state) => state.restoreSession);
const storedBase = useAuthStore((state) => state.apiBase);
const storedKey = useAuthStore((state) => state.managementKey);
const storedRememberPassword = useAuthStore((state) => state.rememberPassword);
const [apiBase, setApiBase] = useState('');
const [managementKey, setManagementKey] = useState('');
const [showCustomBase, setShowCustomBase] = useState(false);
const [showKey, setShowKey] = useState(false);
const [rememberPassword, setRememberPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [autoLoading, setAutoLoading] = useState(true);
const [autoLoginSuccess, setAutoLoginSuccess] = useState(false);
const [error, setError] = useState('');
const detectedBase = useMemo(() => detectApiBaseFromLocation(), []);
const nextLanguageLabel = language === 'zh-CN' ? t('language.english') : t('language.chinese');
useEffect(() => {
const init = async () => {
try {
const autoLoggedIn = await restoreSession();
if (autoLoggedIn) {
setAutoLoginSuccess(true);
// 延迟跳转,让用户看到成功动画
setTimeout(() => {
const redirect = (location.state as any)?.from?.pathname || '/';
navigate(redirect, { replace: true });
}, 1300);
} else {
setApiBase(storedBase || detectedBase);
setManagementKey(storedKey || '');
setRememberPassword(storedRememberPassword || Boolean(storedKey));
}
} finally {
if (!autoLoginSuccess) {
setAutoLoading(false);
}
}
};
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleSubmit = useCallback(async () => {
if (!managementKey.trim()) {
setError(t('login.error_required'));
return;
}
const baseToUse = apiBase ? normalizeApiBase(apiBase) : detectedBase;
setLoading(true);
setError('');
try {
await login({
apiBase: baseToUse,
managementKey: managementKey.trim(),
rememberPassword
});
showNotification(t('common.connected_status'), 'success');
navigate('/', { replace: true });
} catch (err: any) {
const message = getLocalizedErrorMessage(err, t);
setError(message);
showNotification(`${t('notification.login_failed')}: ${message}`, 'error');
} finally {
setLoading(false);
}
}, [apiBase, detectedBase, login, managementKey, navigate, rememberPassword, showNotification, t]);
const handleSubmitKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Enter' && !loading) {
event.preventDefault();
handleSubmit();
}
},
[loading, handleSubmit]
);
if (isAuthenticated && !autoLoading && !autoLoginSuccess) {
const redirect = (location.state as any)?.from?.pathname || '/';
return
Management Center