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

View File

@@ -1,24 +1,23 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Layout from './components/Layout';
import Hero from './components/Hero';
import Skills from './components/Skills';
import Projects from './components/Projects';
import Experience from './components/Experience';
import Contact from './components/Contact';
// 创建一个简单包装组件而不使用ChakraProvider
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div style={{ width: '100%', maxWidth: '100vw', overflow: 'hidden' }}>
<Layout>
<Hero />
<Skills />
<Experience />
<Projects />
<Contact />
</Layout>
</div>
);
}

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;

View File

@@ -0,0 +1,140 @@
import React from "react";
import {
Box,
Container,
Heading,
Text,
Stack,
Flex,
Circle,
} from "@chakra-ui/react";
import { motion } from "framer-motion";
interface ExperienceItemProps {
position: string;
company: string;
period: string;
description: string;
}
const ExperienceItem = ({ position, company, period, description }: ExperienceItemProps) => {
return (
<Box mb={8} position="relative">
<Flex align="center">
<Circle size="40px" bg="blue.500" color="white" mr={4} fontSize="sm" fontWeight="bold">
{period.split("-")[0]}
</Circle>
<Box>
<Heading as="h3" size="md">
{position}
</Heading>
<Text fontWeight="medium" color="blue.500">
{company}
</Text>
<Text fontSize="sm" color="gray.500" mt={1}>
{period}
</Text>
</Box>
</Flex>
<Box mt={4} ml="52px">
<Text color="gray.600">{description}</Text>
</Box>
</Box>
);
};
const Experience = () => {
const experiences = [
{
position: "高级Java架构师",
company: "某科技有限公司",
period: "2020 - 至今",
description:
"负责公司核心业务系统的架构设计和技术选型主导微服务架构重构和云原生改造带领团队完成多个高并发、高可用的分布式系统开发。优化系统性能解决系统扩展性问题将系统响应时间提升40%,支持每日千万级交易量。",
},
{
position: "大数据开发工程师",
company: "某大数据公司",
period: "2018 - 2020",
description:
"负责公司数据处理平台的开发和优化主要使用Hadoop、Spark和Flink技术栈设计实现了实时和离线数据处理流程开发数据ETL过程构建数据仓库为企业提供数据分析支持。成功将数据处理延迟从小时级降低到分钟级。",
},
{
position: "Java高级开发工程师",
company: "某互联网金融公司",
period: "2016 - 2018",
description:
"负责公司支付系统和风控系统的开发维护使用Spring Boot、Spring Cloud微服务技术栈设计实现高并发交易系统优化数据库访问性能开发分布式事务解决方案。系统日均处理交易50万笔峰值TPS达到3000。",
},
{
position: "Java开发工程师",
company: "某软件开发公司",
period: "2014 - 2016",
description:
"参与企业级应用系统开发负责后台服务和API开发使用Spring、MyBatis等框架实现业务逻辑和数据访问层编写单元测试和集成测试参与code review和性能优化工作。",
},
];
return (
<Box py={20} bg="gray.50" id="experience">
<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"
>
Java后台开发与大数据工程实践经验
</Text>
<Box
position="relative"
maxWidth="800px"
mx="auto"
px={{ base: 4, md: 0 }}
>
<Box
position="absolute"
left="19px"
top="40px"
bottom="20px"
width="2px"
bg="gray.200"
zIndex={1}
></Box>
<Stack gap={0} position="relative" zIndex={2}>
{experiences.map((exp, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
>
<ExperienceItem {...exp} />
</motion.div>
))}
</Stack>
</Box>
</motion.div>
</Container>
</Box>
);
};
export default Experience;

