精确搜索
This commit is contained in:
+34
-4
@@ -62,6 +62,8 @@ function SearchPageClient() {
|
|||||||
const [forceRefresh, setForceRefresh] = useState(false);
|
const [forceRefresh, setForceRefresh] = useState(false);
|
||||||
// 是否使用了缓存结果
|
// 是否使用了缓存结果
|
||||||
const [isFromCache, setIsFromCache] = useState(false);
|
const [isFromCache, setIsFromCache] = useState(false);
|
||||||
|
// 精确搜索开关
|
||||||
|
const [exactSearch, setExactSearch] = useState(true);
|
||||||
|
|
||||||
// 生成缓存键
|
// 生成缓存键
|
||||||
const getCacheKey = (query: string) => {
|
const getCacheKey = (query: string) => {
|
||||||
@@ -257,12 +259,28 @@ function SearchPageClient() {
|
|||||||
// 2.4 兜底:使用 episodes.length(最不可靠)
|
// 2.4 兜底:使用 episodes.length(最不可靠)
|
||||||
return item.episodes.length === 1 ? 'movie' : 'tv';
|
return item.episodes.length === 1 ? 'movie' : 'tv';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 辅助函数:检查标题是否包含搜索词(用于精确搜索)
|
||||||
|
const titleContainsQuery = (title: string, query: string): boolean => {
|
||||||
|
if (!exactSearch) return true; // 如果未开启精确搜索,不过滤
|
||||||
|
if (!query || !title) return true; // 如果没有搜索词或标题,不过滤
|
||||||
|
|
||||||
|
const normalizedTitle = title.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
return normalizedTitle.includes(normalizedQuery);
|
||||||
|
};
|
||||||
// 聚合后的结果(按标题和年份分组)
|
// 聚合后的结果(按标题和年份分组)
|
||||||
const aggregatedResults = useMemo(() => {
|
const aggregatedResults = useMemo(() => {
|
||||||
|
// 首先应用精确搜索过滤
|
||||||
|
const filteredResults = exactSearch
|
||||||
|
? searchResults.filter(item => titleContainsQuery(item.title, currentQueryRef.current))
|
||||||
|
: searchResults;
|
||||||
|
|
||||||
//===== 阶段1:按 normalizedTitle-type 初步分组 =====
|
//===== 阶段1:按 normalizedTitle-type 初步分组 =====
|
||||||
const preliminaryMap = new Map<string, SearchResult[]>();
|
const preliminaryMap = new Map<string, SearchResult[]>();
|
||||||
|
|
||||||
searchResults.forEach((item) => {
|
filteredResults.forEach((item) => {
|
||||||
const normalizedTitle = normalizeTitle(item.title);
|
const normalizedTitle = normalizeTitle(item.title);
|
||||||
const type = getType(item);
|
const type = getType(item);
|
||||||
const preliminaryKey = `${normalizedTitle}-${type}`;
|
const preliminaryKey = `${normalizedTitle}-${type}`;
|
||||||
@@ -316,7 +334,7 @@ function SearchPageClient() {
|
|||||||
|
|
||||||
// 按出现顺序返回聚合结果
|
// 按出现顺序返回聚合结果
|
||||||
return keyOrder.map(key => [key, finalMap.get(key)!] as [string, SearchResult[]]);
|
return keyOrder.map(key => [key, finalMap.get(key)!] as [string, SearchResult[]]);
|
||||||
}, [searchResults]);
|
}, [searchResults, exactSearch]);
|
||||||
|
|
||||||
// 当聚合结果变化时,如果某个聚合已存在,则调用其卡片 ref 的 set 方法增量更新
|
// 当聚合结果变化时,如果某个聚合已存在,则调用其卡片 ref 的 set 方法增量更新
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -422,7 +440,13 @@ function SearchPageClient() {
|
|||||||
// 非聚合:应用筛选与排序
|
// 非聚合:应用筛选与排序
|
||||||
const filteredAllResults = useMemo(() => {
|
const filteredAllResults = useMemo(() => {
|
||||||
const { source, title, year, yearOrder } = filterAll;
|
const { source, title, year, yearOrder } = filterAll;
|
||||||
const filtered = searchResults.filter((item) => {
|
|
||||||
|
// 首先应用精确搜索过滤
|
||||||
|
const exactSearchFiltered = exactSearch
|
||||||
|
? searchResults.filter(item => titleContainsQuery(item.title, currentQueryRef.current))
|
||||||
|
: searchResults;
|
||||||
|
|
||||||
|
const filtered = exactSearchFiltered.filter((item) => {
|
||||||
if (source !== 'all' && item.source !== source) return false;
|
if (source !== 'all' && item.source !== source) return false;
|
||||||
if (title !== 'all' && item.title !== title) return false;
|
if (title !== 'all' && item.title !== title) return false;
|
||||||
if (year !== 'all' && item.year !== year) return false;
|
if (year !== 'all' && item.year !== year) return false;
|
||||||
@@ -451,7 +475,7 @@ function SearchPageClient() {
|
|||||||
a.title.localeCompare(b.title) :
|
a.title.localeCompare(b.title) :
|
||||||
b.title.localeCompare(a.title);
|
b.title.localeCompare(a.title);
|
||||||
});
|
});
|
||||||
}, [searchResults, filterAll, searchQuery]);
|
}, [searchResults, filterAll, searchQuery, exactSearch]);
|
||||||
|
|
||||||
// 聚合:应用筛选与排序
|
// 聚合:应用筛选与排序
|
||||||
const filteredAggResults = useMemo(() => {
|
const filteredAggResults = useMemo(() => {
|
||||||
@@ -575,6 +599,12 @@ function SearchPageClient() {
|
|||||||
} else if (defaultFluidSearch !== undefined) {
|
} else if (defaultFluidSearch !== undefined) {
|
||||||
setUseFluidSearch(defaultFluidSearch);
|
setUseFluidSearch(defaultFluidSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取精确搜索设置
|
||||||
|
const savedExactSearch = localStorage.getItem('exactSearch');
|
||||||
|
if (savedExactSearch !== null) {
|
||||||
|
setExactSearch(savedExactSearch === 'true');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听搜索历史更新事件
|
// 监听搜索历史更新事件
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
const [danmakuMaxCount, setDanmakuMaxCount] = useState(0);
|
const [danmakuMaxCount, setDanmakuMaxCount] = useState(0);
|
||||||
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false);
|
const [danmakuHeatmapDisabled, setDanmakuHeatmapDisabled] = useState(false);
|
||||||
const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false);
|
const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false);
|
||||||
|
const [exactSearch, setExactSearch] = useState(true);
|
||||||
|
|
||||||
// 邮件通知设置
|
// 邮件通知设置
|
||||||
const [userEmail, setUserEmail] = useState('');
|
const [userEmail, setUserEmail] = useState('');
|
||||||
@@ -472,6 +473,12 @@ export const UserMenu: React.FC = () => {
|
|||||||
if (savedSearchTraditionalToSimplified !== null) {
|
if (savedSearchTraditionalToSimplified !== null) {
|
||||||
setSearchTraditionalToSimplified(savedSearchTraditionalToSimplified === 'true');
|
setSearchTraditionalToSimplified(savedSearchTraditionalToSimplified === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载精确搜索设置
|
||||||
|
const savedExactSearch = localStorage.getItem('exactSearch');
|
||||||
|
if (savedExactSearch !== null) {
|
||||||
|
setExactSearch(savedExactSearch === 'true');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -931,6 +938,13 @@ export const UserMenu: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExactSearchToggle = (value: boolean) => {
|
||||||
|
setExactSearch(value);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('exactSearch', String(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleHomeBannerToggle = (value: boolean) => {
|
const handleHomeBannerToggle = (value: boolean) => {
|
||||||
setHomeBannerEnabled(value);
|
setHomeBannerEnabled(value);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -1899,6 +1913,30 @@ export const UserMenu: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 精确搜索 */}
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div>
|
||||||
|
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||||
|
精确搜索
|
||||||
|
</h4>
|
||||||
|
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||||
|
开启后,搜索结果将过滤掉不包含搜索词的内容
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<label className='flex items-center cursor-pointer'>
|
||||||
|
<div className='relative'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
className='sr-only peer'
|
||||||
|
checked={exactSearch}
|
||||||
|
onChange={(e) => handleExactSearchToggle(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||||
|
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user