import { memo, useCallback, useEffect, useState } from 'react' import { useNavigate } from 'react-router' import { Activity, Eye, EyeOff, Shield, ServerOff } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { authApi } from '@/api' import { toast } from 'sonner' /** * Login 页面 - 优化版本 * * 优化点: * 1. React.memo 包裹组件 * 2. 表单处理使用 useCallback 缓存 * 3. 错误处理使用 sonner toast 替代 alert * 4. 清理 useEffect 中的未使用变量 */ const Login = memo(function Login() { const [username, setUsername] = useState('admin') const [password, setPassword] = useState('admin123') const [showPassword, setShowPassword] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [backendStatus, setBackendStatus] = useState<'checking' | 'online' | 'offline'>('checking') const navigate = useNavigate() useEffect(() => { // Check if already logged in const token = localStorage.getItem('token') if (token) { navigate('/') return } // Quick backend check checkBackend() // eslint-disable-next-line react-hooks/exhaustive-deps }, [navigate]) // 后端状态检查 - useCallback缓存 const checkBackend = useCallback(async () => { try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 3000) // 用公开的健康检查端点探活;避免打 /auth/login(生产环境被 nginx 限流 5r/m, // 反复刷新登录页会触发限流并锁死真正的登录请求) const res = await fetch('/api/v1/health', { method: 'GET', signal: controller.signal, }) clearTimeout(timeoutId) setBackendStatus(res.ok ? 'online' : 'offline') } catch { setBackendStatus('offline') } }, []) // 表单提交处理 - useCallback缓存 const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault() setLoading(true) setError('') try { const result = await authApi.login(username, password) localStorage.setItem('token', result.accessToken) localStorage.setItem('refreshToken', result.refreshToken) localStorage.setItem('user', JSON.stringify(result.user)) toast.success('登录成功') navigate('/') } catch (err: unknown) { const message = err instanceof Error ? err.message : '登录失败' if (message.includes('Cannot connect')) { setError('无法连接后端服务,请确认后端已启动(端口 8080)。') setBackendStatus('offline') } else if ( message.includes('invalid') || message.includes('password') || message.includes('密码') || message.includes('Unauthorized') ) { setError('用户名或密码错误') } else { setError(message) } } finally { setLoading(false) } }, [username, password, navigate] ) // 密码显示切换 - useCallback缓存 const togglePassword = useCallback(() => { setShowPassword((prev) => !prev) }, []) return (
{/* Logo */}

LokiDeploy

Kubernetes Loki 日志部署平台

{/* Backend Status */} {backendStatus === 'offline' && (

后端服务未启动

无法连接 API 服务,请先启动后端:

./run.sh
)} {/* Login Card */}

登录

{error && (
{error}
)}
setUsername(e.target.value)} placeholder="请输入用户名" required className="h-10" autoComplete="username" />
setPassword(e.target.value)} placeholder="请输入密码" required className="h-10 pr-10" autoComplete="current-password" />

默认账号:admin / admin123

) }) export default Login