This commit is contained in:
2025-02-27 17:10:33 +08:00
parent d3a3e1918c
commit d61028a104
20 changed files with 4274 additions and 96 deletions

311
src/components/Contact.tsx Normal file
View File

@@ -0,0 +1,311 @@
import React, { useState } from "react";
import {
Box,
Button,
Container,
Flex,
Heading,
Input,
Stack,
Text,
Textarea,
useToast,
} from "@chakra-ui/react";
import { FaEnvelope, FaPhone, FaMapMarkerAlt } from "react-icons/fa";
import { motion } from "framer-motion";
import axios from "axios";
interface ContactInfoProps {
icon: React.ReactNode;
title: string;
content: string;
}
const ContactInfo = ({ icon, title, content }: ContactInfoProps) => {
return (
<Flex align="center" mb={4}>
<Box color="blue.500" mr={4}>
{icon}
</Box>
<Box>
<Text fontWeight="bold">{title}</Text>
<Text color="gray.600">{content}</Text>
</Box>
</Flex>
);
};
const Contact = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [subject, setSubject] = useState("");
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [cooldown, setCooldown] = useState(0);
const toast = useToast();
const API_URL = process.env.REACT_APP_API_URL || "http://localhost:3001";
// 倒计时函数
React.useEffect(() => {
let timer: NodeJS.Timeout | null = null;
if (cooldown > 0) {
timer = setInterval(() => {
setCooldown((prev) => {
if (prev <= 1) {
if (timer) clearInterval(timer);
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => {
if (timer) clearInterval(timer);
};
}, [cooldown]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// 如果在冷却时间内,不允许提交
if (cooldown > 0) {
toast({
title: "请求过于频繁",
description: `请在${cooldown}秒后再试`,
status: "warning",
duration: 3000,
isClosable: true,
position: "top"
});
return;
}
setLoading(true);
setError("");
try {
// 调用后端API发送邮件
const response = await axios.post(`${API_URL}/api/send-email`, {
name,
email,
subject,
message
});
// 显示成功消息
toast({
title: "发送成功",
description: "消息已发送,感谢您的留言,我会尽快回复您!",
status: "success",
duration: 5000,
isClosable: true,
position: "top"
});
// 重置表单
setName("");
setEmail("");
setSubject("");
setMessage("");
} catch (err: any) {
console.error("发送邮件失败:", err);
// 处理速率限制错误
if (err.response?.status === 429) {
const remainingTime = err.response.data.remainingTime || 60;
setCooldown(remainingTime);
toast({
title: "请求过于频繁",
description: `请在${remainingTime}秒后再试`,
status: "warning",
duration: 5000,
isClosable: true,
position: "top"
});
} else {
// 显示其他错误消息
setError(
err.response?.data?.error ||
"发送邮件失败,请稍后再试或直接通过电话联系我"
);
toast({
title: "发送失败",
description: err.response?.data?.error || "发送邮件失败,请稍后再试",
status: "error",
duration: 5000,
isClosable: true,
position: "top"
});
}
} finally {
setLoading(false);
}
};
return (
<Box py={20} id="contact">
<Container maxW="7xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Heading
mb={2}
fontSize={{ base: "3xl", md: "4xl" }}
fontWeight="bold"
textAlign="center"
color="gray.700"
>
</Heading>
<Text
fontSize="xl"
textAlign="center"
mb={10}
color="gray.600"
maxW="3xl"
mx="auto"
>
</Text>
<Flex
direction={{ base: "column", md: "row" }}
gap={8}
bg="white"
borderRadius="lg"
overflow="hidden"
boxShadow="lg"
>
<Box
flex={1}
bg="blue.600"
color="white"
p={{ base: 6, md: 8 }}
position="relative"
overflow="hidden"
>
<Box position="relative" zIndex={2}>
<Heading as="h3" size="lg" mb={6}>
</Heading>
<Stack gap={6}>
<ContactInfo
icon={FaEnvelope({ size: 20 })}
title="邮箱"
content="aisi_verify@163.com"
/>
<ContactInfo
icon={FaPhone({ size: 20 })}
title="电话"
content="+86 185 3901 7628"
/>
<ContactInfo
icon={FaMapMarkerAlt({ size: 20 })}
title="地址"
content="北京市海淀区中关村科技园"
/>
</Stack>
<Text mt={10}>
Java后台开发和大数据项目进行深入交流
</Text>
</Box>
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
bg="blue.700"
opacity={0.5}
zIndex={1}
transform="rotate(-5deg) scale(1.2) translateY(10%)"
/>
</Box>
<Box flex={1.5} p={{ base: 6, md: 8 }}>
<form onSubmit={handleSubmit}>
<Stack gap={4}>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="请输入您的姓名"
borderColor="gray.300"
required
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="请输入您的邮箱"
borderColor="gray.300"
required
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}></Text>
<Input
type="text"
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="请输入主题"
borderColor="gray.300"
/>
</Box>
<Box>
<Text fontWeight="bold" mb={1}> *</Text>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="请输入您的留言内容"
borderColor="gray.300"
height="150px"
required
/>
</Box>
{error && (
<Text color="red.500" fontSize="sm">
{error}
</Text>
)}
{cooldown > 0 && (
<Text color="orange.500" fontSize="sm">
{cooldown}
</Text>
)}
<Button
mt={4}
colorScheme="blue"
type="submit"
isLoading={loading}
loadingText="发送中..."
size="lg"
fontWeight="normal"
w={{ base: "full", md: "auto" }}
isDisabled={cooldown > 0}
>
{cooldown > 0 ? `请等待 ${cooldown}` : (loading ? "发送中..." : "发送消息")}
</Button>
</Stack>
</form>
</Box>
</Flex>
</motion.div>
</Container>
</Box>
);
};
export default Contact;