feat: 精简首页 - 删除作品区域及多余社交链接
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-252
@@ -1,176 +1,8 @@
|
||||
import { useRef, useState, useCallback, useEffect } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowUpRight, FileText } from "lucide-react";
|
||||
import { FileText } from "lucide-react";
|
||||
import Navbar from "@/components/Navbar";
|
||||
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
title: "ServerQA 自动化测试平台",
|
||||
desc: "服务器领域的自动化测试框架,支持接口与性能测试",
|
||||
tags: ["Python", "Pytest", "Locust"],
|
||||
image: "/images/project-1.jpg",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "PerfMon 性能监控中心",
|
||||
desc: "服务器性能实时监控与历史趋势分析仪表盘",
|
||||
tags: ["Grafana", "Prometheus", "Go"],
|
||||
image: "/images/project-2.jpg",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "VibeCI 测试流水线",
|
||||
desc: "基于 Vibe Coding 理念的测试持续集成工具",
|
||||
tags: ["Node.js", "Docker", "GitHub Actions"],
|
||||
image: "/images/project-3.jpg",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "FaultSim 故障模拟器",
|
||||
desc: "分布式系统的混沌工程测试与故障注入工具",
|
||||
tags: ["Python", "Kubernetes", "gRPC"],
|
||||
image: "/images/project-4.jpg",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "LogLens 日志分析工具",
|
||||
desc: "大规模日志的智能聚合、搜索与异常检测平台",
|
||||
tags: ["ELK", "React", "ClickHouse"],
|
||||
image: "/images/project-5.jpg",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "TestHub 用例管理平台",
|
||||
desc: "测试用例的编写、评审、执行与覆盖率追踪系统",
|
||||
tags: ["Next.js", "PostgreSQL", "tRPC"],
|
||||
image: "/images/project-6.jpg",
|
||||
},
|
||||
];
|
||||
|
||||
function WorkGrid() {
|
||||
const containerRef = useRef<HTMLUListElement>(null);
|
||||
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
|
||||
const [mouseX, setMouseX] = useState(0);
|
||||
const [mouseY, setMouseY] = useState(0);
|
||||
const [containerX, setContainerX] = useState(200);
|
||||
const [containerY, setContainerY] = useState(200);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
||||
const calculateDistance = useCallback(
|
||||
(node: HTMLLIElement) => {
|
||||
if (!containerX || !containerY) return 0;
|
||||
const rect = node.getBoundingClientRect();
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(rect.left + rect.width / 2 - containerX, 2) +
|
||||
Math.pow(rect.top + rect.height / 2 - containerY, 2)
|
||||
);
|
||||
return Math.round(distance * 100) / 100;
|
||||
},
|
||||
[containerX, containerY]
|
||||
);
|
||||
|
||||
const handleMouseMove = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
setMouseX(e.clientX - rect.left);
|
||||
setMouseY(e.clientY - rect.top);
|
||||
|
||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
itemRefs.current.forEach((node) => {
|
||||
if (node) {
|
||||
const dist = calculateDistance(node);
|
||||
node.style.setProperty("--box-distance", `${dist}px`);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
[calculateDistance]
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (!containerRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
setContainerX(rect.width / 2);
|
||||
setContainerY(rect.height / 2);
|
||||
setIsHovering(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsHovering(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ul
|
||||
ref={containerRef}
|
||||
className="work-grid"
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={
|
||||
{
|
||||
"--x": `${mouseX}px`,
|
||||
"--y": `${mouseY}px`,
|
||||
"--is-hovering": isHovering ? 1 : 0,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{projects.map((project, index) => (
|
||||
<li
|
||||
key={project.id}
|
||||
ref={(el) => { itemRefs.current[index] = el; }}
|
||||
className="grid__item"
|
||||
style={{
|
||||
opacity: 0,
|
||||
animation: `fadeInItem 0.6s cubic-bezier(0.19, 1, 0.22, 1) ${index * 0.1}s forwards`,
|
||||
}}
|
||||
>
|
||||
<div className="image-reveal-container" style={{ background: "#f7f6f3" }}>
|
||||
<div className="aspect-[3/2] overflow-hidden">
|
||||
<img
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<h3 className="text-[#141414] font-semibold text-lg mb-1">{project.title}</h3>
|
||||
<p className="text-[#5a5a5a] text-sm leading-relaxed mb-3">{project.desc}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-xs px-2.5 py-1 rounded-full bg-[#e8e6e1] text-[#5a5a5a] font-medium"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="item__content-overlay" />
|
||||
<div className="reveal-frame top-left" style={{ top: 0, left: 0 }} />
|
||||
<div className="reveal-frame top-right" style={{ top: 0, right: 0 }} />
|
||||
<div className="reveal-frame right-top" style={{ top: 0, right: 0 }} />
|
||||
<div className="reveal-frame right-bottom" style={{ bottom: 0, right: 0 }} />
|
||||
<div className="reveal-frame bottom-right" style={{ bottom: 0, right: 0 }} />
|
||||
<div className="reveal-frame bottom-left" style={{ bottom: 0, left: 0 }} />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: "#e8e6e1" }}>
|
||||
@@ -224,14 +56,6 @@ export default function Home() {
|
||||
transition={{ duration: 0.8, delay: 0.3, ease: [0.19, 1, 0.22, 1] }}
|
||||
className="flex flex-wrap gap-4"
|
||||
>
|
||||
<a
|
||||
href="#works"
|
||||
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-2xl text-white font-medium text-base transition-all duration-300 hover:shadow-lg hover:translate-y-[-2px]"
|
||||
style={{ background: "#4a6cf7" }}
|
||||
>
|
||||
查看作品
|
||||
<ArrowUpRight size={18} />
|
||||
</a>
|
||||
<Link
|
||||
to="/blog"
|
||||
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-2xl font-medium text-base transition-all duration-300 hover:shadow-md hover:translate-y-[-2px]"
|
||||
@@ -255,7 +79,7 @@ export default function Home() {
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{/* Bilibili */}
|
||||
<a
|
||||
href="https://space.bilibili.com/"
|
||||
href="https://space.bilibili.com/297925716?spm_id_from=333.1007.0.0"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 hover:shadow-md"
|
||||
@@ -270,7 +94,7 @@ export default function Home() {
|
||||
|
||||
{/* CSDN */}
|
||||
<a
|
||||
href="https://blog.csdn.net/"
|
||||
href="https://blog.csdn.net/weixin_45839854?spm=1000.2115.3001.5343"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 hover:shadow-md"
|
||||
@@ -283,80 +107,13 @@ export default function Home() {
|
||||
<span>CSDN</span>
|
||||
</a>
|
||||
|
||||
{/* GitHub */}
|
||||
<a
|
||||
href="https://github.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 hover:shadow-md"
|
||||
style={{ background: "#f7f6f3", color: "#5a5a5a" }}
|
||||
title="GitHub"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
|
||||
{/* Juejin 掘金 */}
|
||||
<a
|
||||
href="https://juejin.cn/user/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 hover:shadow-md"
|
||||
style={{ background: "#f7f6f3", color: "#5a5a5a" }}
|
||||
title="稀土掘金"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M4.2 4h15.6c.6 0 1 .4 1 1v1.3c0 .6-.4 1-1 1H4.2c-.6 0-1-.4-1-1V5c0-.6.4-1 1-1zm0 4.7h10.4c.6 0 1 .4 1 1v1.3c0 .6-.4 1-1 1H4.2c-.6 0-1-.4-1-1v-1.3c0-.6.4-1 1-1zm0 4.6h15.6c.6 0 1 .4 1 1v1.3c0 .6-.4 1-1 1H4.2c-.6 0-1-.4-1-1v-1.3c0-.6.4-1 1-1zm0 4.7h10.4c.6 0 1 .4 1 1V20c0 .6-.4 1-1 1H4.2c-.6 0-1-.4-1-1v-1c0-.6.4-1 1-1z"/>
|
||||
</svg>
|
||||
<span>稀土掘金</span>
|
||||
</a>
|
||||
|
||||
{/* Zhihu 知乎 */}
|
||||
<a
|
||||
href="https://www.zhihu.com/people/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 hover:shadow-md"
|
||||
style={{ background: "#f7f6f3", color: "#5a5a5a" }}
|
||||
title="知乎"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5.721 0C2.251 0 0 2.25 0 5.719V18.28C0 21.751 2.252 24 5.721 24h12.56C21.751 24 24 21.75 24 18.281V5.72C24 2.249 21.75 0 18.281 0zm1.964 4.078c-.271.73-.5 1.434-.68 2.11h4.587c.545-.006.445 1.168.445 1.168H6.283a58.104 58.104 0 0 1-.037 3.32h3.342c.524.006.59 1.22.59 1.22H6.183c.04.8.228 2.025.455 3.07.215.977.475 1.74.717 2.087.24.346.414.31.6.27.184-.04.56-.346.84-1.028.28-.683.397-1.565.397-1.565h1.34s-.06.966-.373 2.032c-.315 1.066-.93 2.468-2.37 2.91-1.44.442-2.453-.224-2.916-.85-.462-.626-.78-2.026-.97-3.18-.19-1.155-.255-2.36-.255-3.165H2.26c.006-.55.12-1.224.12-1.22h1.88V6.186H3.47c-.12 0-.164-.82-.164-1.108h4.38zm8.137.004h1.56l2.14 8.39 2.17-8.39h1.49l-3.16 10.868h-1.15l-3.05-10.868zm-2.76 0h1.39v10.868h-1.39V4.082z"/>
|
||||
</svg>
|
||||
<span>知乎</span>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Works Section */}
|
||||
<section id="works" className="py-24 md:py-32" style={{ background: "#e8e6e1" }}>
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8, ease: [0.19, 1, 0.22, 1] }}
|
||||
className="mb-16"
|
||||
>
|
||||
<h2
|
||||
className="font-semibold mb-4"
|
||||
style={{ fontSize: "clamp(32px, 4vw, 48px)", lineHeight: 1.1, color: "#141414" }}
|
||||
>
|
||||
精选作品
|
||||
</h2>
|
||||
<p className="text-[#5a5a5a] text-lg max-w-xl">
|
||||
过去几年里,我参与和主导的一些代表性项目。
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<WorkGrid />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12" style={{ background: "#e8e6e1" }}>
|
||||
<div className="max-w-7xl mx-auto px-6 text-center">
|
||||
@@ -375,12 +132,6 @@ export default function Home() {
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>{`
|
||||
@keyframes fadeInItem {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user