/* eslint-disable react-hooks/exhaustive-deps, no-console */ 'use client'; import { Heart, LinkIcon } from 'lucide-react'; import Image from 'next/image'; import { useRouter, useSearchParams } from 'next/navigation'; import { Suspense, useEffect, useState } from 'react'; import { isFavorited, toggleFavorite } from '@/lib/db.client'; import { SearchResult } from '@/lib/types'; import PageLayout from '@/components/PageLayout'; function AggregatePageClient() { const searchParams = useSearchParams(); const query = searchParams.get('q') || ''; const title = searchParams.get('title') || ''; const year = searchParams.get('year') || ''; const type = searchParams.get('type') || ''; const [results, setResults] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const router = useRouter(); useEffect(() => { if (!query) { setError('缺少搜索关键词'); setLoading(false); return; } const fetchData = async () => { try { const res = await fetch( `/api/search?q=${encodeURIComponent(query.trim())}` ); if (!res.ok) { throw new Error('搜索失败'); } const data = await res.json(); const all: SearchResult[] = data.results || []; const map = new Map(); all.forEach((r) => { // 根据传入参数进行精确匹配: // 1. 如果提供了 title,则按 title 精确匹配,否则按 query 精确匹配; // 2. 如果还提供了 year,则额外按 year 精确匹配。 const titleMatch = title ? r.title === title : r.title === query; const yearMatch = year ? r.year === year : true; if (!titleMatch || !yearMatch) { return; } // 如果还传入了 type,则按 type 精确匹配 if (type === 'tv' && r.episodes.length === 1) { return; } if (type === 'movie' && r.episodes.length !== 1) { return; } const key = `${r.title}-${r.year}`; const arr = map.get(key) || []; arr.push(r); map.set(key, arr); }); if (map.size === 0 && type) { // 无匹配,忽略 type 做重新匹配 all.forEach((r) => { const titleMatch = title ? r.title === title : r.title === query; const yearMatch = year ? r.year === year : true; if (!titleMatch || !yearMatch) { return; } const key = `${r.title}-${r.year}`; const arr = map.get(key) || []; arr.push(r); map.set(key, arr); }); } if (map.size == 1) { setResults(Array.from(map.values()).flat()); } else if (map.size > 1) { // 存在多个匹配,跳转到搜索页 router.push(`/search?q=${encodeURIComponent(query.trim())}`); } } catch (e) { setError(e instanceof Error ? e.message : '搜索失败'); } finally { setLoading(false); } }; fetchData(); }, [query, router]); // 选出信息最完整的字段 const chooseString = (vals: (string | undefined)[]): string | undefined => { return vals.reduce((best, v) => { if (!v) return best; if (!best) return v; return v.length > best.length ? v : best; }, undefined); }; // 出现次数最多的非 0 数字 const chooseNumber = (vals: (number | undefined)[]): number | undefined => { const countMap = new Map(); vals.forEach((v) => { if (v !== undefined && v !== 0) { countMap.set(v, (countMap.get(v) || 0) + 1); } }); let selected: number | undefined = undefined; let maxCount = 0; countMap.forEach((cnt, num) => { if (cnt > maxCount) { maxCount = cnt; selected = num; } }); return selected; }; const aggregatedInfo = { title: title || query, cover: chooseString(results.map((d) => d.poster)), desc: chooseString(results.map((d) => d.desc)), type: chooseString(results.map((d) => d.type_name)), year: chooseString(results.map((d) => d.year)), remarks: chooseString(results.map((d) => d.class)), douban_id: chooseNumber(results.map((d) => d.douban_id)), }; const infoReady = Boolean( aggregatedInfo.cover || aggregatedInfo.desc || aggregatedInfo.type || aggregatedInfo.year || aggregatedInfo.remarks ); const uniqueSources = Array.from( new Map(results.map((r) => [r.source, r])).values() ); // 详情映射,便于快速获取每个源的集数 const sourceDetailMap = new Map(results.map((d) => [d.source, d])); // 新增:播放源卡片组件,包含收藏逻辑 const SourceCard = ({ src }: { src: SearchResult }) => { const d = sourceDetailMap.get(src.source); const epCount = d ? d.episodes.length : src.episodes.length; const [favorited, setFavorited] = useState(false); // 初次加载检查收藏状态 useEffect(() => { (async () => { try { const fav = await isFavorited(src.source, src.id); setFavorited(fav); } catch { /* 忽略错误 */ } })(); }, [src.source, src.id]); // 切换收藏状态 const handleToggleFavorite = async ( e: React.MouseEvent ) => { e.preventDefault(); e.stopPropagation(); try { const newState = await toggleFavorite(src.source, src.id, { title: src.title, source_name: src.source_name, year: src.year, cover: src.poster, total_episodes: src.episodes.length, save_time: Date.now(), }); setFavorited(newState); } catch { /* 忽略错误 */ } }; return ( {/* 收藏爱心 */} {/* 名称 */} {src.source_name} {/* 集数徽标 */} {epCount && epCount > 1 ? ( {epCount}集 ) : null} ); }; return (
{loading ? (
) : error ? (
加载失败
{error}
) : !infoReady ? (
未找到匹配结果
) : (
{/* 主信息区:左图右文 */}
{/* 封面 */}
{aggregatedInfo.title}
{/* 右侧信息 */}

{aggregatedInfo.title} {aggregatedInfo.douban_id && ( e.stopPropagation()} className='ml-2' > )}

{aggregatedInfo.remarks && ( {aggregatedInfo.remarks} )} {aggregatedInfo.year && {aggregatedInfo.year}} {aggregatedInfo.type && {aggregatedInfo.type}}
{aggregatedInfo.desc}
{/* 选播放源 */} {uniqueSources.length > 0 && (
选择播放源
共 {uniqueSources.length} 个
{uniqueSources.map((src) => ( ))}
)}
)}
); } export default function AggregatePage() { return ( ); }