feat: 更新登陆和注册UI
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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%">
|
||||
|
||||
Reference in New Issue
Block a user