import { Button } from "@heroui/button"; import { Input } from "@heroui/input"; import { Card, CardBody, CardHeader } from "@heroui/card"; import { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import toast from 'react-hot-toast'; import { title } from "@/components/primitives"; import DefaultLayout from "@/layouts/default"; import { login, LoginData, checkCaptcha } from "@/api"; import "@/utils/tac.css"; import "@/utils/tac.min.js"; import bgImage from "@/images/bg.jpg"; interface LoginForm { username: string; password: string; captchaId: string; } interface CaptchaConfig { requestCaptchaDataUrl: string; validCaptchaUrl: string; bindEl: string; validSuccess: (res: any, captcha: any, tac: any) => void; validFail?: (res: any, captcha: any, tac: any) => void; btnCloseFun?: (event: any, tac: any) => void; btnRefreshFun?: (event: any, tac: any) => void; } interface CaptchaStyle { btnUrl?: string; bgUrl?: string; logoUrl?: string | null; moveTrackMaskBgColor?: string; moveTrackMaskBorderColor?: string; } export default function IndexPage() { const [form, setForm] = useState({ username: "", password: "", captchaId: "", }); const [loading, setLoading] = useState(false); const [errors, setErrors] = useState>({}); const [showCaptcha, setShowCaptcha] = useState(false); const navigate = useNavigate(); const tacInstanceRef = useRef(null); const captchaContainerRef = useRef(null); // 清理验证码实例 useEffect(() => { return () => { if (tacInstanceRef.current) { tacInstanceRef.current.destroyWindow(); tacInstanceRef.current = null; } }; }, []); // 验证表单 const validateForm = (): boolean => { const newErrors: Partial = {}; if (!form.username.trim()) { newErrors.username = '请输入用户名'; } if (!form.password.trim()) { newErrors.password = '请输入密码'; } else if (form.password.length < 6) { newErrors.password = '密码长度至少6位'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // 处理输入变化 const handleInputChange = (field: keyof LoginForm, value: string) => { setForm(prev => ({ ...prev, [field]: value })); // 清除该字段的错误 if (errors[field]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }; // 初始化验证码 const initCaptcha = async () => { if (!window.TAC || !captchaContainerRef.current) { return; } try { // 清理之前的验证码实例 if (tacInstanceRef.current) { tacInstanceRef.current.destroyWindow(); tacInstanceRef.current = null; } // 获取API基础URL const baseURL = import.meta.env.VITE_API_BASE ? `${import.meta.env.VITE_API_BASE}/api/v1` : '/api/v1'; const config: CaptchaConfig = { requestCaptchaDataUrl: `${baseURL}/captcha/generate`, validCaptchaUrl: `${baseURL}/captcha/verify`, bindEl: "#captcha-container", validSuccess: (res: any, _: any, tac: any) => { form.captchaId = res.data.validToken setShowCaptcha(false); tac.destroyWindow(); performLogin(); }, validFail: (_: any, _captcha: any, tac: any) => { tac.reloadCaptcha(); }, btnCloseFun: (_event: any, tac: any) => { setShowCaptcha(false); tac.destroyWindow(); setLoading(false); }, btnRefreshFun: (_event: any, tac: any) => { tac.reloadCaptcha(); } }; // 检测暗黑模式 const isDarkMode = document.documentElement.classList.contains('dark') || document.documentElement.getAttribute('data-theme') === 'dark' || window.matchMedia('(prefers-color-scheme: dark)').matches; // 根据主题调整颜色 const trackColor = isDarkMode ? "#4a5568" : "#7db0be"; // 暗黑模式使用更深的灰蓝色 const style: CaptchaStyle = { bgUrl: bgImage, logoUrl: null, moveTrackMaskBgColor: trackColor, moveTrackMaskBorderColor: trackColor }; tacInstanceRef.current = new window.TAC(config, style); tacInstanceRef.current.init(); } catch (error) { console.error('初始化验证码失败:', error); toast.error('验证码初始化失败,请刷新页面重试'); setShowCaptcha(false); setLoading(false); } }; // 执行登录请求 const performLogin = async () => { try { const loginData: LoginData = { username: form.username.trim(), password: form.password, captchaId: form.captchaId, }; const response = await login(loginData); if (response.code !== 0) { toast.error(response.msg || "登录失败"); return; } // 检查是否需要强制修改密码 if (response.data.requirePasswordChange) { localStorage.setItem('token', response.data.token); localStorage.setItem("role_id", response.data.role_id.toString()); localStorage.setItem("name", response.data.name); localStorage.setItem("admin", (response.data.role_id === 0).toString()); toast.success('检测到默认密码,即将跳转到修改密码页面'); navigate("/change-password"); return; } // 保存登录信息 localStorage.setItem('token', response.data.token); localStorage.setItem("role_id", response.data.role_id.toString()); localStorage.setItem("name", response.data.name); localStorage.setItem("admin", (response.data.role_id === 0).toString()); // 登录成功 toast.success('登录成功'); navigate("/dashboard"); } catch (error) { console.error('登录错误:', error); toast.error("网络错误,请稍后重试"); } finally { setLoading(false); } }; const handleLogin = async () => { if (!validateForm()) return; setLoading(true); try { // 先检查是否需要验证码 const checkResponse = await checkCaptcha(); if (checkResponse.code !== 0) { toast.error("检查验证码状态失败,请重试"); setLoading(false); return; } // 根据返回值决定是否显示验证码 if (checkResponse.data === 0) { // 不需要验证码,直接登录 await performLogin(); } else { // 需要验证码,显示验证码弹层 setShowCaptcha(true); // 延时初始化验证码,确保DOM已渲染 setTimeout(() => { initCaptcha(); }, 100); } } catch (error) { console.error('检查验证码状态错误:', error); toast.error("网络错误,请稍后重试"); setLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !loading) { handleLogin(); } }; return (

登陆

请输入您的账号信息

handleInputChange('username', e.target.value)} onKeyDown={handleKeyPress} variant="bordered" isDisabled={loading} isInvalid={!!errors.username} errorMessage={errors.username} /> handleInputChange('password', e.target.value)} onKeyDown={handleKeyPress} variant="bordered" isDisabled={loading} isInvalid={!!errors.password} />
{/* 验证码弹层 */} {showCaptcha && (
{/* 背景遮罩层 - 模糊效果,暗黑模式下更深 */}
{/* 验证码容器 */}
)}
); }