diff --git a/src/app/aggregate/page.tsx b/src/app/aggregate/page.tsx index 3d46707..3b87110 100644 --- a/src/app/aggregate/page.tsx +++ b/src/app/aggregate/page.tsx @@ -222,9 +222,11 @@ function AggregatePageClient() { return ( {/* 名称 */} @@ -233,7 +235,7 @@ function AggregatePageClient() { {/* 集数徽标 */} {epCount && epCount > 1 ? ( - + {epCount}集 ) : null} diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index f003a46..9bfd06e 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -773,9 +773,16 @@ function PlayPageClient() { // 处理返回按钮点击 const handleBack = () => { - window.location.href = `/detail?source=${currentSource}&id=${currentId}&title=${encodeURIComponent( - videoTitle - )}`; + const urlParams = new URLSearchParams(window.location.search); + const fromAggregate = urlParams.get('from') === 'aggregate'; + + if (fromAggregate) { + window.location.href = `/aggregate?q=${encodeURIComponent(videoTitle)}`; + } else { + window.location.href = `/detail?source=${currentSource}&id=${currentId}&title=${encodeURIComponent( + videoTitle + )}`; + } }; // 处理上一集 diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index 2bdb86a..e332275 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -3,8 +3,7 @@ import { Search } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { Suspense } from 'react'; -import { useEffect, useRef, useState } from 'react'; +import { Suspense, useEffect, useMemo, useRef, useState } from 'react'; import { addSearchHistory, @@ -12,6 +11,7 @@ import { getSearchHistory, } from '@/lib/db.client'; +import AggregateCard from '@/components/AggregateCard'; import PageLayout from '@/components/PageLayout'; import VideoCard from '@/components/VideoCard'; @@ -37,6 +37,20 @@ function SearchPageClient() { const [searchResults, setSearchResults] = useState([]); const searchInputRef = useRef(null); + // 视图模式:聚合(agg) 或 全部(all) + const [viewMode, setViewMode] = useState<'agg' | 'all'>('agg'); + + // 聚合后的结果(按标题分组) + const aggregatedResults = useMemo(() => { + const map = new Map(); + searchResults.forEach((item) => { + const arr = map.get(item.title) || []; + arr.push(item); + map.set(item.title, arr); + }); + return Array.from(map.values()); + }, [searchResults]); + useEffect(() => { // 自动聚焦搜索框:仅当 URL 中没有搜索参数时 if (!searchParams.get('q')) { @@ -127,19 +141,49 @@ function SearchPageClient() {
) : showResults ? ( - // 搜索结果 -
- {searchResults.map((item) => ( -
- -
- ))} - {searchResults.length === 0 && ( -
- 未找到相关结果 -
- )} -
+
+ {/* 标题 + 聚合开关 */} +
+

搜索结果

+ {/* 聚合开关 */} + +
+
+ {viewMode === 'agg' + ? aggregatedResults.map((group) => { + const key = group[0].title; + return ( +
+ +
+ ); + }) + : searchResults.map((item) => ( +
+ +
+ ))} + {searchResults.length === 0 && ( +
+ 未找到相关结果 +
+ )} +
+
) : searchHistory.length > 0 ? ( // 搜索历史
diff --git a/src/components/AggregateCard.tsx b/src/components/AggregateCard.tsx new file mode 100644 index 0000000..2661c03 --- /dev/null +++ b/src/components/AggregateCard.tsx @@ -0,0 +1,112 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import React, { useState } from 'react'; + +// 聚合卡需要的基本字段,与搜索接口保持一致 +interface SearchResult { + id: string; + title: string; + poster: string; + source: string; + source_name: string; + episodes?: number; +} + +interface AggregateCardProps { + /** 同一标题下的多个搜索结果 */ + items: SearchResult[]; +} + +function PlayCircleSolid({ + className = '', + fillColor = 'none', +}: { + className?: string; + fillColor?: string; +}) { + return ( + + + + + ); +} + +/** + * 与 `VideoCard` 基本一致,删除了集数徽标、来源标签、收藏等功能 + * 点击播放按钮 -> 跳到第一个源播放 + * 点击卡片其他区域 -> 跳到聚合详情页 (/aggregate) + */ +const AggregateCard: React.FC = ({ items }) => { + // 使用列表中的第一个结果做展示 & 播放 + const first = items[0]; + const [playHover, setPlayHover] = useState(false); + const router = useRouter(); + + return ( + +
+ {/* 封面图片 2:3 */} +
+ {first.title} + + {/* Hover 层 & 播放按钮 */} +
+
+
{ + e.preventDefault(); + e.stopPropagation(); + router.push( + `/play?source=${first.source}&id=${ + first.id + }&title=${encodeURIComponent(first.title)}&from=aggregate` + ); + }} + onMouseEnter={() => setPlayHover(true)} + onMouseLeave={() => setPlayHover(false)} + > + +
+
+
+
+ + {/* 标题 */} +
+
+ + {first.title} + +
+
+
+ + ); +}; + +export default AggregateCard;