116
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,116 @@
import React from "react";
// 移除图标导入
// import { FaTwitter, FaYoutube, FaGithub, FaLinkedin } from "react-icons/fa";
// import { IconContext } from "react-icons";
const footerStyle = {
background: "#f7fafc",
color: "#4a5568",
marginTop: "40px"
};
const containerStyle = {
maxWidth: "1200px",
margin: "0 auto",
padding: "40px 16px"
};
const gridStyle = {
display: "grid",
gridTemplateColumns: "2fr 1fr 1fr 1fr",
gap: "32px"
};
const stackStyle = {
display: "flex",
flexDirection: "column" as const,
gap: "24px"
};
const headingStyle = {
color: "#3182ce",
fontSize: "16px",
fontWeight: "bold"
};
const textStyle = {
fontSize: "14px"
};
const socialIconsStyle = {
display: "flex",
flexDirection: "row" as const,
gap: "24px"
};
const iconStyle = {
color: "#718096",
fontSize: "20px",
width: "20px",
height: "20px"
};
export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer style={footerStyle}>
<div style={containerStyle}>
<div style={gridStyle}>
<div style={stackStyle}>
<div>
<h3 style={headingStyle}>
Java开发者
</h3>
</div>
<p style={textStyle}>
© {currentYear} Java开发者.
</p>
<div style={socialIconsStyle}>
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer">
<svg style={iconStyle} viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
</svg>
</a>
<a href="https://linkedin.com/in/yourusername" target="_blank" rel="noopener noreferrer">
<svg style={iconStyle} viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/>
</svg>
</a>
<a href="https://twitter.com/yourusername" target="_blank" rel="noopener noreferrer">
<svg style={iconStyle} viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/>
</svg>
</a>
<a href="https://youtube.com/c/yourusername" target="_blank" rel="noopener noreferrer">
<svg style={iconStyle} viewBox="0 0 576 512" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"/>
</svg>
</a>
</div>
</div>
<div style={stackStyle}>
<h4 style={headingStyle}></h4>
<a href="/" style={textStyle}></a>
<a href="#skills" style={textStyle}></a>
<a href="#experience" style={textStyle}></a>
<a href="#projects" style={textStyle}></a>
</div>
<div style={stackStyle}>
<h4 style={headingStyle}></h4>
<a href="/blog" style={textStyle}></a>
<a href="#contact" style={textStyle}></a>
<a href="/resources" style={textStyle}></a>
<a href="/books" style={textStyle}></a>
</div>
<div style={stackStyle}>
<h4 style={headingStyle}></h4>
<p style={textStyle}>邮箱: your.email@example.com</p>
<p style={textStyle}>: +86 138 xxxx xxxx</p>
<p style={textStyle}>地址: 中国·</p>
</div>
</div>
</div>
</footer>
);
}

158
src/components/Hero.tsx Normal file
View File

@@ -0,0 +1,158 @@
import React from "react";
import {
Box,
Button,
Container,
Flex,
Heading,
Image,
Stack,
Text,
Link,
} from "@chakra-ui/react";
import { Icon } from "@chakra-ui/icon";
import { TypeAnimation } from 'react-type-animation';
import { FaGithub, FaLinkedin } from 'react-icons/fa';
import { motion } from "framer-motion";
const Hero = () => {
// 由于useColorModeValue在当前版本可能不可用我们使用简单的方式定义颜色
const textColor = "blue.600";
const headingColor = "gray.800";
const descriptionColor = "gray.600";
const hoverBg = "gray.200";
return (
<Container maxW={"7xl"} id="hero">
<Stack
align={"center"}
py={{ base: 20, md: 28 }}
direction={{ base: "column", md: "row" }}
gap={{ base: 8, md: 10 }}
>
<Stack flex={1} gap={{ base: 5, md: 10 }}>
<Heading
lineHeight={1.1}
fontWeight={600}
fontSize={{ base: "3xl", sm: "4xl", lg: "6xl" }}
>
<Text
as={"span"}
position={"relative"}
color={textColor}
>
</Text>
<br />
<Text as={"span"} color={headingColor}>
Java后台与大数据专家
</Text>
</Heading>
<Box>
<TypeAnimation
sequence={[
'专注于分布式系统开发',
1000,
'精通Spring Boot & Cloud',
1000,
'Hadoop & Spark 数据处理',
1000,
'Kafka & Flink 流处理',
1000
]}
wrapper="span"
speed={50}
style={{ fontSize: '2em', display: 'inline-block', color: descriptionColor }}
repeat={Infinity}
/>
</Box>
<Text color={descriptionColor}>
Java后台开发工程师和大数据专家
</Text>
<Stack
gap={{ base: 4, sm: 6 }}
direction={{ base: "column", sm: "row" }}
>
<Button
onClick={() => document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' })}
rounded={"full"}
size={"lg"}
fontWeight={"normal"}
px={6}
colorScheme={"blue"}
>
</Button>
<Button
onClick={() => document.getElementById('projects')?.scrollIntoView({ behavior: 'smooth' })}
rounded={"full"}
size={"lg"}
fontWeight={"normal"}
px={6}
colorScheme="gray"
>
<Box mr={2}><Icon as={FaGithub as any} /></Box>
</Button>
</Stack>
<Stack direction={"row"} gap={6}>
<Link href="https://github.com/yourusername" target="_blank">
<Button
variant="ghost"
borderRadius="full"
size="lg"
_hover={{ bg: hoverBg }}
>
<Icon as={FaGithub as any} boxSize="28px" />
</Button>
</Link>
<Link href="https://linkedin.com/in/yourusername" target="_blank">
<Button
variant="ghost"
borderRadius="full"
size="lg"
_hover={{ bg: hoverBg }}
>
<Icon as={FaLinkedin as any} boxSize="28px" />
</Button>
</Link>
</Stack>
</Stack>
<Flex
flex={1}
justify={"center"}
align={"center"}
position={"relative"}
w={"full"}
>
<Box
position={"relative"}
height={"400px"}
rounded={"2xl"}
boxShadow={"2xl"}
width={"full"}
overflow={"hidden"}
>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<Image
alt={"Hero Image"}
fit={"cover"}
align={"center"}
w={"100%"}
h={"100%"}
src={"https://images.unsplash.com/photo-1555066931-4365d14bab8c?q=80&w=2070"}
/>
</motion.div>
</Box>
</Flex>
</Stack>
</Container>
);
};
export default Hero;

19
src/components/Layout.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from "react";
import Navbar from "./Navbar";
import Footer from "./Footer";
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps) {
return (
<div>
<Navbar />
<div style={{ minHeight: "100vh", paddingTop: "60px" }}>
{children}
</div>
<Footer />
</div>
);
}

