feat(search): add tabbed category navigation + align NetEase result counts

- Replace single-page layout with pill-slider tabs (单曲/专辑/歌单) under
  the search box, showing only one category at a time with result counts
- NetEase album/playlist search limit raised from 5 to 10 to match QQ

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
NoSetViolinandClaude Opus 4.7 committed 2026-05-14 16:56:18 +08:00
1 parent ea6501ea81
commit 997bb17ceb
2 files changed
+72 -9

No files matched your search

+2 -2
View File
@@ -112,12 +112,12 @@ export class NeteaseProvider implements MusicProvider {
params: {
keywords: query,
type: 1000,
limit: 5,
limit: 10,
...this.cookieParams,
},
}),
this.api.get("/cloudsearch", {
params: { keywords: query, type: 10, limit: 5, ...this.cookieParams },
params: { keywords: query, type: 10, limit: 10, ...this.cookieParams },
}),
]);
+70 -7
View File
@@ -21,8 +21,31 @@
<div v-if="loading" class="loading">搜索中...</div>
<template v-else-if="songs.length || albums.length || playlists.length">
<section v-if="albums.length" class="result-section">
<h2 class="section-title">专辑</h2>
<div class="tab-bar">
<button
class="tab"
:class="{ active: activeTab === 'songs' }"
@click="activeTab = 'songs'"
>
单曲<span class="tab-count">{{ songs.length }}</span>
</button>
<button
class="tab"
:class="{ active: activeTab === 'albums' }"
@click="activeTab = 'albums'"
>
专辑<span class="tab-count">{{ albums.length }}</span>
</button>
<button
class="tab"
:class="{ active: activeTab === 'playlists' }"
@click="activeTab = 'playlists'"
>
歌单<span class="tab-count">{{ playlists.length }}</span>
</button>
</div>
<section v-if="activeTab === 'albums' && albums.length" class="result-section">
<div class="card-grid">
<router-link
v-for="al in albums"
@@ -40,8 +63,7 @@
</div>
</section>
<section v-if="playlists.length" class="result-section">
<h2 class="section-title">歌单</h2>
<section v-if="activeTab === 'playlists' && playlists.length" class="result-section">
<div class="card-grid">
<router-link
v-for="pl in playlists"
@@ -58,8 +80,7 @@
</div>
</section>
<section v-if="songs.length" class="result-section">
<h2 class="section-title">单曲</h2>
<section v-if="activeTab === 'songs' && songs.length" class="result-section">
<SongCard
v-for="(song, i) in songs"
:key="`${song.platform}-${song.id}`"
@@ -91,6 +112,7 @@ const store = usePlayerStore();
const route = useRoute();
const query = ref((route.query.q as string) || '');
const activeTab = ref<'songs' | 'albums' | 'playlists'>('songs');
interface Album { id: string; name: string; artist: string; coverUrl: string; songCount?: number; platform: string; }
interface Playlist { id: string; name: string; coverUrl: string; songCount?: number; platform: string; }
@@ -105,6 +127,7 @@ async function doSearch() {
if (!query.value.trim()) return;
loading.value = true;
searched.value = true;
activeTab.value = 'songs';
try {
const res = await axios.get('/api/music/search/all', { params: { q: query.value } });
songs.value = res.data.songs ?? [];
@@ -200,9 +223,49 @@ onMounted(() => {
gap: 2px;
}
.tab-bar {
display: flex;
gap: 6px;
margin-bottom: 24px;
padding: 4px;
background: var(--bg-card);
border-radius: var(--radius-md);
width: fit-content;
}
.tab {
padding: 8px 20px;
border-radius: calc(var(--radius-md) - 2px);
font-size: 14px;
font-family: inherit;
font-weight: var(--fw-semi);
color: var(--text-secondary);
background: transparent;
cursor: pointer;
transition: all var(--transition-fast);
white-space: nowrap;
&:hover { color: var(--text-primary); }
&.active {
background: var(--color-primary);
color: #fff;
.tab-count { opacity: 0.85; }
}
}
.tab-count {
margin-left: 5px;
opacity: 0.55;
font-weight: var(--fw-regular);
font-size: 13px;
&::before { content: '('; }
&::after { content: ')'; }
}
.result-section {
margin-bottom: 32px;
.section-title { font-size: 18px; margin: 0 0 12px; opacity: 0.85; }
}
.card-grid {
display: grid;