311 lines
9.0 KiB
TypeScript
311 lines
9.0 KiB
TypeScript
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;
|