feat: add search history

This commit is contained in:
shinya
2025-06-19 23:56:15 +08:00
parent 73541b668a
commit 0d884cb1a6
2 changed files with 142 additions and 9 deletions

View File

@@ -5,12 +5,18 @@ import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
addSearchHistory,
clearSearchHistory,
getSearchHistory,
} from '@/lib/db.client';
import PageLayout from '@/components/PageLayout';
import VideoCard from '@/components/VideoCard';
function SearchPageClient() {
// 模拟搜索历史数据
const mockSearchHistory = ['流浪地球', '三体', '狂飙', '满江红'];
// 搜索历史
const [searchHistory, setSearchHistory] = useState<string[]>([]);
// 定义搜索结果类型
type SearchResult = {
@@ -33,6 +39,12 @@ function SearchPageClient() {
useEffect(() => {
// 自动聚焦搜索框
searchInputRef.current?.focus();
// 加载搜索历史
(async () => {
const history = await getSearchHistory();
setSearchHistory(history);
})();
}, []);
useEffect(() => {
@@ -41,6 +53,12 @@ function SearchPageClient() {
if (query) {
setSearchQuery(query);
fetchSearchResults(query);
// 保存到搜索历史
addSearchHistory(query).then(async () => {
const history = await getSearchHistory();
setSearchHistory(history);
});
} else {
setShowResults(false);
}
@@ -68,10 +86,15 @@ function SearchPageClient() {
setIsLoading(true);
setShowResults(true);
// 模拟搜索延迟
setTimeout(() => {
fetchSearchResults(searchQuery);
}, 1000);
// 直接发请求
fetchSearchResults(searchQuery);
// 保存到搜索历史
addSearchHistory(searchQuery).then(async () => {
const history = await getSearchHistory();
setSearchHistory(history);
});
};
return (
@@ -114,21 +137,32 @@ function SearchPageClient() {
</div>
)}
</div>
) : mockSearchHistory.length > 0 ? (
) : searchHistory.length > 0 ? (
// 搜索历史
<section className='mb-12'>
<h2 className='mb-4 text-xl font-bold text-gray-800 text-left'>
{searchHistory.length > 0 && (
<button
onClick={async () => {
await clearSearchHistory();
setSearchHistory([]);
}}
className='ml-3 text-sm text-gray-500 hover:text-red-500 transition-colors'
>
</button>
)}
</h2>
<div className='flex flex-wrap gap-2'>
{mockSearchHistory.map((item, index) => (
{searchHistory.map((item, index) => (
<button
key={index}
onClick={() => {
setSearchQuery(item);
router.push(`/search?q=${encodeURIComponent(item)}`);
}}
className='px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full text-sm text-gray-700 transition-colors duration-200'
className='px-4 py-2 bg-gray-500/10 hover:bg-gray-300 rounded-full text-sm text-gray-700 transition-colors duration-200'
>
{item}
</button>

View File

@@ -36,6 +36,12 @@ const STORAGE_TYPE =
| 'database'
| undefined) || 'localstorage';
// ---------------- 搜索历史相关常量 ----------------
const SEARCH_HISTORY_KEY = 'moontv_search_history';
// 搜索历史最大保存条数
const SEARCH_HISTORY_LIMIT = 20;
// ---- 工具函数 ----
async function fetchFromApi<T>(path: string): Promise<T> {
const res = await fetch(path);
@@ -164,3 +170,96 @@ export async function deletePlayRecord(
throw err;
}
}
/* ---------------- 搜索历史相关 API ---------------- */
/**
* 获取搜索历史
*/
export async function getSearchHistory(): Promise<string[]> {
// 如果配置为使用数据库,则从后端 API 获取
if (STORAGE_TYPE === 'database') {
try {
return fetchFromApi<string[]>('/api/searchhistory');
} catch (err) {
console.error('获取搜索历史失败:', err);
return [];
}
}
// 默认从 localStorage 读取
if (typeof window === 'undefined') {
return [];
}
try {
const raw = localStorage.getItem(SEARCH_HISTORY_KEY);
if (!raw) return [];
const arr = JSON.parse(raw) as string[];
// 仅返回字符串数组
return Array.isArray(arr) ? arr : [];
} catch (err) {
console.error('读取搜索历史失败:', err);
return [];
}
}
/**
* 将关键字添加到搜索历史
*/
export async function addSearchHistory(keyword: string): Promise<void> {
const trimmed = keyword.trim();
if (!trimmed) return;
// 数据库模式
if (STORAGE_TYPE === 'database') {
try {
await fetch('/api/searchhistory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ keyword: trimmed }),
});
} catch (err) {
console.error('保存搜索历史失败:', err);
}
return;
}
// localStorage 模式
if (typeof window === 'undefined') return;
try {
const history = await getSearchHistory();
const newHistory = [trimmed, ...history.filter((k) => k !== trimmed)];
// 限制长度
if (newHistory.length > SEARCH_HISTORY_LIMIT) {
newHistory.length = SEARCH_HISTORY_LIMIT;
}
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory));
} catch (err) {
console.error('保存搜索历史失败:', err);
}
}
/**
* 清空搜索历史
*/
export async function clearSearchHistory(): Promise<void> {
// 数据库模式
if (STORAGE_TYPE === 'database') {
try {
await fetch('/api/searchhistory', {
method: 'DELETE',
});
} catch (err) {
console.error('清空搜索历史失败:', err);
}
return;
}
// localStorage 模式
if (typeof window === 'undefined') return;
localStorage.removeItem(SEARCH_HISTORY_KEY);
}