From 0d884cb1a62e33e3a7cca79c8af0d7afeba56bc7 Mon Sep 17 00:00:00 2001 From: shinya Date: Thu, 19 Jun 2025 23:56:15 +0800 Subject: [PATCH] feat: add search history --- src/app/search/page.tsx | 52 ++++++++++++++++++---- src/lib/db.client.ts | 99 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 9 deletions(-) diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index 965e2a4..962a089 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -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([]); // 定义搜索结果类型 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() { )} - ) : mockSearchHistory.length > 0 ? ( + ) : searchHistory.length > 0 ? ( // 搜索历史

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

- {mockSearchHistory.map((item, index) => ( + {searchHistory.map((item, index) => ( diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts index 821e793..5534e73 100644 --- a/src/lib/db.client.ts +++ b/src/lib/db.client.ts @@ -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(path: string): Promise { const res = await fetch(path); @@ -164,3 +170,96 @@ export async function deletePlayRecord( throw err; } } + +/* ---------------- 搜索历史相关 API ---------------- */ + +/** + * 获取搜索历史 + */ +export async function getSearchHistory(): Promise { + // 如果配置为使用数据库,则从后端 API 获取 + if (STORAGE_TYPE === 'database') { + try { + return fetchFromApi('/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 { + 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 { + // 数据库模式 + 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); +}