mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(web): per-platform source tabs on Home and Library
Recommend playlists, daily songs, and user playlists on Home now show a [网易云][QQ] tab when both platforms are logged in. Library 我的歌单 gets the same tab. Selection persists per-section in localStorage and falls back gracefully when the persisted source becomes unavailable (e.g., user logged out). Removes dead 我的收藏 block from Library that referenced a non-existent /api/music/user/liked endpoint. Spec: docs/superpowers/specs/2026-05-06-music-source-tabs-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a216709227
commit
ac55a346be
4 files changed
+199
-61
No files matched your search
+27
-30
@@ -3,14 +3,15 @@
|
||||
<h1 class="page-title">音乐库</h1>
|
||||
|
||||
<!-- 我的歌单 -->
|
||||
<section class="section" v-if="store.userPlaylists.length > 0">
|
||||
<section class="section" v-if="userAvailable.length > 0">
|
||||
<h2 class="section-title">
|
||||
我的歌单
|
||||
<span class="section-count">{{ store.userPlaylists.length }}</span>
|
||||
<span class="section-count">{{ currentUserPlaylists.length }}</span>
|
||||
<SourceTabs v-model="userSource" :sources="userAvailable" />
|
||||
</h2>
|
||||
<div class="playlist-grid">
|
||||
<RouterLink
|
||||
v-for="pl in store.userPlaylists"
|
||||
v-for="pl in currentUserPlaylists"
|
||||
:key="pl.id"
|
||||
:to="`/playlist/${pl.id}?platform=${pl.platform}`"
|
||||
class="playlist-card hover-scale"
|
||||
@@ -40,23 +41,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 我的收藏 -->
|
||||
<section class="section" v-if="liked.length > 0">
|
||||
<h2 class="section-title">我的收藏</h2>
|
||||
<div class="song-list">
|
||||
<SongCard
|
||||
v-for="(song, i) in liked.slice(0, 10)"
|
||||
:key="`fav-${song.id}-${i}`"
|
||||
:song="song"
|
||||
:index="i + 1"
|
||||
:active="store.currentSong?.id === song.id"
|
||||
@play="store.play(song.name, song.platform)"
|
||||
@add="store.addToQueue(song.name, song.platform)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="!historyLoading && store.userPlaylists.length === 0 && history.length === 0 && liked.length === 0" class="empty-state">
|
||||
<div v-if="!historyLoading && userAvailable.length === 0 && history.length === 0" class="empty-state">
|
||||
<Icon icon="mdi:music-box-outline" class="empty-icon" />
|
||||
<div>登录网易云或QQ音乐后,这里将显示你的歌单和播放记录</div>
|
||||
</div>
|
||||
@@ -64,19 +49,38 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import axios from 'axios';
|
||||
import { usePlayerStore, type Song } from '../stores/player.js';
|
||||
import { usePlayerStore, type Song, type Source } from '../stores/player.js';
|
||||
import { loadTabSource, saveTabSource } from '../stores/sourceTabs.js';
|
||||
import CoverArt from '../components/CoverArt.vue';
|
||||
import SongCard from '../components/SongCard.vue';
|
||||
import SourceTabs from '../components/SourceTabs.vue';
|
||||
|
||||
const store = usePlayerStore();
|
||||
|
||||
const history = ref<Song[]>([]);
|
||||
const liked = ref<Song[]>([]);
|
||||
const historyLoading = ref(true);
|
||||
|
||||
const userAvailable = computed<Source[]>(() => {
|
||||
const s: Source[] = [];
|
||||
if (store.authStatus.netease) s.push('netease');
|
||||
if (store.authStatus.qq) s.push('qq');
|
||||
return s;
|
||||
});
|
||||
|
||||
const userSource = ref<Source>(loadTabSource('library.user'));
|
||||
watch(userSource, (v) => saveTabSource('library.user', v));
|
||||
|
||||
const userSourceSafe = computed<Source>(() =>
|
||||
userAvailable.value.includes(userSource.value)
|
||||
? userSource.value
|
||||
: userAvailable.value[0] ?? 'netease'
|
||||
);
|
||||
|
||||
const currentUserPlaylists = computed(() => store.userPlaylists[userSourceSafe.value] ?? []);
|
||||
|
||||
onMounted(async () => {
|
||||
if (!store.activeBotId) {
|
||||
await store.fetchBots();
|
||||
@@ -91,13 +95,6 @@ onMounted(async () => {
|
||||
} catch {
|
||||
// API may not be ready
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.get('/api/music/user/liked');
|
||||
liked.value = res.data.songs ?? [];
|
||||
} catch {
|
||||
// Liked songs API may not exist yet
|
||||
}
|
||||
}
|
||||
|
||||
historyLoading.value = false;
|
||||
|
||||
Reference in new issue
Block a user