241
src/components/Navbar.tsx Normal file
View File

@@ -0,0 +1,241 @@
import React, { useState, useEffect } from "react";
const navbarStyle = {
position: "fixed" as const,
top: 0,
width: "100%",
zIndex: 100,
background: "white",
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.1)",
borderBottom: "1px solid #eaeaea"
};
const containerStyle = {
maxWidth: "1200px",
margin: "0 auto",
padding: "12px 16px",
display: "flex",
alignItems: "center",
justifyContent: "space-between"
};
const logoStyle = {
fontWeight: "bold",
fontSize: "20px",
color: "#3182ce"
};
const navLinksStyle = {
display: "flex",
gap: "32px"
};
const linkStyle = {
color: "#4a5568",
fontWeight: 500,
textDecoration: "none",
padding: "8px 0",
position: "relative" as const,
transition: "color 0.3s ease"
};
const activeLinkStyle = {
...linkStyle,
color: "#3182ce",
};
const linkHoverStyle = {
color: "#3182ce",
};
const linkAfterStyle = {
content: '""',
position: "absolute" as const,
width: "0%",
height: "2px",
bottom: "0",
left: "50%",
backgroundColor: "#3182ce",
transition: "all 0.3s ease",
transform: "translateX(-50%)"
};
const activeLinkAfterStyle = {
...linkAfterStyle,
width: "100%"
};
const buttonStyle = {
padding: "8px 16px",
backgroundColor: "#3182ce",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
transition: "background-color 0.3s ease",
};
const buttonHoverStyle = {
backgroundColor: "#2c5282",
};
const mobileMenuStyle = {
display: "none",
"@media (max-width: 768px)": {
display: "block"
}
};
const Navbar = () => {
const [activeSection, setActiveSection] = useState("home");
const [hoveredLink, setHoveredLink] = useState<string | null>(null);
const [isButtonHovered, setIsButtonHovered] = useState(false);
// 监听滚动事件,更新当前活动的导航项
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY;
// 获取各个部分的位置
const homeSection = document.getElementById("home")?.offsetTop || 0;
const skillsSection = document.getElementById("skills")?.offsetTop || 0;
const experienceSection = document.getElementById("experience")?.offsetTop || 0;
const projectsSection = document.getElementById("projects")?.offsetTop || 0;
const contactSection = document.getElementById("contact")?.offsetTop || 0;
// 设置一个偏移量,使导航项在滚动到相应部分之前就高亮显示
const offset = 100;
// 根据滚动位置更新活动的导航项
if (scrollPosition < skillsSection - offset) {
setActiveSection("home");
} else if (scrollPosition < experienceSection - offset) {
setActiveSection("skills");
} else if (scrollPosition < projectsSection - offset) {
setActiveSection("experience");
} else if (scrollPosition < contactSection - offset) {
setActiveSection("projects");
} else {
setActiveSection("contact");
}
};
window.addEventListener("scroll", handleScroll);
// 组件卸载时移除事件监听
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
// 获取链接样式
const getLinkStyle = (section: string) => {
const isActive = activeSection === section;
const isHovered = hoveredLink === section;
return {
...(isActive ? activeLinkStyle : linkStyle),
...(isHovered ? linkHoverStyle : {}),
"&::after": isActive ? activeLinkAfterStyle : isHovered ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle
};
};
return (
<nav style={navbarStyle}>
<div style={containerStyle}>
<div style={logoStyle}>
Java开发者
</div>
<div style={navLinksStyle}>
<a
href="/"
id="home-link"
style={getLinkStyle("home")}
onMouseEnter={() => setHoveredLink("home")}
onMouseLeave={() => setHoveredLink(null)}
onClick={() => setActiveSection("home")}
>
<div style={activeSection === "home" ? activeLinkAfterStyle : hoveredLink === "home" ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle} />
</a>
<a
href="#skills"
id="skills-link"
style={getLinkStyle("skills")}
onMouseEnter={() => setHoveredLink("skills")}
onMouseLeave={() => setHoveredLink(null)}
onClick={() => setActiveSection("skills")}
>
<div style={activeSection === "skills" ? activeLinkAfterStyle : hoveredLink === "skills" ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle} />
</a>
<a
href="#experience"
id="experience-link"
style={getLinkStyle("experience")}
onMouseEnter={() => setHoveredLink("experience")}
onMouseLeave={() => setHoveredLink(null)}
onClick={() => setActiveSection("experience")}
>
<div style={activeSection === "experience" ? activeLinkAfterStyle : hoveredLink === "experience" ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle} />
</a>
<a
href="#projects"
id="projects-link"
style={getLinkStyle("projects")}
onMouseEnter={() => setHoveredLink("projects")}
onMouseLeave={() => setHoveredLink(null)}
onClick={() => setActiveSection("projects")}
>
<div style={activeSection === "projects" ? activeLinkAfterStyle : hoveredLink === "projects" ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle} />
</a>
<a
href="#contact"
id="contact-link"
style={getLinkStyle("contact")}
onMouseEnter={() => setHoveredLink("contact")}
onMouseLeave={() => setHoveredLink(null)}
onClick={() => setActiveSection("contact")}
>
<div style={activeSection === "contact" ? activeLinkAfterStyle : hoveredLink === "contact" ? { ...linkAfterStyle, width: "50%" } : linkAfterStyle} />
</a>
</div>
<div>
<button
style={{
...buttonStyle,
...(isButtonHovered ? buttonHoverStyle : {})
}}
onMouseEnter={() => setIsButtonHovered(true)}
onMouseLeave={() => setIsButtonHovered(false)}
onClick={() => {
const contactSection = document.getElementById('contact');
if (contactSection) {
contactSection.scrollIntoView({ behavior: 'smooth' });
setActiveSection("contact");
}
}}
>
</button>
</div>
<div style={mobileMenuStyle}>
<button
style={{ background: "transparent", border: "none" }}
onClick={() => {}}
>
</button>
</div>
</div>
</nav>
);
};
export default Navbar;

