From acb1d4e7ecf179b06a6a3d2c3117b913277afafc Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 27 May 2026 12:38:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9F=B3=E4=B9=90=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E6=90=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../music/v2/discovery/hot-search/route.ts | 62 ++++++++-- src/app/music/search/page.tsx | 114 ++++++++++++++++-- 2 files changed, 158 insertions(+), 18 deletions(-) diff --git a/src/app/api/music/v2/discovery/hot-search/route.ts b/src/app/api/music/v2/discovery/hot-search/route.ts index 6c6ae47..5e47ffc 100644 --- a/src/app/api/music/v2/discovery/hot-search/route.ts +++ b/src/app/api/music/v2/discovery/hot-search/route.ts @@ -5,25 +5,65 @@ import { badRequest, internalError } from '@/lib/music-v2-api'; export const runtime = 'nodejs'; +type HotSearchItem = { keyword: string; name: string; artist: string; source: string }; +type LxHotSearchPayload = + | string[] + | Array<{ name?: string; keyword?: string; word?: string; singer?: string; source?: string }> + | { source?: string; list?: string[] | Array<{ name?: string; keyword?: string; word?: string; singer?: string; source?: string }> }; + +function normalizeHotSearchPayload(payload: LxHotSearchPayload, fallbackSource: string): HotSearchItem[] { + const payloadSource = Array.isArray(payload) ? fallbackSource : payload?.source || fallbackSource; + const rawList = Array.isArray(payload) ? payload : payload?.list; + if (!Array.isArray(rawList)) return []; + + return rawList + .map((item) => { + if (typeof item === 'string') { + return { keyword: item, name: item, artist: '', source: payloadSource }; + } + const keyword = item.name || item.keyword || item.word || ''; + return { + keyword, + name: keyword, + artist: item.singer || '', + source: item.source || payloadSource, + }; + }) + .filter((item) => item.keyword); +} + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const source = searchParams.get('source') || 'mg'; if (!isMusicSource(source)) return badRequest('不支持的音源'); - const list = await lxGetJson>(`/api/music/hotSearch?source=${source}`, 'none'); + const fallbackSources = [source, 'mg', 'kw', 'tx', 'wy', 'kg'].filter((item, index, arr) => arr.indexOf(item) === index); + let list: HotSearchItem[] = []; - return NextResponse.json({ - success: true, - data: { - list: list.map(item => ({ - keyword: item.name, - name: item.name, - artist: item.singer || '', - source: item.source, - })), + for (const candidate of fallbackSources) { + try { + const payload = await lxGetJson(`/api/music/hotSearch?source=${candidate}`, 'none'); + list = normalizeHotSearchPayload(payload, candidate); + if (list.length > 0) break; + } catch { + continue; + } + } + + return NextResponse.json( + { + success: true, + data: { + list, + }, }, - }); + { + headers: { + 'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=3600', + }, + } + ); } catch (error) { return internalError('获取热搜失败', (error as Error).message); } diff --git a/src/app/music/search/page.tsx b/src/app/music/search/page.tsx index 7566e3d..cb2f395 100644 --- a/src/app/music/search/page.tsx +++ b/src/app/music/search/page.tsx @@ -2,12 +2,22 @@ import { useEffect, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; +import { Flame, RefreshCw } from 'lucide-react'; import { playMusicList } from '@/lib/music/actions'; import MusicLoadingIndicator from '@/components/music/MusicLoadingIndicator'; import SongList from '@/components/music/SongList'; import { mapSong, musicSources, normalizeSource } from '@/lib/music/shared'; import type { Song } from '@/lib/music/types'; +type HotSearchItem = { keyword: string; artist?: string }; + +const HOT_SEARCH_CACHE_DURATION = 60 * 60 * 1000; +const HOT_SEARCH_LIMIT = 20; + +function getHotSearchCacheKey(source: string) { + return `music_hot_search_${source}`; +} + export default function MusicSearchPage() { const router = useRouter(); const searchParams = useSearchParams(); @@ -16,12 +26,67 @@ export default function MusicSearchPage() { const [keyword, setKeyword] = useState(q); const [selectedSource, setSelectedSource] = useState(source); const [songs, setSongs] = useState([]); + const [hotSearches, setHotSearches] = useState([]); + const [hotLoading, setHotLoading] = useState(false); const [loading, setLoading] = useState(false); const [showSourceMenu, setShowSourceMenu] = useState(false); + const loadHotSearch = async (forceRefresh = false) => { + const cacheKey = getHotSearchCacheKey(source); + let cachedData: HotSearchItem[] | null = null; + let cacheExpired = true; + + if (!forceRefresh) { + try { + const cached = localStorage.getItem(cacheKey); + if (cached) { + const { data, timestamp } = JSON.parse(cached); + if (Array.isArray(data)) { + cachedData = data; + cacheExpired = Date.now() - Number(timestamp || 0) > HOT_SEARCH_CACHE_DURATION; + } + } + } catch { + cachedData = null; + } + } + + if (cachedData) { + setHotSearches(cachedData); + setHotLoading(false); + } else { + setHotSearches([]); + setHotLoading(true); + } + + if (!cachedData || cacheExpired || forceRefresh) { + try { + const res = await fetch(`/api/music/v2/discovery/hot-search?source=${source}`); + const data = await res.json(); + if (data.success) { + const nextHotSearches = (data.data?.list || []).slice(0, HOT_SEARCH_LIMIT); + setHotSearches(nextHotSearches); + try { + localStorage.setItem(cacheKey, JSON.stringify({ data: nextHotSearches, timestamp: Date.now() })); + } catch { + // ignore cache write failure + } + } else if (!cachedData) { + setHotSearches([]); + } + } catch { + if (!cachedData) setHotSearches([]); + } finally { + setHotLoading(false); + } + } + }; + useEffect(() => { setSelectedSource(source); setKeyword(q); + void loadHotSearch(); + if (!q) { setSongs([]); return; @@ -144,14 +209,49 @@ export default function MusicSearchPage() { ) : q ? ( ) : ( -
-
- - - +
+
+ +
+
热门搜索
+
当前音源:{currentSourceLabel}
+
+
+ {hotLoading ? ( + + ) : hotSearches.length > 0 ? ( +
+ {hotSearches.map((item, index) => ( + + ))} +
+ ) : ( +
暂无热搜数据
+ )} +
+
-
开始你的音乐探索
-
在上方输入你想听的歌曲、歌手或专辑,我们为你搜罗全网好音乐。
)}