feat: 完成前端展示网站核心功能开发

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

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

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

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

Phase 3核心功能基本完成,前端展示网站达到完全可用状态。
This commit is contained in:
xujiang
2025-07-11 12:27:36 +08:00
parent af222afc33
commit 9046befcf1
8 changed files with 1383 additions and 87 deletions

View File

@ -9,16 +9,21 @@ 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 { usePhotos, type Photo } from "@/lib/queries"
import { useInfinitePhotos, type Photo } from "@/lib/queries"
import { useToast } from "@/components/ui/use-toast"
export default function HomePage() {
const { data: photos = [], isLoading, error } = usePhotos()
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) {
@ -26,7 +31,7 @@ export default function HomePage() {
toast({
title: "数据加载失败",
description: isRealApi
? "无法连接到后端API请确保后端服务正在运行 (localhost:8888)"
? "无法连接到后端API请确保后端服务正在运行 (localhost:8080)"
: "无法连接到Mock API请确保Mock API正在运行 (localhost:3001)",
variant: "destructive",
})
@ -34,16 +39,87 @@ export default function HomePage() {
}, [error, toast])
const filteredPhotos = useMemo(() => {
if (activeCategory === "all") {
return photos
let filtered = [...photos]
// 分类过滤
if (activeCategory !== "all") {
filtered = filtered.filter((photo: Photo) =>
photo.category.toLowerCase() === activeCategory.toLowerCase()
)
}
return photos.filter((photo: Photo) => photo.category === activeCategory)
}, [photos, activeCategory])
// 搜索过滤
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)
}
@ -71,11 +147,27 @@ export default function HomePage() {
const handleTabChange = (tab: string) => {
setActiveTab(tab)
// Reset filters when switching tabs
if (tab === "timeline") {
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")
}
@ -84,6 +176,10 @@ export default function HomePage() {
switch (activeTab) {
case "gallery":
return "摄影作品集"
case "categories":
return "分类浏览"
case "tags":
return "标签云"
case "timeline":
return "创作时间线"
case "about":
@ -99,6 +195,10 @@ export default function HomePage() {
switch (activeTab) {
case "gallery":
return "用镜头记录世界的美好瞬间,每一张照片都是时光的诗篇"
case "categories":
return "按分类探索摄影作品,发现不同主题下的精彩瞬间"
case "tags":
return "通过标签云发现相关主题,探索摄影作品的多样性"
case "timeline":
return "按时间顺序回顾摄影创作历程,见证技艺与视角的成长轨迹"
case "about":
@ -128,11 +228,37 @@ export default function HomePage() {
{activeTab === "gallery" && (
<>
<FilterBar activeCategory={activeCategory} onFilter={handleFilter} />
<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} />}