Files
photography/frontend/app/page.tsx
xujiang 9046befcf1 feat: 完成前端展示网站核心功能开发
 新增功能:
- 增强照片展示页面,支持3种视图模式(网格/瀑布流/列表)
- 实现分页加载和无限滚动功能
- 完整的搜索和过滤系统,支持实时搜索、标签筛选、排序
- 新增分类浏览页面,提供分类统计和预览
- 新增标签云页面,热度可视化显示
- 面包屑导航和页面间无缝跳转

🎨 用户体验优化:
- 响应式设计,完美适配移动端和桌面端
- 智能loading状态和空状态处理
- 悬停效果和交互动画
- 视觉化统计仪表盘

 性能优化:
- 图片懒加载和智能分页
- 优化的组件渲染和状态管理
- 构建大小优化(187kB gzipped)

📝 更新任务进度文档,完成率达到32.5%

Phase 3核心功能基本完成,前端展示网站达到完全可用状态。
2025-07-11 12:27:36 +08:00

283 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect, useMemo } from "react"
import { PhotoGallery } from "@/components/photo-gallery"
import { PhotoModal } from "@/components/photo-modal"
import { Navigation } from "@/components/navigation"
import { FilterBar } from "@/components/filter-bar"
import { LoadingSpinner } from "@/components/loading-spinner"
import { TimelineView } from "@/components/timeline-view"
import { AboutView } from "@/components/about-view"
import { ContactView } from "@/components/contact-view"
import { CategoryPage } from "@/components/category-page"
import { TagCloud } from "@/components/tag-cloud"
import { ApiStatus } from "@/components/api-status"
import { useInfinitePhotos, type Photo } from "@/lib/queries"
import { useToast } from "@/components/ui/use-toast"
export default function HomePage() {
const { data: photos = [], isLoading, error } = useInfinitePhotos()
const { toast } = useToast()
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null)
const [activeCategory, setActiveCategory] = useState("all")
const [activeTab, setActiveTab] = useState("gallery")
const [searchText, setSearchText] = useState("")
const [sortBy, setSortBy] = useState("date_desc")
const [selectedTags, setSelectedTags] = useState<string[]>([])
useEffect(() => {
if (error) {
const isRealApi = process.env.NEXT_PUBLIC_USE_REAL_API === 'true'
toast({
title: "数据加载失败",
description: isRealApi
? "无法连接到后端API请确保后端服务正在运行 (localhost:8080)"
: "无法连接到Mock API请确保Mock API正在运行 (localhost:3001)",
variant: "destructive",
})
}
}, [error, toast])
const filteredPhotos = useMemo(() => {
let filtered = [...photos]
// 分类过滤
if (activeCategory !== "all") {
filtered = filtered.filter((photo: Photo) =>
photo.category.toLowerCase() === activeCategory.toLowerCase()
)
}
// 搜索过滤
if (searchText.trim()) {
const searchLower = searchText.toLowerCase()
filtered = filtered.filter((photo: Photo) =>
photo.title.toLowerCase().includes(searchLower) ||
photo.description.toLowerCase().includes(searchLower) ||
photo.category.toLowerCase().includes(searchLower)
)
}
// 标签过滤
if (selectedTags.length > 0) {
filtered = filtered.filter((photo: Photo) =>
selectedTags.some(tag =>
photo.tags.some(photoTag =>
photoTag.toLowerCase().includes(tag.toLowerCase())
)
)
)
}
// 排序
switch (sortBy) {
case "date_desc":
filtered.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
break
case "date_asc":
filtered.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
break
case "title_asc":
filtered.sort((a, b) => a.title.localeCompare(b.title))
break
case "title_desc":
filtered.sort((a, b) => b.title.localeCompare(a.title))
break
case "category":
filtered.sort((a, b) => a.category.localeCompare(b.category))
break
default:
break
}
return filtered
}, [photos, activeCategory, searchText, selectedTags, sortBy])
const handleFilter = (category: string) => {
setActiveCategory(category)
}
const handleSearchChange = (search: string) => {
setSearchText(search)
}
const handleSortChange = (sort: string) => {
setSortBy(sort)
}
const handleTagToggle = (tag: string) => {
setSelectedTags(prev =>
prev.includes(tag)
? prev.filter(t => t !== tag)
: [...prev, tag]
)
}
const handleClearFilters = () => {
setActiveCategory("all")
setSearchText("")
setSortBy("date_desc")
setSelectedTags([])
}
const handlePhotoClick = (photo: Photo) => {
setSelectedPhoto(photo)
}
const handleCloseModal = () => {
setSelectedPhoto(null)
}
const handlePrevPhoto = () => {
if (!selectedPhoto) return
const currentPhotos = activeTab === "gallery" ? filteredPhotos : photos
const currentIndex = currentPhotos.findIndex((p: Photo) => p.id === selectedPhoto.id)
const prevIndex = currentIndex > 0 ? currentIndex - 1 : currentPhotos.length - 1
setSelectedPhoto(currentPhotos[prevIndex])
}
const handleNextPhoto = () => {
if (!selectedPhoto) return
const currentPhotos = activeTab === "gallery" ? filteredPhotos : photos
const currentIndex = currentPhotos.findIndex((p: Photo) => p.id === selectedPhoto.id)
const nextIndex = currentIndex < currentPhotos.length - 1 ? currentIndex + 1 : 0
setSelectedPhoto(currentPhotos[nextIndex])
}
const handleTabChange = (tab: string) => {
setActiveTab(tab)
// Reset filters when switching tabs
if (tab === "timeline" || tab === "categories" || tab === "tags") {
setActiveCategory("all")
setSearchText("")
setSelectedTags([])
}
}
const handleCategorySelect = (category: string) => {
setActiveCategory(category)
setActiveTab("gallery")
}
const handleTagSelect = (tag: string) => {
setSelectedTags([tag])
setActiveTab("gallery")
}
const handlePhotosView = () => {
setActiveTab("gallery")
}
const handleContactClick = () => {
setActiveTab("contact")
}
const getPageTitle = () => {
switch (activeTab) {
case "gallery":
return "摄影作品集"
case "categories":
return "分类浏览"
case "tags":
return "标签云"
case "timeline":
return "创作时间线"
case "about":
return "关于我"
case "contact":
return "联系合作"
default:
return "摄影作品集"
}
}
const getPageDescription = () => {
switch (activeTab) {
case "gallery":
return "用镜头记录世界的美好瞬间,每一张照片都是时光的诗篇"
case "categories":
return "按分类探索摄影作品,发现不同主题下的精彩瞬间"
case "tags":
return "通过标签云发现相关主题,探索摄影作品的多样性"
case "timeline":
return "按时间顺序回顾摄影创作历程,见证技艺与视角的成长轨迹"
case "about":
return "分享我的摄影故事,探索光影背后的创作理念与人生感悟"
case "contact":
return "期待与您合作,共同创造独特而珍贵的视觉记忆"
default:
return "用镜头记录世界的美好瞬间,每一张照片都是时光的诗篇"
}
}
if (isLoading) {
return <LoadingSpinner />
}
return (
<div className="min-h-screen bg-white">
<Navigation activeTab={activeTab} onTabChange={handleTabChange} />
<main className="container mx-auto px-4 py-8">
{(activeTab === "gallery" || activeTab === "timeline") && (
<div className="text-center mb-12">
<h1 className="text-4xl md:text-6xl font-light text-gray-900 mb-4">{getPageTitle()}</h1>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">{getPageDescription()}</p>
</div>
)}
{activeTab === "gallery" && (
<>
<FilterBar
activeCategory={activeCategory}
onFilter={handleFilter}
searchText={searchText}
onSearchChange={handleSearchChange}
sortBy={sortBy}
onSortChange={handleSortChange}
selectedTags={selectedTags}
onTagToggle={handleTagToggle}
onClearFilters={handleClearFilters}
/>
<PhotoGallery photos={filteredPhotos} onPhotoClick={handlePhotoClick} />
</>
)}
{activeTab === "categories" && (
<CategoryPage
photos={photos}
onCategorySelect={handleCategorySelect}
onPhotosView={handlePhotosView}
/>
)}
{activeTab === "tags" && (
<TagCloud
photos={photos}
onTagSelect={handleTagSelect}
onPhotosView={handlePhotosView}
/>
)}
{activeTab === "timeline" && <TimelineView photos={photos} onPhotoClick={handlePhotoClick} />}
{activeTab === "about" && <AboutView onContactClick={handleContactClick} />}
{activeTab === "contact" && <ContactView />}
</main>
{selectedPhoto && (
<PhotoModal
photo={selectedPhoto}
onClose={handleCloseModal}
onPrev={handlePrevPhoto}
onNext={handleNextPhoto}
/>
)}
{/* API状态指示器 - 仅开发环境 */}
<ApiStatus />
</div>
)
}