189
src/components/Projects.tsx Normal file
View File

@@ -0,0 +1,189 @@
import React from "react";
import {
Box,
Button,
Container,
Flex,
Heading,
Image,
SimpleGrid,
Stack,
Text,
Badge,
Link,
} from "@chakra-ui/react";
import { Icon } from "@chakra-ui/icon";
import { motion } from "framer-motion";
import { FaGithub, FaExternalLinkAlt } from "react-icons/fa";
interface ProjectCardProps {
title: string;
description: string;
image: string;
technologies: string[];
github: string | null;
demo: string | null;
}
const ProjectCard = ({ title, description, image, technologies, github, demo }: ProjectCardProps) => {
return (
<Box
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
boxShadow="lg"
bg="white"
_hover={{ transform: "translateY(-5px)", transition: "transform 0.3s ease" }}
>
<Image
src={image}
alt={title}
objectFit="cover"
h="200px"
w="100%"
/>
<Box p={6}>
<Stack gap={4}>
<Heading as="h3" size="md" color="blue.600">
{title}
</Heading>
<Text color="gray.600" fontSize="sm">
{description}
</Text>
<Flex flexWrap="wrap" gap={2}>
{technologies.map((tech, index) => (
<Badge key={index} colorScheme="blue" fontSize="0.8em" px={2} py={1} borderRadius="full">
{tech}
</Badge>
))}
</Flex>
<Flex gap={4}>
{github && (
<Link href={github} target="_blank">
<Button
size="sm"
colorScheme="gray"
variant="outline"
>
<Icon as={FaGithub as any} mr={2} />
</Button>
</Link>
)}
{demo && (
<Link href={demo} target="_blank">
<Button
size="sm"
colorScheme="blue"
>
<Icon as={FaExternalLinkAlt as any} mr={2} />
</Button>
</Link>
)}
</Flex>
</Stack>
</Box>
</Box>
);
};
const Projects = () => {
const projects = [
{
title: "分布式微服务电商平台",
description: "基于Spring Cloud构建的高可用电商系统包含用户、商品、订单、支付等微服务实现了服务注册发现、配置中心、负载均衡、熔断降级等功能。",
image: "https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?q=80&w=2070",
technologies: ["Java", "Spring Boot", "Spring Cloud", "MySQL", "Redis", "RabbitMQ"],
github: "#",
demo: "#",
},
{
title: "实时数据分析平台",
description: "基于Kafka和Flink构建的实时数据处理平台支持实时日志收集、指标计算、异常检测和业务监控应用于电商、金融等多个场景。",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070",
technologies: ["Java", "Flink", "Kafka", "Elasticsearch", "Kibana", "Prometheus"],
github: "#",
demo: "#",
},
{
title: "分布式数据仓库系统",
description: "构建基于Hadoop生态的数据仓库系统实现离线数据的ETL处理、存储和分析支持多维度数据分析和报表生成。",
image: "https://images.unsplash.com/photo-1504384308090-c894fdcc538d?q=80&w=2070",
technologies: ["Hadoop", "Hive", "Spark", "HBase", "Sqoop", "Airflow"],
github: "#",
demo: null,
},
{
title: "容器化微服务DevOps平台",
description: "搭建基于Kubernetes的微服务DevOps平台实现应用的自动构建、部署、监控和运维提高了开发和运维效率。",
image: "https://images.unsplash.com/photo-1667372393119-3d4c48d07fc9?q=80&w=2070",
technologies: ["Docker", "Kubernetes", "Jenkins", "Prometheus", "Grafana", "Istio"],
github: "#",
demo: null,
},
{
title: "分布式搜索引擎",
description: "基于Elasticsearch构建的分布式搜索系统支持全文检索、模糊匹配、数据聚合等功能应用于企业内部知识库和产品搜索。",
image: "https://images.unsplash.com/photo-1483058712412-4245e9b90334?q=80&w=2070",
technologies: ["Java", "Elasticsearch", "Spring Boot", "Redis", "MySQL", "Vue"],
github: "#",
demo: "#",
},
{
title: "机器学习推荐系统",
description: "基于Spark MLlib实现的个性化推荐系统通过协同过滤和内容特征分析为用户提供精准的产品和内容推荐。",
image: "https://images.unsplash.com/photo-1489549132488-d00b7eee80f1?q=80&w=2073",
technologies: ["Spark", "Python", "MLlib", "Kafka", "Spring Boot", "MySQL"],
github: "#",
demo: null,
},
];
return (
<Box py={20} bg="gray.50" id="projects">
<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>
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={8}>
{projects.map((project, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
>
<ProjectCard {...project} />
</motion.div>
))}
</SimpleGrid>
</motion.div>
</Container>
</Box>
);
};
export default Projects;

