From dd6d2e802c08428fe6f399715f41d9ed563ca3e1 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Sun, 28 Dec 2025 16:15:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E6=BC=AB=E7=A3=81?= =?UTF-8?q?=E5=8A=9B=E7=BF=BB=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AcgSearch.tsx | 114 +++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/src/components/AcgSearch.tsx b/src/components/AcgSearch.tsx index 31a14de..a3248e3 100644 --- a/src/components/AcgSearch.tsx +++ b/src/components/AcgSearch.tsx @@ -2,7 +2,7 @@ 'use client'; import { AlertCircle, Download, ExternalLink, Loader2 } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import Toast, { ToastProps } from '@/components/Toast'; @@ -35,17 +35,23 @@ export default function AcgSearch({ onError, }: AcgSearchProps) { const [loading, setLoading] = useState(false); - const [results, setResults] = useState(null); + const [allItems, setAllItems] = useState([]); // 所有加载的项目 const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); + const [hasMore, setHasMore] = useState(true); const [downloadingId, setDownloadingId] = useState(null); const [showNameDialog, setShowNameDialog] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const [customName, setCustomName] = useState(''); const [toast, setToast] = useState(null); + const loadMoreRef = useRef(null); + const isLoadingMoreRef = useRef(false); // 执行搜索 - const performSearch = async (page: number) => { + const performSearch = async (page: number, isLoadMore = false) => { + if (isLoadingMoreRef.current) return; + + isLoadingMoreRef.current = true; setLoading(true); setError(null); @@ -67,7 +73,19 @@ export default function AcgSearch({ } const data: AcgSearchResult = await response.json(); - setResults(data); + + if (isLoadMore) { + // 追加新数据 + setAllItems(prev => [...prev, ...data.items]); + // 如果当前页没有结果,说明没有更多了 + setHasMore(data.items.length > 0); + } else { + // 新搜索,重置数据 + setAllItems(data.items); + // 如果第一页有结果,假设可能还有更多 + setHasMore(data.items.length > 0); + } + setCurrentPage(page); } catch (err: any) { const errorMsg = err.message || '搜索失败,请稍后重试'; @@ -75,6 +93,7 @@ export default function AcgSearch({ onError?.(errorMsg); } finally { setLoading(false); + isLoadingMoreRef.current = false; } }; @@ -89,14 +108,45 @@ export default function AcgSearch({ return; } - performSearch(1); + // 重置状态并开始新搜索 + setAllItems([]); + setCurrentPage(1); + setHasMore(true); + performSearch(1, false); }, [triggerSearch]); - // 处理页码变化 - const handlePageChange = (newPage: number) => { - if (newPage < 1 || loading) return; - performSearch(newPage); - }; + // 加载更多数据 + const loadMore = useCallback(() => { + if (!loading && hasMore && !isLoadingMoreRef.current) { + performSearch(currentPage + 1, true); + } + }, [loading, hasMore, currentPage]); + + // 使用 Intersection Observer 监听滚动到底部 + useEffect(() => { + const element = loadMoreRef.current; + if (!element) return; + + const observer = new IntersectionObserver( + (entries) => { + const target = entries[0]; + if (target.isIntersecting) { + loadMore(); + } + }, + { + root: null, + rootMargin: '100px', + threshold: 0.1, + } + ); + + observer.observe(element); + + return () => { + observer.unobserve(element); + }; + }, [loadMore]); // 打开命名弹窗 const handleOpenDownloadDialog = (item: AcgSearchItem) => { @@ -150,7 +200,7 @@ export default function AcgSearch({ } }; - if (loading && !results) { + if (loading && allItems.length === 0) { return (
@@ -174,7 +224,7 @@ export default function AcgSearch({ ); } - if (!results || results.total === 0) { + if (allItems.length === 0) { return (
@@ -189,35 +239,9 @@ export default function AcgSearch({ return (
- {/* 搜索结果统计 */} -
-
- 第 {currentPage} 页, - 共 {results.total} 个结果 -
- - {/* 分页按钮 */} -
- - -
-
- {/* 结果列表 */}
- {results.items.map((item) => ( + {allItems.map((item) => (
+ {/* 加载更多指示器 */} + {hasMore && ( +
+
+ +

+ 加载更多... +

+
+
+ )} + {/* 命名弹窗 */} {showNameDialog && (