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 axios from "axios"; import { isWebViewFunc } from "@/utils/panel"; import { siteConfig } from "@/config/site"; 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); const [isWebView, setIsWebView] = useState(false); // 清理验证码实例 useEffect(() => { return () => { if (tacInstanceRef.current) { tacInstanceRef.current.destroyWindow(); tacInstanceRef.current = null; } }; }, []); // 检测是否在WebView中运行 useEffect(() => { setIsWebView(isWebViewFunc()); }, []); // 验证表单 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; } // 使用axios的baseURL,确保在WebView中使用正确的面板地址 const baseURL = axios.defaults.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("检查验证码状态失败,请重试" + checkResponse.msg); setLoading(false); return; } // 根据返回值决定是否显示验证码 if (checkResponse.data === 0) { // 不需要验证码,直接登录 await performLogin(); } else { // 需要验证码,显示验证码弹层 setShowCaptcha(true); // 延时初始化验证码,确保DOM已渲染 setTimeout(() => { initCaptcha(); }, 100); } } catch (error) { console.error("检查验证码状态错误:", error); toast.error("网络错误,请稍后重试" + error); setLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !loading) { handleLogin(); } }; return (

登陆

请输入您的账号信息

handleInputChange("username", e.target.value) } onKeyDown={handleKeyPress} /> handleInputChange("password", e.target.value) } onKeyDown={handleKeyPress} />
{/* 版权信息 - 固定在底部,不占据布局空间 */}

Powered by{" "} flux-panel

v{isWebView ? siteConfig.app_version : siteConfig.version}

{/* 验证码弹层 */} {showCaptcha && (
{/* 背景遮罩层 - 模糊效果,暗黑模式下更深 */}
{/* 验证码容器 */}
)}
); }