init
This commit is contained in:
311
src/components/Contact.tsx
Normal file
311
src/components/Contact.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user