/* eslint-disable react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any */ 'use client'; import { Search, X } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { Suspense, useEffect, useMemo, useState } from 'react'; import { addSearchHistory, clearSearchHistory, deleteSearchHistory, getSearchHistory, } from '@/lib/db.client'; import { SearchResult } from '@/lib/types'; import PageLayout from '@/components/PageLayout'; import VideoCard from '@/components/VideoCard'; function SearchPageClient() { // 搜索历史 const [searchHistory, setSearchHistory] = useState([]); const router = useRouter(); const searchParams = useSearchParams(); const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(false); const [showResults, setShowResults] = useState(false); const [searchResults, setSearchResults] = useState([]); // 视图模式:聚合(agg) 或 全部(all),默认值由环境变量 NEXT_PUBLIC_AGGREGATE_SEARCH_RESULT 决定 const defaultAggregate = typeof window !== 'undefined' && Boolean((window as any).RUNTIME_CONFIG?.AGGREGATE_SEARCH_RESULT); const [viewMode, setViewMode] = useState<'agg' | 'all'>( defaultAggregate ? 'agg' : 'all' ); // 聚合后的结果(按标题和年份分组) const aggregatedResults = useMemo(() => { const map = new Map(); searchResults.forEach((item) => { // 使用 title + year + type 作为键,year 必然存在,但依然兜底 'unknown' const key = `${item.title}-${item.year || 'unknown'}-${ item.episodes.length === 1 ? 'movie' : 'tv' }`; const arr = map.get(key) || []; arr.push(item); map.set(key, arr); }); return Array.from(map.entries()).sort((a, b) => { // 优先排序:标题与搜索词完全一致的排在前面 const aExactMatch = a[1][0].title === searchQuery.trim(); const bExactMatch = b[1][0].title === searchQuery.trim(); if (aExactMatch && !bExactMatch) return -1; if (!aExactMatch && bExactMatch) return 1; // 如果都匹配或都不匹配,则按原来的逻辑排序 return a[1][0].year === b[1][0].year ? a[0].localeCompare(b[0]) : a[1][0].year > b[1][0].year ? -1 : 1; }); }, [searchResults]); useEffect(() => { // 无搜索参数时聚焦搜索框 !searchParams.get('q') && document.getElementById('searchInput')?.focus(); getSearchHistory().then(setSearchHistory); }, []); useEffect(() => { // 当搜索参数变化时更新搜索状态 const query = searchParams.get('q'); if (query) { setSearchQuery(query); fetchSearchResults(query); // 保存到搜索历史 addSearchHistory(query).then(async () => { const history = await getSearchHistory(); setSearchHistory(history); }); } else { setShowResults(false); } }, [searchParams]); const fetchSearchResults = async (query: string) => { try { setIsLoading(true); const response = await fetch( `/api/search?q=${encodeURIComponent(query.trim())}` ); const data = await response.json(); setSearchResults( data.results.sort((a: SearchResult, b: SearchResult) => { // 优先排序:标题与搜索词完全一致的排在前面 const aExactMatch = a.title === query.trim(); const bExactMatch = b.title === query.trim(); if (aExactMatch && !bExactMatch) return -1; if (!aExactMatch && bExactMatch) return 1; // 如果都匹配或都不匹配,则按原来的逻辑排序 return a.year === b.year ? a.title.localeCompare(b.title) : a.year > b.year ? -1 : 1; }) ); setShowResults(true); } catch (error) { setSearchResults([]); } finally { setIsLoading(false); } }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); const trimmed = searchQuery.trim().replace(/\s+/g, ' '); if (!trimmed) return; // 回显搜索框 setSearchQuery(trimmed); setIsLoading(true); setShowResults(true); router.push(`/search?q=${encodeURIComponent(trimmed)}`); // 直接发请求 fetchSearchResults(trimmed); // 保存到搜索历史 addSearchHistory(trimmed).then(async () => { const history = await getSearchHistory(); setSearchHistory(history); }); }; return (
{/* 搜索框 */}
setSearchQuery(e.target.value)} placeholder='搜索电影、电视剧...' className='w-full h-12 rounded-lg bg-gray-50/80 py-3 pl-10 pr-4 text-sm text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-400 focus:bg-white border border-gray-200/50 shadow-sm dark:bg-gray-800 dark:text-gray-300 dark:placeholder-gray-500 dark:focus:bg-gray-700 dark:border-gray-700' />
{/* 搜索结果或搜索历史 */}
{isLoading ? (
) : showResults ? (
{/* 标题 + 聚合开关 */}

搜索结果

{/* 聚合开关 */}
{viewMode === 'agg' ? aggregatedResults.map(([mapKey, group]) => { return (
); }) : searchResults.map((item) => (
))} {searchResults.length === 0 && (
未找到相关结果
)}
) : searchHistory.length > 0 ? ( // 搜索历史

搜索历史 {searchHistory.length > 0 && ( )}

{searchHistory.map((item) => (
{/* 删除按钮 */}
))}
) : null}
); } export default function SearchPage() { return ( ); }