feat: setup frontend project with bun and dynamic data fetching

- Create new frontend project directory with Next.js 15 + React 19
- Migrate from pnpm to bun for faster package management
- Add TanStack Query + Axios for dynamic data fetching
- Create comprehensive Makefile with development commands
- Setup API layer with query hooks and error handling
- Configure environment variables and bun settings
- Add TypeScript type checking and project documentation
- Update CLAUDE.md with bun-specific development workflow
This commit is contained in:
xujiang
2025-07-08 15:28:26 +08:00
parent d06caee35a
commit 3d197eb7e3
87 changed files with 8329 additions and 0 deletions

View File

@ -0,0 +1,248 @@
"use client"
import Image from "next/image"
import { Card, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Camera, Award, MapPin, Calendar, Heart, Lightbulb, Target, Users } from "lucide-react"
interface AboutViewProps {
onContactClick?: () => void
}
export function AboutView({ onContactClick }: AboutViewProps) {
const achievements = [
{
year: "2024",
title: "国际摄影大赛金奖",
description: "风光摄影类别获得金奖",
icon: Award,
},
{
year: "2023",
title: "个人摄影展",
description: "在北京798艺术区举办个人摄影展",
icon: Camera,
},
{
year: "2022",
title: "摄影师认证",
description: "获得国际摄影师协会认证",
icon: Badge,
},
{
year: "2021",
title: "开始职业摄影",
description: "正式成为职业摄影师",
icon: Calendar,
},
]
const skills = [
{ name: "风光摄影", level: 95 },
{ name: "人像摄影", level: 90 },
{ name: "街头摄影", level: 85 },
{ name: "建筑摄影", level: 88 },
{ name: "后期处理", level: 92 },
{ name: "商业摄影", level: 80 },
]
const equipment = [
{
category: "相机机身",
items: ["Canon EOS R5", "Sony A7R IV", "Nikon D850", "Fujifilm X-T4"],
},
{
category: "镜头",
items: ["24-70mm f/2.8", "70-200mm f/4", "85mm f/1.4", "16-35mm f/2.8", "90mm f/2.8 Macro"],
},
{
category: "配件",
items: ["三脚架", "滤镜系统", "闪光灯", "反光板", "无人机"],
},
]
return (
<div className="max-w-6xl mx-auto space-y-16">
{/* Hero Section */}
<div className="text-center mb-16">
<h1 className="text-4xl md:text-6xl font-light text-gray-900 mb-4"></h1>
<p className="text-lg text-gray-600 max-w-2xl mx-auto"></p>
</div>
{/* Personal Introduction */}
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="space-y-6">
<div>
<h2 className="text-3xl font-light text-gray-900 mb-4"></h2>
<p className="text-gray-600 leading-relaxed mb-4">
8
</p>
<p className="text-gray-600 leading-relaxed mb-4">
</p>
<p className="text-gray-600 leading-relaxed">
</p>
</div>
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2 text-gray-600">
<MapPin className="h-4 w-4" />
<span></span>
</div>
<div className="flex items-center gap-2 text-gray-600">
<Calendar className="h-4 w-4" />
<span>8</span>
</div>
<div className="flex items-center gap-2 text-gray-600">
<Camera className="h-4 w-4" />
<span></span>
</div>
</div>
</div>
<div className="relative">
<div className="aspect-[3/4] relative overflow-hidden rounded-lg">
<Image src="/placeholder.svg?height=600&width=450" alt="摄影师张明华" fill className="object-cover" />
</div>
</div>
</div>
{/* Photography Philosophy */}
<div className="bg-gray-50 rounded-2xl p-8 md:p-12">
<div className="text-center mb-12">
<h2 className="text-3xl font-light text-gray-900 mb-4"></h2>
<p className="text-gray-600 max-w-3xl mx-auto"></p>
</div>
<div className="grid md:grid-cols-3 gap-8">
<div className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Heart className="h-8 w-8 text-blue-600" />
</div>
<h3 className="text-xl font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 leading-relaxed">
</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Lightbulb className="h-8 w-8 text-green-600" />
</div>
<h3 className="text-xl font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 leading-relaxed">
</p>
</div>
<div className="text-center">
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Target className="h-8 w-8 text-purple-600" />
</div>
<h3 className="text-xl font-medium text-gray-900 mb-3"></h3>
<p className="text-gray-600 leading-relaxed">
</p>
</div>
</div>
</div>
{/* Skills */}
<div>
<h2 className="text-3xl font-light text-gray-900 mb-8 text-center"></h2>
<div className="grid md:grid-cols-2 gap-6">
{skills.map((skill) => (
<div key={skill.name} className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-gray-900 font-medium">{skill.name}</span>
<span className="text-gray-600 text-sm">{skill.level}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-gray-900 h-2 rounded-full transition-all duration-1000"
style={{ width: `${skill.level}%` }}
></div>
</div>
</div>
))}
</div>
</div>
{/* Achievements Timeline */}
<div>
<h2 className="text-3xl font-light text-gray-900 mb-12 text-center"></h2>
<div className="relative">
<div className="absolute left-8 md:left-1/2 top-0 bottom-0 w-0.5 bg-gray-200 transform md:-translate-x-0.5"></div>
<div className="space-y-8">
{achievements.map((achievement, index) => (
<div
key={achievement.year}
className={`relative flex items-center ${index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse"}`}
>
<div className="absolute left-8 md:left-1/2 w-4 h-4 bg-gray-900 rounded-full transform -translate-x-1/2 z-10"></div>
<div
className={`w-full md:w-5/12 ml-16 md:ml-0 ${
index % 2 === 0 ? "md:mr-auto md:pr-8" : "md:ml-auto md:pl-8"
}`}
>
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0">
<achievement.icon className="h-6 w-6 text-gray-600" />
</div>
<div>
<div className="text-sm text-gray-500 mb-1">{achievement.year}</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">{achievement.title}</h3>
<p className="text-gray-600 text-sm">{achievement.description}</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
))}
</div>
</div>
</div>
{/* Equipment */}
<div>
<h2 className="text-3xl font-light text-gray-900 mb-8 text-center"></h2>
<div className="grid md:grid-cols-3 gap-8">
{equipment.map((category) => (
<Card key={category.category} className="border-0 shadow-sm">
<CardContent className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">{category.category}</h3>
<div className="space-y-2">
{category.items.map((item) => (
<div key={item} className="flex items-center gap-2">
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
<span className="text-gray-600 text-sm">{item}</span>
</div>
))}
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Call to Action */}
<div className="bg-gray-900 text-white rounded-2xl p-8 md:p-12 text-center">
<Users className="h-12 w-12 mx-auto mb-6 text-gray-300" />
<h2 className="text-3xl font-light mb-4"></h2>
<p className="text-gray-300 mb-8 max-w-2xl mx-auto">
</p>
<Button onClick={onContactClick} className="bg-white text-gray-900 hover:bg-gray-100">
</Button>
</div>
</div>
)
}