167
src/components/Skills.tsx Normal file
View File

@@ -0,0 +1,167 @@
import React from "react";
import {
Box,
Container,
Flex,
Heading,
SimpleGrid,
Text,
} from "@chakra-ui/react";
import { Icon } from "@chakra-ui/icon";
import { FaJava, FaDatabase, FaCloud, FaServer } from "react-icons/fa";
import { motion } from "framer-motion";
interface SkillData {
name: string;
level: number;
}
interface SkillCategoryProps {
title: string;
icon: React.ReactNode;
skills: SkillData[];
}
const SkillCategory = ({ title, icon, skills }: SkillCategoryProps) => {
const bgColor = "gray.50";
const borderColor = "gray.200";
const titleColor = "blue.600";
return (
<Box
borderWidth="1px"
borderRadius="lg"
borderColor={borderColor}
p={6}
bg={bgColor}
boxShadow="md"
_hover={{ transform: "translateY(-5px)", transition: "all 0.3s ease" }}
>
<Flex align="center" mb={4}>
{icon}
<Heading size="md" ml={2} color={titleColor}>
{title}
</Heading>
</Flex>
<Box bg="gray.200" h="1px" w="100%" mb={4}></Box>
{skills.map((skill, index) => (
<Box key={index} mb={4}>
<Flex justify="space-between" mb={1}>
<Text fontWeight="medium">{skill.name}</Text>
<Text fontWeight="medium">{skill.level}%</Text>
</Flex>
<Box
w="100%"
h="8px"
bg="gray.100"
borderRadius="full"
position="relative"
overflow="hidden"
>
<Box
position="absolute"
h="100%"
w={`${skill.level}%`}
bg="blue.500"
borderRadius="full"
/>
</Box>
</Box>
))}
</Box>
);
};
const Skills = () => {
const headingColor = "gray.700";
const textColor = "gray.600";
const javaSkills = [
{ name: "Java Core", level: 95 },
{ name: "Spring Boot", level: 90 },
{ name: "Spring Cloud", level: 85 },
{ name: "Microservices", level: 88 },
{ name: "JPA/Hibernate", level: 85 }
];
const databaseSkills = [
{ name: "MySQL", level: 90 },
{ name: "PostgreSQL", level: 85 },
{ name: "MongoDB", level: 80 },
{ name: "Redis", level: 88 },
{ name: "Oracle", level: 75 }
];
const bigDataSkills = [
{ name: "Hadoop", level: 85 },
{ name: "Spark", level: 80 },
{ name: "Kafka", level: 88 },
{ name: "Flink", level: 78 },
{ name: "Hive", level: 82 }
];
const cloudSkills = [
{ name: "Docker", level: 90 },
{ name: "Kubernetes", level: 82 },
{ name: "AWS", level: 78 },
{ name: "Azure", level: 75 },
{ name: "CI/CD", level: 85 }
];
return (
<Box py={20} id="skills">
<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={headingColor}
>
</Heading>
<Text
fontSize="xl"
textAlign="center"
mb={10}
color={textColor}
maxW="3xl"
mx="auto"
>
Java开发和大数据处理经验
</Text>
<SimpleGrid columns={{ base: 1, md: 2 }} gap={8}>
<SkillCategory
title="Java后台开发"
icon={<Icon as={FaJava as any} boxSize="24px" color="#5382a1" />}
skills={javaSkills}
/>
<SkillCategory
title="数据库技术"
icon={<Icon as={FaDatabase as any} boxSize="24px" color="#f29111" />}
skills={databaseSkills}
/>
<SkillCategory
title="大数据技术"
icon={<Icon as={FaServer as any} boxSize="24px" color="#e25a1c" />}
skills={bigDataSkills}
/>
<SkillCategory
title="云原生 & DevOps"
icon={<Icon as={FaCloud as any} boxSize="24px" color="#1c9be2" />}
skills={cloudSkills}
/>
</SimpleGrid>
</motion.div>
</Container>
</Box>
);
};
export default Skills;

