feat: 更新登陆和注册UI

This commit is contained in:
zdl
2025-10-14 16:24:36 +08:00
parent e0ca328e1c
commit 29816de72b
5 changed files with 1414 additions and 93 deletions

View File

@@ -1,5 +1,5 @@
// src/views/Authentication/SignIn/SignInIllustration.js - Session版本
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
Box,
Button,
@@ -21,7 +21,7 @@ import {
FormErrorMessage
} from "@chakra-ui/react";
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons";
import { FaMobile, FaWeixin, FaLock, FaQrcode } from "react-icons/fa";
import { FaMobile, FaLock } from "react-icons/fa";
import { useNavigate, useLocation } from "react-router-dom";
import { useAuth } from "../../../contexts/AuthContext";
import PrivacyPolicyModal from "../../../components/PrivacyPolicyModal";
@@ -30,6 +30,7 @@ import AuthBackground from "../../../components/Auth/AuthBackground";
import AuthHeader from "../../../components/Auth/AuthHeader";
import AuthFooter from "../../../components/Auth/AuthFooter";
import VerificationCodeInput from "../../../components/Auth/VerificationCodeInput";
import WechatRegister from "../../../components/Auth/WechatRegister";
// API配置
const isProduction = process.env.NODE_ENV === 'production';
@@ -41,6 +42,9 @@ export default function SignInIllustration() {
const toast = useToast();
const { login, checkSession } = useAuth();
// 追踪组件挂载状态,防止内存泄漏
const isMountedRef = useRef(true);
// 页面状态
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState({});
@@ -126,14 +130,22 @@ export default function SignInIllustration() {
const [countdown, setCountdown] = useState(0);
useEffect(() => {
let timer;
let isMounted = true;
if (countdown > 0) {
timer = setInterval(() => {
setCountdown(prev => prev - 1);
if (isMounted) {
setCountdown(prev => prev - 1);
}
}, 1000);
} else if (countdown === 0) {
} else if (countdown === 0 && isMounted) {
setVerificationCodeSent(false);
}
return () => clearInterval(timer);
return () => {
isMounted = false;
if (timer) clearInterval(timer);
};
}, [countdown]);
// 发送验证码
@@ -174,8 +186,21 @@ export default function SignInIllustration() {
}),
});
// ✅ 安全检查:验证 response 存在
if (!response) {
throw new Error('网络请求失败,请检查网络连接');
}
const data = await response.json();
// 组件卸载后不再执行后续操作
if (!isMountedRef.current) return;
// ✅ 安全检查:验证 data 存在
if (!data) {
throw new Error('服务器响应为空');
}
if (response.ok && data.success) {
toast({
title: "验证码已发送",
@@ -189,47 +214,22 @@ export default function SignInIllustration() {
throw new Error(data.error || '发送验证码失败');
}
} catch (error) {
toast({
title: "发送验证码失败",
description: error.message || "请稍后重试",
status: "error",
duration: 3000,
});
} finally {
setSendingCode(false);
}
};
// 点击扫码,打开微信登录窗口
const openWechatLogin = async () => {
try {
setIsLoading(true);
console.log("请求微信登录1...");
// 获取微信二维码地址
const response = await fetch(`${API_BASE_URL}/api/auth/wechat/qrcode`);
if (!response.ok) {
throw new Error('获取二维码失败');
if (isMountedRef.current) {
toast({
title: "发送验证码失败",
description: error.message || "请稍后重试",
status: "error",
duration: 3000,
});
}
const data = await response.json();
// 方案1直接跳转推荐
window.location.href = data.auth_url;
} catch (error) {
console.error('获取微信授权失败:', error);
toast({
title: "获取微信授权失败",
description: error.message || "请稍后重试",
status: "error",
duration: 3000,
});
} finally {
setIsLoading(false);
if (isMountedRef.current) {
setSendingCode(false);
}
}
};
// 验证码登录函数
const loginWithVerificationCode = async (credential, verificationCode, authLoginType) => {
try {
@@ -246,29 +246,48 @@ export default function SignInIllustration() {
}),
});
// ✅ 安全检查:验证 response 存在
if (!response) {
throw new Error('网络请求失败,请检查网络连接');
}
const data = await response.json();
// 组件卸载后不再执行后续操作
if (!isMountedRef.current) {
return { success: false, error: '操作已取消' };
}
// ✅ 安全检查:验证 data 存在
if (!data) {
throw new Error('服务器响应为空');
}
if (response.ok && data.success) {
// 更新认证状态
await checkSession();
toast({
title: "登录成功",
description: "欢迎回来!",
status: "success",
duration: 3000,
});
if (isMountedRef.current) {
toast({
title: "登录成功",
description: "欢迎回来!",
status: "success",
duration: 3000,
});
}
return { success: true };
} else {
throw new Error(data.error || '验证码登录失败');
}
} catch (error) {
toast({
title: "登录失败",
description: error.message || "请检查验证码是否正确",
status: "error",
duration: 3000,
});
if (isMountedRef.current) {
toast({
title: "登录失败",
description: error.message || "请检查验证码是否正确",
status: "error",
duration: 3000,
});
}
return { success: false, error: error.message };
}
};
@@ -356,6 +375,15 @@ export default function SignInIllustration() {
}
};
// 组件卸载时清理
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return (
<Flex minH="100vh" position="relative" overflow="hidden">
{/* 背景 */}
@@ -463,26 +491,7 @@ export default function SignInIllustration() {
{/* 右侧:微信登陆 - 20% 宽度 */}
<Box flex="1">
<Center width="100%" bg="gray.50" borderRadius="lg" p={8}>
<VStack spacing={6}>
<VStack spacing={2}>
<Text fontSize="lg" fontWeight="bold" color={"gray.700"}>
微信扫一扫
</Text>
</VStack>
<Icon as={FaQrcode} w={20} h={20} color={"green.500"} />
{/* isLoading={isLoading || !wechatAuthUrl} */}
<Button
colorScheme="green"
size="lg"
leftIcon={<Icon as={FaWeixin} />}
onClick={openWechatLogin}
_hover={{ transform: "translateY(-2px)", boxShadow: "lg" }}
_active={{ transform: "translateY(0)" }}
>
扫码登录
</Button>
</VStack>
<WechatRegister />
</Center>
</Box>
</HStack>

View File

@@ -1,5 +1,5 @@
// src\views\Authentication\SignUp/SignUpIllustration.js
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
Box,
Button,
@@ -51,6 +51,9 @@ export default function SignUpPage() {
const navigate = useNavigate();
const toast = useToast();
// 追踪组件挂载状态,防止内存泄漏
const isMountedRef = useRef(true);
// 隐私政策弹窗状态
const { isOpen: isPrivacyModalOpen, onOpen: onPrivacyModalOpen, onClose: onPrivacyModalClose } = useDisclosure();
@@ -95,10 +98,20 @@ export default function SignUpPage() {
try {
setIsLoading(true);
await axios.post(`${API_BASE_URL}/api/auth/${endpoint}`, {
const response = await axios.post(`${API_BASE_URL}/api/auth/${endpoint}`, {
[fieldName]: contact
}, {
timeout: 10000 // 添加10秒超时
});
// 组件卸载后不再执行后续操作
if (!isMountedRef.current) return;
// ✅ 安全检查:验证 response 和 data 存在
if (!response || !response.data) {
throw new Error('服务器响应为空');
}
toast({
title: "验证码已发送",
description: "请查收短信",
@@ -108,22 +121,36 @@ export default function SignUpPage() {
setCountdown(60);
} catch (error) {
toast({
title: "发送失败",
description: error.response?.data?.error || "请稍后重试",
status: "error",
duration: 3000,
});
if (isMountedRef.current) {
toast({
title: "发送失败",
description: error.response?.data?.error || error.message || "请稍后重试",
status: "error",
duration: 3000,
});
}
} finally {
setIsLoading(false);
if (isMountedRef.current) {
setIsLoading(false);
}
}
};
// 倒计时效果
useEffect(() => {
let isMounted = true;
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
const timer = setTimeout(() => {
if (isMounted) {
setCountdown(countdown - 1);
}
}, 1000);
return () => {
isMounted = false;
clearTimeout(timer);
};
}
}, [countdown]);
@@ -188,7 +215,17 @@ export default function SignUpPage() {
};
}
await axios.post(`${API_BASE_URL}${endpoint}`, data);
const response = await axios.post(`${API_BASE_URL}${endpoint}`, data, {
timeout: 10000 // 添加10秒超时
});
// 组件卸载后不再执行后续操作
if (!isMountedRef.current) return;
// ✅ 安全检查:验证 response 和 data 存在
if (!response || !response.data) {
throw new Error('注册请求失败:服务器响应为空');
}
toast({
title: "注册成功",
@@ -198,17 +235,23 @@ export default function SignUpPage() {
});
setTimeout(() => {
navigate("/auth/sign-in");
if (isMountedRef.current) {
navigate("/auth/sign-in");
}
}, 2000);
} catch (error) {
toast({
title: "注册失败",
description: error.response?.data?.error || "请稍后重试",
status: "error",
duration: 3000,
});
if (isMountedRef.current) {
toast({
title: "注册失败",
description: error.response?.data?.error || error.message || "请稍后重试",
status: "error",
duration: 3000,
});
}
} finally {
setIsLoading(false);
if (isMountedRef.current) {
setIsLoading(false);
}
}
};
@@ -220,6 +263,15 @@ export default function SignUpPage() {
}
};
// 组件卸载时清理
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
// 公用的用户名和密码输入框组件
const commonAuthFields = (
<VStack spacing={4} width="100%">