updated
This commit is contained in:
@@ -25,26 +25,22 @@ import {
|
||||
Spinner,
|
||||
Divider,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
useDisclosure
|
||||
AlertIcon
|
||||
} from "@chakra-ui/react";
|
||||
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons";
|
||||
import { FaUser, FaEnvelope, FaMobile, FaWeixin, FaLock, FaQrcode, FaCode } from "react-icons/fa";
|
||||
import { FaUser, FaEnvelope, FaMobile, FaWeixin, FaLock, FaQrcode } from "react-icons/fa";
|
||||
import { useNavigate, Link, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "../../../contexts/AuthContext";
|
||||
import PrivacyPolicyModal from "../../../components/PrivacyPolicyModal";
|
||||
import UserAgreementModal from "../../../components/UserAgreementModal";
|
||||
|
||||
// API配置
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const API_BASE_URL = isProduction ? "" : "http://49.232.185.254:5000";
|
||||
const API_BASE_URL = isProduction ? "" : "http://49.232.185.254:5001";
|
||||
|
||||
export default function SignInIllustration() {
|
||||
const [loginType, setLoginType] = useState(0); // 0: 微信, 1: 手机号
|
||||
const [loginType, setLoginType] = useState(0); // 0: 微信, 1: 手机号, 2: 邮箱, 3: 用户名
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||
|
||||
// 传统登录表单数据
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -52,50 +48,16 @@ export default function SignInIllustration() {
|
||||
email: "",
|
||||
phone: "",
|
||||
password: "",
|
||||
verificationCode: "", // 添加验证码字段
|
||||
});
|
||||
|
||||
// 验证码登录相关状态
|
||||
const [useVerificationCode, setUseVerificationCode] = useState(false);
|
||||
const [verificationCodeSent, setVerificationCodeSent] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [sendingCode, setSendingCode] = useState(false);
|
||||
|
||||
// 微信登录相关状态
|
||||
const [wechatAuthUrl, setWechatAuthUrl] = useState("");
|
||||
|
||||
// 隐私政策弹窗状态
|
||||
const {
|
||||
isOpen: isPrivacyModalOpen,
|
||||
onOpen: onPrivacyModalOpen,
|
||||
onClose: onPrivacyModalClose
|
||||
} = useDisclosure();
|
||||
|
||||
// 用户协议弹窗状态
|
||||
const {
|
||||
isOpen: isUserAgreementModalOpen,
|
||||
onOpen: onUserAgreementModalOpen,
|
||||
onClose: onUserAgreementModalClose
|
||||
} = useDisclosure();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const toast = useToast();
|
||||
const { login, checkSession } = useAuth();
|
||||
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
if (countdown > 0) {
|
||||
timer = setInterval(() => {
|
||||
setCountdown(prev => prev - 1);
|
||||
}, 1000);
|
||||
} else if (countdown === 0) {
|
||||
setVerificationCodeSent(false);
|
||||
}
|
||||
return () => clearInterval(timer);
|
||||
}, [countdown]);
|
||||
|
||||
// 检查URL参数中的错误信息(微信登录失败时)
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -173,17 +135,6 @@ export default function SignInIllustration() {
|
||||
|
||||
// 打开微信登录窗口
|
||||
const openWechatLogin = () => {
|
||||
if (!agreeToTerms) {
|
||||
toast({
|
||||
title: "请先同意协议",
|
||||
description: "请勾选同意用户协议和隐私政策后再使用微信登录",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (wechatAuthUrl) {
|
||||
// 方案1:直接跳转(推荐)
|
||||
window.location.href = wechatAuthUrl;
|
||||
@@ -201,70 +152,6 @@ export default function SignInIllustration() {
|
||||
}
|
||||
};
|
||||
|
||||
// 发送验证码
|
||||
const sendVerificationCode = async () => {
|
||||
const credential = formData.phone;
|
||||
const type = 'phone';
|
||||
|
||||
if (!credential) {
|
||||
toast({
|
||||
title: "请先输入手机号",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 基本格式验证
|
||||
if (!/^1[3-9]\d{9}$/.test(credential)) {
|
||||
toast({
|
||||
title: "请输入有效的手机号",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setSendingCode(true);
|
||||
const response = await fetch(`${API_BASE_URL}/api/auth/send-verification-code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
credential,
|
||||
type,
|
||||
purpose: 'login'
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
toast({
|
||||
title: "验证码已发送",
|
||||
description: "验证码已发送到您的手机号",
|
||||
status: "success",
|
||||
duration: 3000,
|
||||
});
|
||||
setVerificationCodeSent(true);
|
||||
setCountdown(60); // 60秒倒计时
|
||||
} else {
|
||||
throw new Error(data.error || '发送验证码失败');
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "发送验证码失败",
|
||||
description: error.message || "请稍后重试",
|
||||
status: "error",
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setSendingCode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({
|
||||
@@ -275,18 +162,6 @@ export default function SignInIllustration() {
|
||||
|
||||
const handleTraditionalLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!agreeToTerms) {
|
||||
toast({
|
||||
title: "请先同意协议",
|
||||
description: "请勾选同意用户协议和隐私政策后再登录",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
@@ -296,40 +171,28 @@ export default function SignInIllustration() {
|
||||
if (loginType === 1) {
|
||||
credential = formData.phone;
|
||||
authLoginType = 'phone';
|
||||
} else if (loginType === 2) {
|
||||
credential = formData.email;
|
||||
authLoginType = 'email';
|
||||
} else if (loginType === 3) {
|
||||
credential = formData.username;
|
||||
authLoginType = 'username';
|
||||
}
|
||||
|
||||
// 验证码登录
|
||||
if (useVerificationCode && loginType === 1) {
|
||||
if (!credential || !formData.verificationCode) {
|
||||
toast({
|
||||
title: "请填写完整信息",
|
||||
description: "手机号和验证码不能为空",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!credential || !formData.password) {
|
||||
toast({
|
||||
title: "请填写完整信息",
|
||||
description: "用户名和密码不能为空",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await loginWithVerificationCode(credential, formData.verificationCode, authLoginType);
|
||||
if (result.success) {
|
||||
navigate("/home");
|
||||
}
|
||||
} else {
|
||||
// 传统密码登录
|
||||
if (!credential || !formData.password) {
|
||||
toast({
|
||||
title: "请填写完整信息",
|
||||
description: `${getCredentialName()}和密码不能为空`,
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await login(credential, formData.password, authLoginType);
|
||||
|
||||
const result = await login(credential, formData.password, authLoginType);
|
||||
if (result.success) {
|
||||
navigate("/home");
|
||||
}
|
||||
if (result.success) {
|
||||
navigate("/home");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
@@ -338,63 +201,31 @@ export default function SignInIllustration() {
|
||||
}
|
||||
};
|
||||
|
||||
// 验证码登录函数
|
||||
const loginWithVerificationCode = async (credential, verificationCode, authLoginType) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/auth/login-with-code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
credential,
|
||||
verification_code: verificationCode,
|
||||
login_type: authLoginType
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
// 更新认证状态
|
||||
await checkSession();
|
||||
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,
|
||||
});
|
||||
return { success: false, error: error.message };
|
||||
const getInputPlaceholder = () => {
|
||||
switch (loginType) {
|
||||
case 1: return "请输入手机号";
|
||||
case 2: return "请输入邮箱";
|
||||
case 3: return "请输入用户名";
|
||||
default: return "请输入用户名";
|
||||
}
|
||||
};
|
||||
|
||||
// 获取凭据名称
|
||||
const getCredentialName = () => {
|
||||
return "手机号";
|
||||
};
|
||||
|
||||
const getInputPlaceholder = () => {
|
||||
return "请输入手机号";
|
||||
};
|
||||
|
||||
const getInputName = () => {
|
||||
return "phone";
|
||||
switch (loginType) {
|
||||
case 1: return "phone";
|
||||
case 2: return "email";
|
||||
case 3: return "username";
|
||||
default: return "username";
|
||||
}
|
||||
};
|
||||
|
||||
const getInputValue = () => {
|
||||
return formData.phone;
|
||||
switch (loginType) {
|
||||
case 1: return formData.phone;
|
||||
case 2: return formData.email;
|
||||
case 3: return formData.username;
|
||||
default: return formData.username;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -532,6 +363,32 @@ export default function SignInIllustration() {
|
||||
<Icon as={FaMobile} mr={1} />
|
||||
手机号
|
||||
</Tab>
|
||||
<Tab
|
||||
borderRadius="lg"
|
||||
_selected={{
|
||||
bg: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
color: "white",
|
||||
transform: "scale(1.02)"
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
fontSize="sm"
|
||||
>
|
||||
<Icon as={FaEnvelope} mr={1} />
|
||||
邮箱
|
||||
</Tab>
|
||||
<Tab
|
||||
borderRadius="lg"
|
||||
_selected={{
|
||||
bg: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
color: "white",
|
||||
transform: "scale(1.02)"
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
fontSize="sm"
|
||||
>
|
||||
<Icon as={FaUser} mr={1} />
|
||||
用户名
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</Box>
|
||||
@@ -541,54 +398,16 @@ export default function SignInIllustration() {
|
||||
{loginType === 0 ? (
|
||||
// 微信登录 - 简化版
|
||||
<VStack spacing={6}>
|
||||
{/* 协议同意勾选框 - 微信登录 */}
|
||||
<Box width="100%">
|
||||
<Checkbox
|
||||
isChecked={agreeToTerms}
|
||||
onChange={(e) => setAgreeToTerms(e.target.checked)}
|
||||
colorScheme="green"
|
||||
size="sm"
|
||||
>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
我已阅读并同意{" "}
|
||||
<ChakraLink
|
||||
color="green.500"
|
||||
fontSize="sm"
|
||||
onClick={onUserAgreementModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "green.600" }}
|
||||
>
|
||||
《用户协议》
|
||||
</ChakraLink>
|
||||
{" "}和{" "}
|
||||
<ChakraLink
|
||||
color="green.500"
|
||||
fontSize="sm"
|
||||
onClick={onPrivacyModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "green.600" }}
|
||||
>
|
||||
《隐私政策》
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Checkbox>
|
||||
{!agreeToTerms && (
|
||||
<Text fontSize="xs" color="red.500" mt={1} ml={6}>
|
||||
请先同意用户协议和隐私政策
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Center width="100%" bg="gray.50" borderRadius="lg" p={8}>
|
||||
<VStack spacing={6}>
|
||||
<Icon as={FaQrcode} w={20} h={20} color={agreeToTerms ? "green.500" : "gray.400"} />
|
||||
<Icon as={FaQrcode} w={20} h={20} color="green.500" />
|
||||
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="lg" fontWeight="bold" color={agreeToTerms ? "gray.700" : "gray.400"}>
|
||||
<Text fontSize="lg" fontWeight="bold" color="gray.700">
|
||||
微信扫码登录
|
||||
</Text>
|
||||
<Text fontSize="sm" color={agreeToTerms ? "gray.500" : "gray.400"} textAlign="center">
|
||||
{agreeToTerms ? "使用微信扫一扫,安全快速登录" : "请先同意协议后使用微信登录"}
|
||||
<Text fontSize="sm" color="gray.500" textAlign="center">
|
||||
使用微信扫一扫,安全快速登录
|
||||
</Text>
|
||||
</VStack>
|
||||
|
||||
@@ -599,20 +418,19 @@ export default function SignInIllustration() {
|
||||
onClick={openWechatLogin}
|
||||
isLoading={isLoading || !wechatAuthUrl}
|
||||
loadingText="准备中..."
|
||||
isDisabled={!wechatAuthUrl || !agreeToTerms}
|
||||
_hover={agreeToTerms ? {
|
||||
isDisabled={!wechatAuthUrl}
|
||||
_hover={{
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: "lg"
|
||||
} : {}}
|
||||
_active={agreeToTerms ? {
|
||||
}}
|
||||
_active={{
|
||||
transform: "translateY(0)"
|
||||
} : {}}
|
||||
opacity={agreeToTerms ? 1 : 0.6}
|
||||
}}
|
||||
>
|
||||
立即扫码登录
|
||||
</Button>
|
||||
|
||||
{!wechatAuthUrl && !isLoading && agreeToTerms && (
|
||||
{!wechatAuthUrl && !isLoading && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="link"
|
||||
@@ -626,17 +444,15 @@ export default function SignInIllustration() {
|
||||
</Center>
|
||||
|
||||
<Alert
|
||||
status={agreeToTerms ? "info" : "warning"}
|
||||
status="info"
|
||||
borderRadius="lg"
|
||||
bg={agreeToTerms ? "blue.50" : "orange.50"}
|
||||
bg="blue.50"
|
||||
border="1px solid"
|
||||
borderColor={agreeToTerms ? "blue.200" : "orange.200"}
|
||||
borderColor="blue.200"
|
||||
>
|
||||
<AlertIcon color={agreeToTerms ? "blue.500" : "orange.500"} />
|
||||
<Text fontSize="sm" color={agreeToTerms ? "blue.700" : "orange.700"}>
|
||||
{agreeToTerms
|
||||
? "点击按钮后将跳转到微信授权页面"
|
||||
: "请先同意用户协议和隐私政策"}
|
||||
<AlertIcon color="blue.500" />
|
||||
<Text fontSize="sm" color="blue.700">
|
||||
点击按钮后将跳转到微信授权页面
|
||||
</Text>
|
||||
</Alert>
|
||||
</VStack>
|
||||
@@ -663,115 +479,40 @@ export default function SignInIllustration() {
|
||||
/>
|
||||
<InputRightElement pointerEvents="none">
|
||||
<Icon
|
||||
as={FaMobile}
|
||||
as={loginType === 1 ? FaMobile : loginType === 2 ? FaEnvelope : FaUser}
|
||||
color="gray.400"
|
||||
/>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
{/* 登录方式切换(仅手机号显示) */}
|
||||
{loginType === 1 && (
|
||||
<VStack spacing={2} align="stretch">
|
||||
<HStack justify="center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={useVerificationCode ? "ghost" : "solid"}
|
||||
colorScheme="blue"
|
||||
onClick={() => {
|
||||
setUseVerificationCode(false);
|
||||
setFormData(prev => ({ ...prev, verificationCode: "" }));
|
||||
<FormControl isRequired>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
placeholder="请输入密码"
|
||||
borderRadius="lg"
|
||||
bg="gray.50"
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
_focus={{
|
||||
borderColor: "blue.500",
|
||||
boxShadow: "0 0 0 1px #667eea"
|
||||
}}
|
||||
>
|
||||
密码登录
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={useVerificationCode ? "solid" : "ghost"}
|
||||
colorScheme="green"
|
||||
onClick={() => setUseVerificationCode(true)}
|
||||
>
|
||||
验证码登录
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 密码输入框 */}
|
||||
{!useVerificationCode ? (
|
||||
<FormControl isRequired>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
placeholder="请输入密码"
|
||||
borderRadius="lg"
|
||||
bg="gray.50"
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
_focus={{
|
||||
borderColor: "blue.500",
|
||||
boxShadow: "0 0 0 1px #667eea"
|
||||
}}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label={showPassword ? "隐藏密码" : "显示密码"}
|
||||
icon={showPassword ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label={showPassword ? "隐藏密码" : "显示密码"}
|
||||
icon={showPassword ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
) : (
|
||||
// 验证码输入框
|
||||
<VStack spacing={3} align="stretch">
|
||||
<HStack>
|
||||
<FormControl isRequired flex={2}>
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
name="verificationCode"
|
||||
value={formData.verificationCode}
|
||||
onChange={handleInputChange}
|
||||
placeholder="请输入验证码"
|
||||
borderRadius="lg"
|
||||
bg="gray.50"
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
_focus={{
|
||||
borderColor: "green.500",
|
||||
boxShadow: "0 0 0 1px #48bb78"
|
||||
}}
|
||||
maxLength={6}
|
||||
/>
|
||||
<InputRightElement>
|
||||
<Icon as={FaCode} color="gray.400" />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
<Button
|
||||
flex={1}
|
||||
size="lg"
|
||||
colorScheme="green"
|
||||
variant="outline"
|
||||
onClick={sendVerificationCode}
|
||||
isLoading={sendingCode}
|
||||
isDisabled={verificationCodeSent && countdown > 0}
|
||||
borderRadius="lg"
|
||||
>
|
||||
{sendingCode
|
||||
? "发送中..."
|
||||
: verificationCodeSent && countdown > 0
|
||||
? `${countdown}s`
|
||||
: "发送验证码"
|
||||
}
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<HStack justify="space-between" width="100%">
|
||||
<Checkbox
|
||||
@@ -786,66 +527,23 @@ export default function SignInIllustration() {
|
||||
</ChakraLink>
|
||||
</HStack>
|
||||
|
||||
{/* 协议同意勾选框 */}
|
||||
<Box width="100%">
|
||||
<Checkbox
|
||||
isChecked={agreeToTerms}
|
||||
onChange={(e) => setAgreeToTerms(e.target.checked)}
|
||||
colorScheme="blue"
|
||||
size="sm"
|
||||
>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
我已阅读并同意{" "}
|
||||
<ChakraLink
|
||||
color="blue.500"
|
||||
fontSize="sm"
|
||||
onClick={onUserAgreementModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "blue.600" }}
|
||||
>
|
||||
《用户协议》
|
||||
</ChakraLink>
|
||||
{" "}和{" "}
|
||||
<ChakraLink
|
||||
color="blue.500"
|
||||
fontSize="sm"
|
||||
onClick={onPrivacyModalOpen}
|
||||
textDecoration="underline"
|
||||
_hover={{ color: "blue.600" }}
|
||||
>
|
||||
《隐私政策》
|
||||
</ChakraLink>
|
||||
</Text>
|
||||
</Checkbox>
|
||||
{!agreeToTerms && (
|
||||
<Text fontSize="xs" color="red.500" mt={1} ml={6}>
|
||||
请先同意用户协议和隐私政策
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
width="100%"
|
||||
size="lg"
|
||||
background={agreeToTerms
|
||||
? "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
: "gray.300"
|
||||
}
|
||||
background="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
_hover={agreeToTerms ? {
|
||||
_hover={{
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: "lg"
|
||||
} : {}}
|
||||
_active={agreeToTerms ? {
|
||||
}}
|
||||
_active={{
|
||||
transform: "translateY(0)"
|
||||
} : {}}
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
loadingText="登录中..."
|
||||
fontWeight="bold"
|
||||
isDisabled={!agreeToTerms}
|
||||
cursor={agreeToTerms ? "pointer" : "not-allowed"}
|
||||
>
|
||||
<Icon as={FaLock} mr={2} />
|
||||
登录
|
||||
@@ -895,38 +593,26 @@ export default function SignInIllustration() {
|
||||
zIndex={1}
|
||||
>
|
||||
<HStack spacing={6}>
|
||||
<ChakraLink
|
||||
color="white"
|
||||
fontSize="sm"
|
||||
opacity={0.8}
|
||||
_hover={{ opacity: 1 }}
|
||||
onClick={onUserAgreementModalOpen}
|
||||
>
|
||||
用户协议
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
Company
|
||||
</ChakraLink>
|
||||
<ChakraLink
|
||||
color="white"
|
||||
fontSize="sm"
|
||||
opacity={0.8}
|
||||
_hover={{ opacity: 1 }}
|
||||
onClick={onPrivacyModalOpen}
|
||||
>
|
||||
隐私政策
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
About Us
|
||||
</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
Team
|
||||
</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
Products
|
||||
</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
Blog
|
||||
</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>
|
||||
Pricing
|
||||
</ChakraLink>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 隐私政策弹窗 */}
|
||||
<PrivacyPolicyModal
|
||||
isOpen={isPrivacyModalOpen}
|
||||
onClose={onPrivacyModalClose}
|
||||
/>
|
||||
|
||||
{/* 用户协议弹窗 */}
|
||||
<UserAgreementModal
|
||||
isOpen={isUserAgreementModalOpen}
|
||||
onClose={onUserAgreementModalClose}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -25,23 +25,19 @@ import {
|
||||
Spinner,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
Divider,
|
||||
useDisclosure,
|
||||
Checkbox
|
||||
Divider
|
||||
} 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 [registerType, setRegisterType] = useState(0); // 0: 微信, 1: 手机号, 2: 邮箱
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
@@ -50,7 +46,6 @@ export default function SignUpPage() {
|
||||
const [wechatStatus, setWechatStatus] = useState("waiting");
|
||||
const [errors, setErrors] = useState({});
|
||||
const [checkInterval, setCheckInterval] = useState(null);
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
username: "",
|
||||
@@ -61,20 +56,6 @@ export default function SignUpPage() {
|
||||
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(); // 使用认证上下文
|
||||
@@ -335,27 +316,28 @@ export default function SignUpPage() {
|
||||
|
||||
// 初始化时如果选择了微信登录,获取授权URL
|
||||
useEffect(() => {
|
||||
if (registerType === 0 && agreeToTerms) {
|
||||
if (registerType === 0) {
|
||||
getWechatQRCode();
|
||||
}
|
||||
}, [registerType, agreeToTerms]);
|
||||
}, []);
|
||||
|
||||
// 发送验证码
|
||||
const sendVerificationCode = async () => {
|
||||
const contact = formData.phone;
|
||||
const endpoint = "send-sms-code";
|
||||
const fieldName = "phone";
|
||||
const isPhone = registerType === 1;
|
||||
const contact = isPhone ? formData.phone : formData.email;
|
||||
const endpoint = isPhone ? "send-sms-code" : "send-email-code";
|
||||
const fieldName = isPhone ? "phone" : "email";
|
||||
|
||||
if (!contact) {
|
||||
toast({
|
||||
title: "请输入手机号",
|
||||
title: `请输入${isPhone ? "手机号" : "邮箱"}`,
|
||||
status: "warning",
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(contact)) {
|
||||
if (isPhone && !/^1[3-9]\d{9}$/.test(contact)) {
|
||||
toast({
|
||||
title: "请输入正确的手机号",
|
||||
status: "warning",
|
||||
@@ -364,6 +346,15 @@ export default function SignUpPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPhone && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(contact)) {
|
||||
toast({
|
||||
title: "请输入正确的邮箱格式",
|
||||
status: "warning",
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await axios.post(`${API_BASE_URL}/api/auth/${endpoint}`, {
|
||||
@@ -372,7 +363,7 @@ export default function SignUpPage() {
|
||||
|
||||
toast({
|
||||
title: "验证码已发送",
|
||||
description: "请查收短信",
|
||||
description: `请查收${isPhone ? "短信" : "邮件"}`,
|
||||
status: "success",
|
||||
duration: 3000,
|
||||
});
|
||||
@@ -424,12 +415,20 @@ export default function SignUpPage() {
|
||||
} else if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
|
||||
newErrors.phone = "请输入正确的手机号";
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.verificationCode) {
|
||||
newErrors.verificationCode = "请输入验证码";
|
||||
if (registerType === 2) {
|
||||
if (!formData.email) {
|
||||
newErrors.email = "请输入邮箱";
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
newErrors.email = "请输入正确的邮箱格式";
|
||||
}
|
||||
}
|
||||
|
||||
if ((registerType === 1 || registerType === 2) && !formData.verificationCode) {
|
||||
newErrors.verificationCode = "请输入验证码";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
@@ -438,17 +437,6 @@ export default function SignUpPage() {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!agreeToTerms) {
|
||||
toast({
|
||||
title: "请先同意协议",
|
||||
description: "请勾选同意用户协议和隐私政策后再注册",
|
||||
status: "warning",
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
@@ -456,13 +444,25 @@ export default function SignUpPage() {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const endpoint = "/api/auth/register/phone";
|
||||
const data = {
|
||||
phone: formData.phone,
|
||||
code: formData.verificationCode,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
};
|
||||
let endpoint, data;
|
||||
|
||||
if (registerType === 1) {
|
||||
endpoint = "/api/auth/register/phone";
|
||||
data = {
|
||||
phone: formData.phone,
|
||||
code: formData.verificationCode,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
};
|
||||
} else {
|
||||
endpoint = "/api/auth/register/email";
|
||||
data = {
|
||||
email: formData.email,
|
||||
code: formData.verificationCode,
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
};
|
||||
}
|
||||
|
||||
await axios.post(`${API_BASE_URL}${endpoint}`, data);
|
||||
|
||||
@@ -505,7 +505,6 @@ export default function SignUpPage() {
|
||||
|
||||
setRegisterType(newType);
|
||||
setErrors({});
|
||||
setAgreeToTerms(false); // 切换注册方式时重置协议同意状态
|
||||
setFormData({
|
||||
username: "",
|
||||
email: "",
|
||||
@@ -519,7 +518,7 @@ export default function SignUpPage() {
|
||||
setWechatStatus("waiting");
|
||||
setWechatAuthUrl("");
|
||||
setWechatSessionId("");
|
||||
// 不自动获取二维码,等用户同意协议后再获取
|
||||
getWechatQRCode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -628,6 +627,9 @@ export default function SignUpPage() {
|
||||
<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>
|
||||
<Tab borderRadius="lg" _selected={{ bg: "linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)", color: "white", transform: "scale(1.02)" }} transition="all 0.2s">
|
||||
<Icon as={FaEnvelope} mr={2} /><Text fontSize="sm">邮箱</Text>
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</Box>
|
||||
@@ -638,112 +640,49 @@ export default function SignUpPage() {
|
||||
{/* 微信注册 */}
|
||||
{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>
|
||||
{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>
|
||||
<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() : "请先同意用户协议和隐私政策"}
|
||||
{getWechatStatusText()}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.500" textAlign="center">
|
||||
{agreeToTerms
|
||||
? "扫码即表示同意创建账号,系统将使用您的微信昵称作为初始用户名"
|
||||
: "同意协议后即可使用微信快速注册"}
|
||||
扫码即表示同意创建账号,系统将使用您的微信昵称作为初始用户名
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
@@ -771,71 +710,48 @@ export default function SignUpPage() {
|
||||
</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 === 2 && (
|
||||
<>
|
||||
<FormControl isRequired isInvalid={!!errors.email}>
|
||||
<FormLabel fontSize="sm">邮箱</FormLabel>
|
||||
<InputGroup>
|
||||
<InputRightElement pointerEvents="none"><Icon as={FaEnvelope} color="gray.400" /></InputRightElement>
|
||||
<Input name="email" type="email" value={formData.email} onChange={handleInputChange} placeholder="请输入邮箱地址" pr="2.5rem" />
|
||||
</InputGroup>
|
||||
<FormErrorMessage>{errors.email}</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}
|
||||
</>
|
||||
)}
|
||||
|
||||
{registerType !== 0 && (
|
||||
<Button
|
||||
type="submit"
|
||||
width="100%"
|
||||
size="lg"
|
||||
background={agreeToTerms
|
||||
? "linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)"
|
||||
: "gray.300"
|
||||
}
|
||||
background="linear-gradient(135deg, #ff8c00 0%, #ff6347 100%)"
|
||||
color="white"
|
||||
borderRadius="lg"
|
||||
_hover={agreeToTerms ? {
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: "lg"
|
||||
} : {}}
|
||||
_active={agreeToTerms ? {
|
||||
transform: "translateY(0)"
|
||||
} : {}}
|
||||
_hover={{ transform: "translateY(-2px)", boxShadow: "lg" }}
|
||||
_active={{ transform: "translateY(0)" }}
|
||||
isLoading={isLoading}
|
||||
loadingText="注册中..."
|
||||
fontWeight="bold"
|
||||
isDisabled={!agreeToTerms}
|
||||
cursor={agreeToTerms ? "pointer" : "not-allowed"}
|
||||
>
|
||||
完成注册
|
||||
</Button>
|
||||
@@ -854,38 +770,14 @@ export default function SignUpPage() {
|
||||
|
||||
<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>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>Company</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>About Us</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>Team</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>Products</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>Blog</ChakraLink>
|
||||
<ChakraLink href="#" color="white" fontSize="sm" opacity={0.8} _hover={{ opacity: 1 }}>Pricing</ChakraLink>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 隐私政策弹窗 */}
|
||||
<PrivacyPolicyModal
|
||||
isOpen={isPrivacyModalOpen}
|
||||
onClose={onPrivacyModalClose}
|
||||
/>
|
||||
|
||||
{/* 用户协议弹窗 */}
|
||||
<UserAgreementModal
|
||||
isOpen={isUserAgreementModalOpen}
|
||||
onClose={onUserAgreementModalClose}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user