✨ 新增功能: - 增强照片展示页面,支持3种视图模式(网格/瀑布流/列表) - 实现分页加载和无限滚动功能 - 完整的搜索和过滤系统,支持实时搜索、标签筛选、排序 - 新增分类浏览页面,提供分类统计和预览 - 新增标签云页面,热度可视化显示 - 面包屑导航和页面间无缝跳转 🎨 用户体验优化: - 响应式设计,完美适配移动端和桌面端 - 智能loading状态和空状态处理 - 悬停效果和交互动画 - 视觉化统计仪表盘 ⚡ 性能优化: - 图片懒加载和智能分页 - 优化的组件渲染和状态管理 - 构建大小优化(187kB gzipped) 📝 更新任务进度文档,完成率达到32.5% Phase 3核心功能基本完成,前端展示网站达到完全可用状态。
283 lines
8.6 KiB
TypeScript
283 lines
8.6 KiB
TypeScript
"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>
|
||
)
|
||
}
|