View File

@@ -3,13 +3,26 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { CustomChakraProvider } from './theme/ChakraContext';
// 使用全局变量
declare global {
interface Window {
ChakraProvider: any;
}
}
// 移除未使用的变量
// const ChakraProvider = window.ChakraProvider || React.Fragment;
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
<CustomChakraProvider>
<App />
</CustomChakraProvider>
</React.StrictMode>
);

354
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,354 @@
import React from "react";
import {
Box,
Heading,
Container,
Text,
Flex,
Image,
VStack,
HStack,
Badge
} from "@chakra-ui/react";
import { Button } from "@chakra-ui/button";
import { Stack, SimpleGrid } from "@chakra-ui/layout";
import { Icon } from "@chakra-ui/icon";
import { useColorModeValue } from "@chakra-ui/color-mode";
import { createIcon } from "@chakra-ui/icons";
import { Link as RouterLink } from "react-router-dom";
import { TypeAnimation } from "react-type-animation";
import { FaJava, FaDatabase, FaCloud, FaGithub } from "react-icons/fa";
import { SiApachekafka, SiSpring, SiApachehadoop, SiApachespark } from "react-icons/si";
import type { IconType } from "react-icons";
export default function Home() {
return (
<Box>
{/* Hero Section */}
<Container maxW={"3xl"}>
<Stack
as={Box}
textAlign={"center"}
gap={{ base: 8, md: 14 }}
py={{ base: 20, md: 36 }}
>
<Heading
fontWeight={700}
fontSize={{ base: "2xl", sm: "4xl", md: "6xl" }}
lineHeight={"110%"}
>
<Text as={"span"} color={"brand.500"}>
Java全栈开发者
</Text>
<br />
<Text as={"span"} color={"accent.500"} fontSize={{ base: "xl", sm: "2xl", md: "4xl" }}>
<TypeAnimation
sequence={[
"专注于后台开发", 2000,
"热爱大数据技术", 2000,
"架构设计师", 2000,
"问题解决者", 2000
]}
wrapper="span"
cursor={true}
repeat={Infinity}
/>
</Text>
</Heading>
<Text color={"gray.500"} fontSize={{ base: "lg", md: "xl" }}>
Java开发者
</Text>
<Stack
direction={"column"}
gap={3}
align={"center"}
alignSelf={"center"}
position={"relative"}
>
<Button
colorScheme={"brand"}
bg={"brand.500"}
rounded={"full"}
px={6}
_hover={{
bg: "brand.600",
}}
onClick={() => window.location.href = '/projects'}
>
</Button>
<Button
variant={"outline"}
colorScheme={"brand"}
rounded={"full"}
px={6}
onClick={() => window.location.href = '/contact'}
>
</Button>
<Box>
<Icon
as={Arrow}
color={useColorModeValue("gray.800", "gray.300")}
w={71}
position={"absolute"}
right={-71}
top={"10px"}
/>
<Text
fontSize={"lg"}
fontFamily={"Caveat"}
position={"absolute"}
right={"-125px"}
top={"-15px"}
transform={"rotate(10deg)"}
>
</Text>
</Box>
</Stack>
</Stack>
</Container>
{/* Skills Overview */}
<Box bg={useColorModeValue("gray.50", "gray.900")} py={20}>
<Container maxW={"6xl"}>
<Heading
textAlign={"center"}
fontSize={"4xl"}
py={10}
fontWeight={"bold"}
>
</Heading>
<SimpleGrid columns={{ base: 2, md: 4 }} gap={10}>
<SkillCard
icon={FaJava}
title="Java开发"
description="精通Java核心技术Spring生态系统和JVM优化"
/>
<SkillCard
icon={SiApachekafka}
title="消息队列"
description="Kafka、RabbitMQ等消息中间件的设计和优化"
/>
<SkillCard
icon={FaDatabase}
title="数据库"
description="关系型数据库与NoSQL数据库的设计和优化"
/>
<SkillCard
icon={SiApachehadoop}
title="大数据技术"
description="Hadoop生态系统包括HDFS、MapReduce等"
/>
<SkillCard
icon={SiApachespark}
title="Spark"
description="使用Spark进行大规模数据处理和分析"
/>
<SkillCard
icon={SiSpring}
title="Spring生态"
description="Spring Boot、Spring Cloud微服务架构"
/>
<SkillCard
icon={FaCloud}
title="云原生"
description="Docker、Kubernetes容器化技术与部署"
/>
<SkillCard
icon={FaGithub}
title="版本控制"
description="Git、Maven、Gradle等开发工具"
/>
</SimpleGrid>
</Container>
</Box>
{/* Featured Projects */}
<Box py={20}>
<Container maxW={"6xl"}>
<Heading
textAlign={"center"}
fontSize={"4xl"}
py={10}
fontWeight={"bold"}
>
</Heading>
<SimpleGrid columns={{ base: 1, md: 2 }} gap={10}>
<ProjectCard
title="分布式电商系统"
description="基于Spring Cloud的微服务架构实现了高可用、高并发的电商平台。"
tags={["Spring Cloud", "微服务", "MySQL", "Redis"]}
image="https://images.unsplash.com/photo-1556742502-ec7c0e9f34b1?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
/>
<ProjectCard
title="大数据分析平台"
description="基于Hadoop、Spark的大数据处理平台实现了实时数据分析和可视化。"
tags={["Spark", "Hadoop", "Kafka", "ElasticSearch"]}
image="https://images.unsplash.com/photo-1560807707-8cc77767d783?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
/>
</SimpleGrid>
<Box textAlign="center" mt={10}>
<Button
colorScheme={"brand"}
bg={"brand.500"}
rounded={"md"}
px={6}
_hover={{
bg: "brand.600",
}}
onClick={() => window.location.href = '/projects'}
>
</Button>
</Box>
</Container>
</Box>
{/* Call to Action */}
<Box bg={useColorModeValue("brand.500", "brand.600")} py={20}>
<Container maxW={"3xl"}>
<Stack
as={Box}
textAlign={"center"}
gap={{ base: 8, md: 14 }}
>
<Heading
fontWeight={700}
fontSize={{ base: "2xl", sm: "4xl", md: "5xl" }}
lineHeight={"110%"}
color={"white"}
>
</Heading>
<Text color={"white"} fontSize={{ base: "lg", md: "xl" }}>
</Text>
<Stack
direction={"column"}
gap={3}
align={"center"}
alignSelf={"center"}
position={"relative"}
>
<Button
colorScheme={"whiteAlpha"}
bg={"whiteAlpha.800"}
rounded={"full"}
px={6}
_hover={{
bg: "whiteAlpha.900",
}}
color={"brand.500"}
fontWeight="bold"
onClick={() => window.location.href = '/contact'}
>
</Button>
</Stack>
</Stack>
</Container>
</Box>
</Box>
);
}
// Components
const SkillCard = ({ icon, title, description }: { icon: IconType, title: string, description: string }) => {
return (
<VStack
align={"center"}
bg={useColorModeValue("white", "gray.800")}
p={5}
boxShadow={"md"}
rounded={"lg"}
transition={"transform 0.3s ease"}
_hover={{
transform: "translateY(-5px)",
boxShadow: "lg",
}}
>
<Box
p={2}
rounded={"full"}
bg={useColorModeValue("brand.50", "brand.900")}
color={"brand.500"}
mb={4}
>
<Icon as={icon as any} w={10} h={10} />
</Box>
<Heading size={"md"} mb={2}>
{title}
</Heading>
<Text textAlign={"center"} color={"gray.500"}>
{description}
</Text>
</VStack>
);
};
const ProjectCard = ({ title, description, tags, image }: {
title: string,
description: string,
tags: string[],
image: string
}) => {
return (
<Box
bg={useColorModeValue("white", "gray.800")}
p={0}
rounded={"lg"}
overflow={"hidden"}
boxShadow={"md"}
transition={"transform 0.3s ease"}
_hover={{
transform: "translateY(-5px)",
boxShadow: "lg",
}}
>
<Image
src={image}
alt={title}
objectFit={"cover"}
w={"100%"}
h={"200px"}
/>
<VStack p={5} align={"start"} gap={3}>
<Heading size={"md"}>{title}</Heading>
<Text color={"gray.500"}>{description}</Text>
<HStack gap={2}>
{tags.map((tag) => (
<Badge
key={tag}
colorScheme={"brand"}
variant={"subtle"}
px={2}
py={1}
rounded={"md"}
>
{tag}
</Badge>
))}
</HStack>
</VStack>
</Box>
);
};
const Arrow = createIcon({
displayName: "Arrow",
viewBox: "0 0 72 24",
path: (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.600904 7.08166C0.764293 6.8879 1.01492 6.79004 1.26654 6.82177C2.83216 7.01918 5.20326 7.24581 7.54543 7.23964C9.92491 7.23338 12.1351 6.98464 13.4704 6.32142C13.84 6.13785 14.2885 6.28805 14.4722 6.65692C14.6559 7.02578 14.5052 7.47362 14.1356 7.6572C12.4625 8.48822 9.94063 8.72541 7.54852 8.7317C5.67514 8.73663 3.79547 8.5985 2.29921 8.44247C2.80955 9.59638 3.50943 10.6396 4.24665 11.7384C4.39435 11.9585 4.54354 12.1809 4.69301 12.4068C5.79543 14.0733 6.88128 15.8995 7.1179 18.2636C7.15893 18.6735 6.85928 19.0393 6.4486 19.0805C6.03792 19.1217 5.67174 18.8227 5.6307 18.4128C5.43271 16.4346 4.52957 14.868 3.4457 13.2296C3.3058 13.0181 3.16221 12.8046 3.01684 12.5885C2.05899 11.1646 1.02372 9.62564 0.457909 7.78069C0.383671 7.53862 0.437515 7.27541 0.600904 7.08166ZM5.52039 10.2248C5.77662 9.90161 6.24663 9.84687 6.57018 10.1025C16.4834 17.9344 29.9158 22.4064 42.0781 21.4773C54.1988 20.5514 65.0339 14.2748 69.9746 0.584299C70.1145 0.196597 70.5427 -0.0046455 70.931 0.134813C71.3193 0.274276 71.5206 0.70162 71.3807 1.08932C66.2105 15.4159 54.8056 22.0014 42.1913 22.965C29.6185 23.9254 15.8207 19.3142 5.64226 11.2727C5.31871 11.0171 5.26415 10.5479 5.52039 10.2248Z"
fill="currentColor"
/>
)
});

View File

@@ -0,0 +1,15 @@
import React, { ReactNode } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import theme from './theme';
interface ChakraProviderProps {
children: ReactNode;
}
export const CustomChakraProvider: React.FC<ChakraProviderProps> = ({ children }) => {
return (
<ChakraProvider theme={theme}>
{children}
</ChakraProvider>
);
};

20
src/theme/theme.ts Normal file
View File

@@ -0,0 +1,20 @@
import { ThemeConfig, extendTheme } from '@chakra-ui/react';
// 创建一个自定义主题
const config: ThemeConfig = {
initialColorMode: 'light',
useSystemColorMode: false,
};
const theme = extendTheme({
config,
colors: {
brand: {
900: "#1a365d",
800: "#153e75",
700: "#2a69ac",
},
},
});
export default theme;