diff --git a/src/app/api/cron/route.ts b/src/app/api/cron/route.ts index ded5a1e..0bdbacd 100644 --- a/src/app/api/cron/route.ts +++ b/src/app/api/cron/route.ts @@ -70,6 +70,7 @@ async function refreshConfig() { throw new Error('配置文件格式错误,请检查 JSON 语法'); } config.ConfigFile = decodedContent; + config.ConfigSubscribtion.LastCheck = new Date().toISOString(); config = refineConfig(config); const storage = getStorage(); if (storage && typeof (storage as any).setAdminConfig === 'function') { diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index a8cfd01..f8ab956 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -18,6 +18,7 @@ import { yellowWords } from '@/lib/yellow'; import PageLayout from '@/components/PageLayout'; import SearchSuggestions from '@/components/SearchSuggestions'; import VideoCard from '@/components/VideoCard'; +import SearchResultFilter, { SearchFilterCategory } from '@/components/SearchResultFilter'; function SearchPageClient() { // 搜索历史 @@ -32,6 +33,19 @@ function SearchPageClient() { const [showResults, setShowResults] = useState(false); const [searchResults, setSearchResults] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); + // 过滤器:非聚合与聚合 + const [filterAll, setFilterAll] = useState<{ source: string; title: string; year: string; yearOrder: 'asc' | 'desc' }>({ + source: 'all', + title: 'all', + year: 'all', + yearOrder: 'desc', + }); + const [filterAgg, setFilterAgg] = useState<{ source: string; title: string; year: string; yearOrder: 'asc' | 'desc' }>({ + source: 'all', + title: 'all', + year: 'all', + yearOrder: 'desc', + }); // 获取默认聚合设置:只读取用户本地设置,默认为 true const getDefaultAggregate = () => { @@ -53,9 +67,8 @@ function SearchPageClient() { const map = new Map(); searchResults.forEach((item) => { // 使用 title + year + type 作为键,year 必然存在,但依然兜底 'unknown' - const key = `${item.title.replaceAll(' ', '')}-${ - item.year || 'unknown' - }-${item.episodes.length === 1 ? 'movie' : 'tv'}`; + const key = `${item.title.replaceAll(' ', '')}-${item.year || 'unknown' + }-${item.episodes.length === 1 ? 'movie' : 'tv'}`; const arr = map.get(key) || []; arr.push(item); map.set(key, arr); @@ -94,6 +107,114 @@ function SearchPageClient() { }); }, [searchResults]); + // 构建筛选选项 + const filterOptions = useMemo(() => { + const sourcesSet = new Map(); + const titlesSet = new Set(); + const yearsSet = new Set(); + + searchResults.forEach((item) => { + if (item.source && item.source_name) { + sourcesSet.set(item.source, item.source_name); + } + if (item.title) titlesSet.add(item.title); + if (item.year) yearsSet.add(item.year); + }); + + const sourceOptions: { label: string; value: string }[] = [ + { label: '全部来源', value: 'all' }, + ...Array.from(sourcesSet.entries()) + .sort((a, b) => a[1].localeCompare(b[1])) + .map(([value, label]) => ({ label, value })), + ]; + + const titleOptions: { label: string; value: string }[] = [ + { label: '全部标题', value: 'all' }, + ...Array.from(titlesSet.values()) + .sort((a, b) => a.localeCompare(b)) + .map((t) => ({ label: t, value: t })), + ]; + + // 年份: 将 unknown 放末尾 + const years = Array.from(yearsSet.values()); + const knownYears = years.filter((y) => y !== 'unknown').sort((a, b) => parseInt(b) - parseInt(a)); + const hasUnknown = years.includes('unknown'); + const yearOptions: { label: string; value: string }[] = [ + { label: '全部年份', value: 'all' }, + ...knownYears.map((y) => ({ label: y, value: y })), + ...(hasUnknown ? [{ label: '未知', value: 'unknown' }] : []), + ]; + + const categoriesAll: SearchFilterCategory[] = [ + { key: 'source', label: '来源', options: sourceOptions }, + { key: 'title', label: '标题', options: titleOptions }, + { key: 'year', label: '年份', options: yearOptions }, + ]; + + const categoriesAgg: SearchFilterCategory[] = [ + { key: 'source', label: '来源', options: sourceOptions }, + { key: 'title', label: '标题', options: titleOptions }, + { key: 'year', label: '年份', options: yearOptions }, + ]; + + return { categoriesAll, categoriesAgg }; + }, [searchResults]); + + // 年份排序辅助 + const compareYear = (aYear: string, bYear: string, order: 'asc' | 'desc') => { + if (aYear === bYear) return 0; + if (aYear === 'unknown') return 1; + if (bYear === 'unknown') return -1; + const diff = parseInt(aYear) - parseInt(bYear); + return order === 'asc' ? diff : -diff; + }; + + // 非聚合:应用筛选与排序 + const filteredAllResults = useMemo(() => { + const { source, title, year, yearOrder } = filterAll; + const filtered = searchResults.filter((item) => { + if (source !== 'all' && item.source !== source) return false; + if (title !== 'all' && item.title !== title) return false; + if (year !== 'all' && item.year !== year) return false; + return true; + }); + // 仍保持“精确标题优先”的二级排序 + return filtered.sort((a, b) => { + const yearComp = compareYear(a.year, b.year, yearOrder); + if (yearComp !== 0) return yearComp; + const aExactMatch = a.title === searchQuery.trim(); + const bExactMatch = b.title === searchQuery.trim(); + if (aExactMatch && !bExactMatch) return -1; + if (!aExactMatch && bExactMatch) return 1; + return a.title.localeCompare(b.title); + }); + }, [searchResults, filterAll, searchQuery]); + + // 聚合:应用筛选与排序 + const filteredAggResults = useMemo(() => { + const { source, title, year, yearOrder } = filterAgg as any; + const filtered = aggregatedResults.filter(([_, group]) => { + const gTitle = group[0]?.title ?? ''; + const gYear = group[0]?.year ?? 'unknown'; + const hasSource = source === 'all' ? true : group.some((item) => item.source === source); + if (!hasSource) return false; + if (title !== 'all' && gTitle !== title) return false; + if (year !== 'all' && gYear !== year) return false; + return true; + }); + return filtered.sort((a, b) => { + const aExactMatch = a[1][0].title.replaceAll(' ', '').includes(searchQuery.trim().replaceAll(' ', '')); + const bExactMatch = b[1][0].title.replaceAll(' ', '').includes(searchQuery.trim().replaceAll(' ', '')); + if (aExactMatch && !bExactMatch) return -1; + if (!aExactMatch && bExactMatch) return 1; + const aYear = a[1][0].year; + const bYear = b[1][0].year; + const yearComp = compareYear(aYear, bYear, yearOrder); + if (yearComp !== 0) return yearComp; + return a[0].localeCompare(b[0]); + }); + }, [aggregatedResults, filterAgg, searchQuery]); + useEffect(() => { // 无搜索参数时聚焦搜索框 !searchParams.get('q') && document.getElementById('searchInput')?.focus(); @@ -318,24 +439,36 @@ function SearchPageClient() { ) : showResults ? (
- {/* 标题 + 聚合开关 */} -
-

- 搜索结果 -

+ {/* 标题 */} +
+

搜索结果

+
+ {/* 筛选器 + 聚合开关 同行 */} +
+
+ {viewMode === 'agg' ? ( + setFilterAgg(v as any)} + /> + ) : ( + setFilterAll(v as any)} + /> + )} +
{/* 聚合开关 */} -