Initial commit
This commit is contained in:
891
src/views/Authentication/SignUp/SignUpIllustration.js
Normal file
891
src/views/Authentication/SignUp/SignUpIllustration.js
Normal file
@@ -0,0 +1,891 @@
|
||||
// src\views\Authentication\SignUp/SignUpIllustration.js
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
Input,
|
||||
Stack,
|
||||
Text,
|
||||
Heading,
|
||||
VStack,
|
||||
HStack,
|
||||
useToast,
|
||||
Icon,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
IconButton,
|
||||
Tab,
|
||||
TabList,
|
||||
Tabs,
|
||||
Link as ChakraLink,
|
||||
Image,
|
||||
Center,
|
||||
Spinner,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
Divider,
|
||||
useDisclosure,
|
||||
Checkbox
|
||||
} from "@chakra-ui/react";
|
||||
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons";
|
||||
import { FaWeixin, FaMobile, FaEnvelope, FaUser, FaLock } from "react-icons/fa";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
import { useAuth } from '../../../contexts/AuthContext'; // 假设AuthContext在这个路径
|
||||
import PrivacyPolicyModal from '../../../components/PrivacyPolicyModal';
|
||||
import UserAgreementModal from '../../../components/UserAgreementModal';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const API_BASE_URL = isProduction ? "" : process.env.REACT_APP_API_URL;
|
||||
|
||||
export default function SignUpPage() {
|
||||
const [registerType, setRegisterType] = useState(0); // 0: 微信, 1: 手机号
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [wechatAuthUrl, setWechatAuthUrl] = useState("");
|
||||
const [wechatSessionId, setWechatSessionId] = useState("");
|
||||
const [wechatStatus, setWechatStatus] = useState("waiting");
|
||||
const [errors, setErrors] = useState({});
|
||||
const [checkInterval, setCheckInterval] = useState(null);
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
username: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
verificationCode: ""
|
||||
});
|
||||
|
||||
// 隐私政策弹窗状态
|
||||
const {
|
||||
isOpen: isPrivacyModalOpen,
|
||||
onOpen: onPrivacyModalOpen,
|
||||
onClose: onPrivacyModalClose
|
||||
} = useDisclosure();
|
||||
|
||||
// 用户协议弹窗状态
|
||||
const {
|
||||
isOpen: isUserAgreementModalOpen,
|
||||
onOpen: onUserAgreementModalOpen,
|
||||
onClose: onUserAgreementModalClose
|
||||
} = useDisclosure();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
const { loginWithWechat } = useAuth(); // 使用认证上下文
|
||||
|
||||
// 监听微信授权窗口的消息
|
||||
useEffect(() => {
|
||||
const handleMessage = (event) => {
|
||||
console.log('收到消息:', event.data, 'from:', event.origin);
|
||||
|
||||
// 放宽来源验证,包含所有可能的域名
|
||||
const allowedOrigins = [
|
||||
window.location.origin,
|
||||
'https://valuefrontier.cn',
|
||||
'http://localhost:3000', // 开发环境
|
||||
'http://127.0.0.1:3000' // 本地开发
|
||||
];
|
||||
|
||||
if (!allowedOrigins.includes(event.origin)) {
|
||||
console.warn('消息来源不受信任:', event.origin);
|
||||
// 但仍然处理,因为可能是跨域问题
|
||||
}
|
||||
|
||||
if (event.data && event.data.type === 'wechat_auth_success') {
|
||||
console.log('收到微信授权成功消息');
|
||||
// 授权成功,立即检查状态
|
||||
if (wechatSessionId) {
|
||||
console.log('开始检查微信状态, sessionId:', wechatSessionId);
|
||||
checkWechatStatus();
|
||||
} else {
|
||||
console.error('wechatSessionId 为空');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, [wechatSessionId]);
|
||||
|
||||
// 获取微信授权URL
|
||||
const getWechatQRCode = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await axios.get(`${API_BASE_URL}/api/auth/wechat/qrcode`);
|
||||
setWechatAuthUrl(response.data.auth_url);
|
||||
setWechatSessionId(response.data.session_id);
|
||||
setWechatStatus("waiting");
|
||||
|
||||
// 开始轮询检查扫码状态
|
||||
startWechatStatusCheck(response.data.session_id);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "获取授权失败",
|
||||
description: error.response?.data?.error || "请稍后重试",
|
||||
status: "error",
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 立即检查微信状态(用于授权成功后)
|
||||
const checkWechatStatus = async () => {
|
||||
try {
|
||||
console.log('检查微信状态, sessionId:', wechatSessionId);
|
||||
|
||||
const response = await axios.post(`${API_BASE_URL}/api/auth/wechat/check`, {
|
||||
session_id: wechatSessionId
|
||||
});
|
||||
|
||||
const { status, user_info } = response.data;
|
||||
console.log('微信状态检查结果:', { status, user_info });
|
||||
setWechatStatus(status);
|
||||
|
||||
if (status === "login_success" || status === "register_success") {
|
||||
// 停止轮询
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
setCheckInterval(null);
|
||||
}
|
||||
|
||||
console.log('开始微信登录流程');
|
||||
|
||||
// 调用登录接口获取token
|
||||
try {
|
||||
let loginResult;
|
||||
|
||||
// 如果有 loginWithWechat 方法,使用它
|
||||
if (loginWithWechat) {
|
||||
console.log('使用 AuthContext 登录');
|
||||
loginResult = await loginWithWechat(wechatSessionId);
|
||||
} else {
|
||||
console.log('使用传统登录方式');
|
||||
// 否则使用原来的方式
|
||||
const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login/wechat`, {
|
||||
session_id: wechatSessionId
|
||||
});
|
||||
|
||||
console.log('登录响应:', loginResponse.data);
|
||||
|
||||
// 保存登录信息(兼容旧方式)
|
||||
if (loginResponse.data.token) {
|
||||
localStorage.setItem('token', loginResponse.data.token);
|
||||
}
|
||||
|
||||
if (loginResponse.data.user) {
|
||||
localStorage.setItem('user', JSON.stringify(loginResponse.data.user));
|
||||
}
|
||||
|
||||
loginResult = { success: true };
|
||||
}
|
||||
|
||||
console.log('登录结果:', loginResult);
|
||||
|
||||
if (loginResult.success) {
|
||||
toast({
|
||||
title: status === "login_success" ? "登录成功" : "注册成功",
|
||||
description: "正在跳转...",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
// 跳转到首页
|
||||
console.log('准备跳转到 /home');
|
||||
setTimeout(() => {
|
||||
navigate("/home"); // 修改为跳转到 /home
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
} catch (loginError) {
|
||||
console.error('登录失败:', loginError);
|
||||
toast({
|
||||
title: "登录失败",
|
||||
description: loginError.response?.data?.error || loginError.message || "请重试",
|
||||
status: "error",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查微信状态失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 增加备用的状态检查机制
|
||||
useEffect(() => {
|
||||
if (registerType === 0 && wechatSessionId) {
|
||||
// 每隔3秒检查一次状态(备用机制)
|
||||
const backupCheck = setInterval(() => {
|
||||
if (wechatStatus === "waiting") {
|
||||
console.log('备用检查机制:检查微信状态');
|
||||
checkWechatStatus();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return () => clearInterval(backupCheck);
|
||||
}
|
||||
}, [registerType, wechatSessionId, wechatStatus]);
|
||||
// 开始检查微信扫码状态
|
||||
const startWechatStatusCheck = (sessionId) => {
|
||||
// 清除之前的轮询
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/api/auth/wechat/check`, {
|
||||
session_id: sessionId
|
||||
});
|
||||
|
||||
const { status, user_info } = response.data;
|
||||
setWechatStatus(status);
|
||||
|
||||
if (status === "login_success" || status === "register_success") {
|
||||
// 成功,停止轮询
|
||||
clearInterval(interval);
|
||||
setCheckInterval(null);
|
||||
|
||||
// 调用登录接口
|
||||
let loginResult;
|
||||
|
||||
if (loginWithWechat) {
|
||||
loginResult = await loginWithWechat(sessionId);
|
||||
} else {
|
||||
const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login/wechat`, {
|
||||
session_id: sessionId
|
||||
});
|
||||
|
||||
// 保存登录信息
|
||||
if (loginResponse.data.token) {
|
||||
localStorage.setItem('token', loginResponse.data.token);
|
||||
}
|
||||
|
||||
if (loginResponse.data.user) {
|
||||
localStorage.setItem('user', JSON.stringify(loginResponse.data.user));
|
||||
}
|
||||
|
||||
loginResult = { success: true };
|
||||
}
|
||||
|
||||
if (loginResult.success) {
|
||||
toast({
|
||||
title: status === "login_success" ? "登录成功" : "注册成功",
|
||||
description: "正在跳转...",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
navigate("/home"); // 跳转到首页
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
} else if (status === "expired") {
|
||||
clearInterval(interval);
|
||||
setCheckInterval(null);
|
||||
setWechatStatus("expired");
|
||||
toast({
|
||||
title: "授权已过期",
|
||||
description: "请重新获取授权",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查微信状态失败:", error);
|
||||
// 继续轮询,不中断
|
||||
}
|
||||
}, 2000); // 每2秒检查一次
|
||||
|
||||
setCheckInterval(interval);
|
||||
|
||||
// 5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
setCheckInterval(null);
|
||||
if (wechatStatus === "waiting") {
|
||||
setWechatStatus("expired");
|
||||
}
|
||||
}
|
||||
}, 300000);
|
||||
};
|
||||
|
||||
// 组件卸载时清除轮询
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
};
|
||||
}, [checkInterval]);
|
||||
|
||||
// 初始化时如果选择了微信登录,获取授权URL
|
||||
useEffect(() => {
|
||||
if (registerType === 0 && agreeToTerms) {
|
||||
getWechatQRCode();
|
||||
}
|
||||
}, [registerType, agreeToTerms]);
|
||||
|
||||
// 发送验证码
|
||||
const sendVerificationCode = async () => {
|
||||
const contact = formData.phone;
|
||||
const endpoint = "send-sms-code";
|
||||
const fieldName = "phone";
|
||||
|
||||
if (!contact) {
|
||||
toast({
|
||||
title: "请输入手机号",
|
||||
status: "warning",
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(contact)) {
|
||||
toast({
|
||||
title: "请输入正确的手机号",
|
||||
status: "warning",
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await axios.post(`${API_BASE_URL}/api/auth/${endpoint}`, {
|
||||
[fieldName]: contact
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "验证码已发送",
|
||||
description: "请查收短信",
|
||||
status: "success",
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
setCountdown(60);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "发送失败",
|
||||
description: error.response?.data?.error || "请稍后重试",
|
||||
status: "error",
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
|
||||
if (!formData.username || formData.username.length < 3) {
|
||||
newErrors.username = "用户名至少3个字符";
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_]{3,20}$/.test(formData.username)) {
|
||||
newErrors.username = "用户名只能包含字母、数字和下划线,3-20个字符";
|
||||
}
|
||||
|
||||
if (!formData.password || formData.password.length < 6) {
|
||||
newErrors.password = "密码至少6个字符";
|
||||
}
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
newErrors.confirmPassword = "两次密码不一致";
|
||||
}
|
||||
|
||||
if (registerType === 1) {
|
||||
if (!formData.phone) {
|
||||
newErrors.phone = "请输入手机号";
|
||||
} else if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
|
||||
newErrors.phone = "请输入正确的手机号";
|
||||
}
|
||||
|
||||
if (!formData.verificationCode) {
|
||||
newErrors.verificationCode = "请输入验证码";
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
// 处理注册提交
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!agreeToTerms) {
|
||||
toast({
|
||||
title: "请先同意协议",
|
||||
description: "请勾选同意用户协议和隐私政策后再注册",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const endpoint = "/api/auth/register/phone";
|
||||
const data = {
|
||||
phone: formData.phone,
|
||||
code: formData.verificationCode,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
};
|
||||
|
||||
await axios.post(`${API_BASE_URL}${endpoint}`, data);
|
||||
|
||||
toast({
|
||||
title: "注册成功",
|
||||
description: "即将跳转到登录页面",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
navigate("/auth/sign-in");
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "注册失败",
|
||||
description: error.response?.data?.error || "请稍后重试",
|
||||
status: "error",
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
if (errors[name]) {
|
||||
setErrors(prev => ({ ...prev, [name]: "" }));
|
||||
}
|
||||
};
|
||||
|
||||
// 切换注册方式时的处理
|
||||
const handleRegisterTypeChange = (newType) => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
setCheckInterval(null);
|
||||
}
|
||||
|
||||
setRegisterType(newType);
|
||||
setErrors({});
|
||||
setAgreeToTerms(false); // 切换注册方式时重置协议同意状态
|
||||
setFormData({
|
||||
username: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
verificationCode: ""
|
||||
});
|
||||
|
||||
if (newType === 0) {
|
||||
setWechatStatus("waiting");
|
||||
setWechatAuthUrl("");
|
||||
setWechatSessionId("");
|
||||
// 不自动获取二维码,等用户同意协议后再获取
|
||||
}
|
||||
};
|
||||
|
||||
const getWechatStatusText = () => {
|
||||
switch (wechatStatus) {
|
||||
case "waiting": return "请使用微信扫描二维码";
|
||||
case "login_success": return "✓ 登录成功,正在跳转...";
|
||||
case "register_success": return "✓ 注册成功,正在跳转...";
|
||||
case "expired": return "授权已过期";
|
||||
default: return "请使用微信扫描二维码";
|
||||
}
|
||||
};
|
||||
|
||||
const getWechatStatusColor = () => {
|
||||
switch (wechatStatus) {
|
||||
case "login_success":
|
||||
case "register_success": return "green.600";
|
||||
case "expired": return "red.600";
|
||||
default: return "gray.600";
|
||||
}
|
||||
};
|
||||
|
||||
// 公用的用户名和密码输入框组件
|
||||
const commonAuthFields = (
|
||||
<>
|
||||
<FormControl isRequired isInvalid={!!errors.username}>
|
||||
<FormLabel fontSize="sm">用户名</FormLabel>
|
||||
<InputGroup>
|
||||
<InputRightElement pointerEvents="none">
|
||||
<Icon as={FaUser} color="gray.400" />
|
||||
</InputRightElement>
|
||||
<Input
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleInputChange}
|
||||
placeholder="设置用户名(3-20个字符)"
|
||||
pr="2.5rem"
|
||||
/>
|
||||
</InputGroup>
|
||||
<FormErrorMessage>{errors.username}</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired isInvalid={!!errors.password}>
|
||||
<FormLabel fontSize="sm">密码</FormLabel>
|
||||
<InputGroup>
|
||||
<Input
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
placeholder="设置密码(至少6个字符)"
|
||||
pr="3rem"
|
||||
/>
|
||||
<InputRightElement width="3rem">
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={showPassword ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
/>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
<FormErrorMessage>{errors.password}</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired isInvalid={!!errors.confirmPassword}>
|
||||
<FormLabel fontSize="sm">确认密码</FormLabel>
|
||||
<Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleInputChange}
|
||||
placeholder="再次输入密码"
|
||||
/>
|
||||
<FormErrorMessage>{errors.confirmPassword}</FormErrorMessage>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex minH="100vh" position="relative" overflow="hidden">
|
||||
{/* 背景 */}
|
||||
<Box
|
||||
position="absolute" top={0} left={0} right={0} bottom={0} zIndex={0}
|
||||
background={`linear-gradient(45deg, rgba(139, 69, 19, 0.9) 0%, rgba(160, 82, 45, 0.8) 15%, rgba(205, 133, 63, 0.7) 30%, rgba(222, 184, 135, 0.8) 45%, rgba(245, 222, 179, 0.6) 60%, rgba(255, 228, 196, 0.7) 75%, rgba(139, 69, 19, 0.8) 100%)`}
|
||||
_before={{ content: '""', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, background: `conic-gradient(from 0deg at 30% 20%, rgba(255, 140, 0, 0.6) 0deg, rgba(255, 69, 0, 0.4) 60deg, rgba(139, 69, 19, 0.5) 120deg, rgba(160, 82, 45, 0.6) 180deg, rgba(205, 133, 63, 0.4) 240deg, rgba(255, 140, 0, 0.5) 300deg, rgba(255, 140, 0, 0.6) 360deg)`, mixBlendMode: 'multiply', animation: 'fluid-rotate 20s linear infinite' }}
|
||||
_after={{ content: '""', position: 'absolute', top: '10%', left: '20%', width: '60%', height: '80%', borderRadius: '50%', background: 'radial-gradient(ellipse at center, rgba(255, 165, 0, 0.3) 0%, rgba(255, 140, 0, 0.2) 50%, transparent 70%)', filter: 'blur(40px)', animation: 'wave-pulse 8s ease-in-out infinite' }}
|
||||
sx={{ '@keyframes fluid-rotate': { '0%': { transform: 'rotate(0deg) scale(1)' }, '50%': { transform: 'rotate(180deg) scale(1.1)' }, '100%': { transform: 'rotate(360deg) scale(1)' } }, '@keyframes wave-pulse': { '0%, 100%': { opacity: 0.4, transform: 'scale(1)' }, '50%': { opacity: 0.8, transform: 'scale(1.2)' } } }}
|
||||
/>
|
||||
|
||||
{/* 主要内容 */}
|
||||
<Flex width="100%" align="center" justify="center" position="relative" zIndex={1} px={6} py={12}>
|
||||
<Box bg="white" borderRadius="2xl" boxShadow="2xl" p={8} width="100%" maxW="480px" backdropFilter="blur(20px)" border="1px solid rgba(255, 255, 255, 0.2)">
|
||||
<VStack spacing={6} mb={8}>
|
||||
<VStack spacing={2}>
|
||||
<Heading size="xl" color="gray.800" fontWeight="bold">创建账户</Heading>
|
||||
<Text color="gray.600" fontSize="md">加入价值前沿,开启投资新征程</Text>
|
||||
</VStack>
|
||||
<Box width="100%">
|
||||
<Tabs index={registerType} onChange={handleRegisterTypeChange} variant="soft-rounded" colorScheme="orange" isFitted>
|
||||
<TabList bg="gray.100" borderRadius="xl" p={1}>
|
||||
<Tab borderRadius="lg" _selected={{ bg: "linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)", color: "white", transform: "scale(1.02)" }} transition="all 0.2s">
|
||||
<Icon as={FaWeixin} mr={2} /><Text fontSize="sm">微信扫码</Text>
|
||||
</Tab>
|
||||
<Tab borderRadius="lg" _selected={{ bg: "linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)", color: "white", transform: "scale(1.02)" }} transition="all 0.2s">
|
||||
<Icon as={FaMobile} mr={2} /><Text fontSize="sm">手机号</Text>
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</Box>
|
||||
</VStack>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<VStack spacing={4}>
|
||||
{/* 微信注册 */}
|
||||
{registerType === 0 && (
|
||||
<>
|
||||
{/* 协议同意勾选框 - 微信注册 */}
|
||||
<Box width="100%" mb={4}>
|
||||
<Checkbox
|
||||
isChecked={agreeToTerms}
|
||||
onChange={(e) => setAgreeToTerms(e.target.checked)}
|
||||
colorScheme="orange"
|
||||
size="sm"
|
||||
>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
我已阅读并同意{" "}
|
||||
<ChakraLink
|
||||
color="orange.500"
|
||||
fontSize="sm"
|
||||
onClick={onUserAgreementModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "orange.600" }}
|
||||
>
|
||||
《用户协议》
|
||||
</ChakraLink>
|
||||
{" "}和{" "}
|
||||
<ChakraLink
|
||||
color="orange.500"
|
||||
fontSize="sm"
|
||||
onClick={onPrivacyModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "orange.600" }}
|
||||
>
|
||||
《隐私政策》
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Checkbox>
|
||||
{!agreeToTerms && (
|
||||
<Text fontSize="xs" color="red.500" mt={1} ml={6}>
|
||||
请先同意用户协议和隐私政策
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Center width="100%" height="420px" bg="gray.50" borderRadius="lg" p={4} mb={4}>
|
||||
{agreeToTerms ? (
|
||||
wechatAuthUrl && wechatStatus !== "expired" ? (
|
||||
<Box position="relative" width="100%" height="100%">
|
||||
<iframe
|
||||
src={wechatAuthUrl}
|
||||
width="100%"
|
||||
height="100%"
|
||||
frameBorder="0"
|
||||
scrolling="no"
|
||||
style={{ borderRadius: '8px' }}
|
||||
/>
|
||||
{(wechatStatus === "login_success" || wechatStatus === "register_success") && (
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}
|
||||
bg="rgba(0,0,0,0.7)" display="flex" alignItems="center"
|
||||
justifyContent="center" borderRadius="lg">
|
||||
<VStack>
|
||||
<Spinner color="white" />
|
||||
<Text color="white" fontWeight="bold">
|
||||
{wechatStatus === "login_success" ? "正在登录..." : "正在创建账号..."}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : wechatStatus === "expired" ? (
|
||||
<VStack>
|
||||
<Text color="gray.500" fontWeight="bold" mb={4}>授权已过期</Text>
|
||||
<Button colorScheme="orange" size="sm" onClick={getWechatQRCode}
|
||||
isLoading={isLoading}>重新获取</Button>
|
||||
</VStack>
|
||||
) : (
|
||||
<VStack>
|
||||
<Spinner size="xl" color="orange.500" />
|
||||
<Text color="gray.500" fontSize="sm">加载中...</Text>
|
||||
</VStack>
|
||||
)
|
||||
) : (
|
||||
<VStack spacing={4}>
|
||||
<Icon as={FaWeixin} w={20} h={20} color="gray.400" />
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="lg" fontWeight="bold" color="gray.400">
|
||||
微信扫码注册
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.400" textAlign="center">
|
||||
请先同意用户协议和隐私政策
|
||||
</Text>
|
||||
</VStack>
|
||||
<Button
|
||||
colorScheme="orange"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
isDisabled
|
||||
opacity={0.6}
|
||||
>
|
||||
同意协议后显示二维码
|
||||
</Button>
|
||||
</VStack>
|
||||
)}
|
||||
</Center>
|
||||
<Text textAlign="center" color={getWechatStatusColor()} fontSize="sm"
|
||||
fontWeight={wechatStatus === "login_success" || wechatStatus === "register_success" ? "bold" : "normal"}>
|
||||
{agreeToTerms ? getWechatStatusText() : "请先同意用户协议和隐私政策"}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.500" textAlign="center">
|
||||
{agreeToTerms
|
||||
? "扫码即表示同意创建账号,系统将使用您的微信昵称作为初始用户名"
|
||||
: "同意协议后即可使用微信快速注册"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 手机号注册 */}
|
||||
{registerType === 1 && (
|
||||
<>
|
||||
<FormControl isRequired isInvalid={!!errors.phone}>
|
||||
<FormLabel fontSize="sm">手机号</FormLabel>
|
||||
<InputGroup>
|
||||
<InputRightElement pointerEvents="none"><Icon as={FaMobile} color="gray.400" /></InputRightElement>
|
||||
<Input name="phone" value={formData.phone} onChange={handleInputChange} placeholder="请输入11位手机号" pr="2.5rem" />
|
||||
</InputGroup>
|
||||
<FormErrorMessage>{errors.phone}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isRequired isInvalid={!!errors.verificationCode}>
|
||||
<FormLabel fontSize="sm">验证码</FormLabel>
|
||||
<HStack>
|
||||
<Input name="verificationCode" value={formData.verificationCode} onChange={handleInputChange} placeholder="请输入6位验证码" />
|
||||
<Button colorScheme="orange" onClick={sendVerificationCode} isDisabled={countdown > 0 || isLoading} isLoading={isLoading && countdown === 0} minW="120px">
|
||||
{countdown > 0 ? `${countdown}秒后重试` : "获取验证码"}
|
||||
</Button>
|
||||
</HStack>
|
||||
<FormErrorMessage>{errors.verificationCode}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Divider my={2} />
|
||||
{commonAuthFields}
|
||||
|
||||
{/* 协议同意勾选框 - 手机号注册 */}
|
||||
<Box width="100%" py={3}>
|
||||
<Checkbox
|
||||
isChecked={agreeToTerms}
|
||||
onChange={(e) => setAgreeToTerms(e.target.checked)}
|
||||
colorScheme="orange"
|
||||
size="sm"
|
||||
>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
我已阅读并同意{" "}
|
||||
<ChakraLink
|
||||
color="orange.500"
|
||||
fontSize="sm"
|
||||
onClick={onUserAgreementModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "orange.600" }}
|
||||
>
|
||||
《用户协议》
|
||||
</ChakraLink>
|
||||
{" "}和{" "}
|
||||
<ChakraLink
|
||||
color="orange.500"
|
||||
fontSize="sm"
|
||||
onClick={onPrivacyModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "orange.600" }}
|
||||
>
|
||||
《隐私政策》
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Checkbox>
|
||||
{!agreeToTerms && (
|
||||
<Text fontSize="xs" color="red.500" mt={1} ml={6}>
|
||||
请先同意用户协议和隐私政策
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{registerType !== 0 && (
|
||||
<Button
|
||||
type="submit"
|
||||
width="100%"
|
||||
size="lg"
|
||||
background={agreeToTerms
|
||||
? "linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)"
|
||||
: "gray.300"
|
||||
}
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
_hover={agreeToTerms ? {
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: "lg"
|
||||
} : {}}
|
||||
_active={agreeToTerms ? {
|
||||
transform: "translateY(0)"
|
||||
} : {}}
|
||||
isLoading={isLoading}
|
||||
loadingText="注册中..."
|
||||
fontWeight="bold"
|
||||
isDisabled={!agreeToTerms}
|
||||
cursor={agreeToTerms ? "pointer" : "not-allowed"}
|
||||
>
|
||||
完成注册
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Text fontSize="sm" color="gray.600" textAlign="center">
|
||||
已有账号?{" "}
|
||||
<ChakraLink as={Link} to="/auth/sign-in" color="orange.500" fontWeight="bold">
|
||||
立即登录
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</VStack>
|
||||
</form>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Box position="absolute" bottom={8} left="50%" transform="translateX(-50%)" zIndex={1}>
|
||||
<HStack spacing={6}>
|
||||
<ChakraLink
|
||||
color="white"
|
||||
fontSize="sm"
|
||||
opacity={0.8}
|
||||
_hover={{ opacity: 1 }}
|
||||
onClick={onUserAgreementModalOpen}
|
||||
>
|
||||
用户协议
|
||||
</ChakraLink>
|
||||
<ChakraLink
|
||||
color="white"
|
||||
fontSize="sm"
|
||||
opacity={0.8}
|
||||
_hover={{ opacity: 1 }}
|
||||
onClick={onPrivacyModalOpen}
|
||||
>
|
||||
隐私政策
|
||||
</ChakraLink>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 隐私政策弹窗 */}
|
||||
<PrivacyPolicyModal
|
||||
isOpen={isPrivacyModalOpen}
|
||||
onClose={onPrivacyModalClose}
|
||||
/>
|
||||
|
||||
{/* 用户协议弹窗 */}
|
||||
<UserAgreementModal
|
||||
isOpen={isUserAgreementModalOpen}
|
||||
onClose={onUserAgreementModalClose}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user