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
+66
-17
@@ -11,6 +11,8 @@ export interface Song {
|
||||
platform: 'netease' | 'qq' | 'bilibili' | 'youtube';
|
||||
}
|
||||
|
||||
export type Source = 'netease' | 'qq';
|
||||
|
||||
export interface BotStatus {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -54,11 +56,12 @@ export const usePlayerStore = defineStore('player', {
|
||||
timings: {} as Record<string, TimingState>,
|
||||
theme: 'dark' as 'dark' | 'light',
|
||||
|
||||
// Home page cache
|
||||
recommendPlaylists: [] as PlaylistItem[],
|
||||
dailySongs: [] as Song[],
|
||||
userPlaylists: [] as PlaylistItem[],
|
||||
// Home page cache, split by source
|
||||
recommendPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[] },
|
||||
dailySongs: { netease: [] as Song[], qq: [] as Song[] },
|
||||
userPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[] },
|
||||
bilibiliPopular: [] as Song[],
|
||||
authStatus: { netease: false, qq: false },
|
||||
lastFetchTime: 0,
|
||||
}),
|
||||
|
||||
@@ -354,24 +357,70 @@ export const usePlayerStore = defineStore('player', {
|
||||
return;
|
||||
}
|
||||
|
||||
const [playlistRes, dailyRes, userRes, biliRes] = await Promise.allSettled([
|
||||
axios.get('/api/music/recommend/playlists'),
|
||||
axios.get('/api/music/recommend/songs'),
|
||||
axios.get('/api/music/user/playlists'),
|
||||
axios.get('/api/music/bilibili/popular?limit=12'),
|
||||
// 1. Fetch auth status for both platforms first.
|
||||
const [neAuthRes, qqAuthRes] = await Promise.allSettled([
|
||||
axios.get('/api/auth/status', { params: { platform: 'netease' } }),
|
||||
axios.get('/api/auth/status', { params: { platform: 'qq' } }),
|
||||
]);
|
||||
this.authStatus.netease =
|
||||
neAuthRes.status === 'fulfilled' && !!neAuthRes.value.data?.loggedIn;
|
||||
this.authStatus.qq =
|
||||
qqAuthRes.status === 'fulfilled' && !!qqAuthRes.value.data?.loggedIn;
|
||||
|
||||
// 2. NetEase data: recommend playlists work anonymously; daily/user
|
||||
// playlists need login but Promise.allSettled isolates failures.
|
||||
const neteasePromises = [
|
||||
axios.get('/api/music/recommend/playlists', { params: { platform: 'netease' } }),
|
||||
axios.get('/api/music/recommend/songs', { params: { platform: 'netease' } }),
|
||||
axios.get('/api/music/user/playlists', { params: { platform: 'netease' } }),
|
||||
];
|
||||
|
||||
// 3. QQ data: only fetch when QQ is logged in. When not logged in,
|
||||
// resolve to empty payloads so the same indexed handling works.
|
||||
const emptyPlaylists = { data: { playlists: [] } };
|
||||
const emptySongs = { data: { songs: [] } };
|
||||
const qqPromises = this.authStatus.qq
|
||||
? [
|
||||
axios.get('/api/music/recommend/playlists', { params: { platform: 'qq' } }),
|
||||
axios.get('/api/music/recommend/songs', { params: { platform: 'qq' } }),
|
||||
axios.get('/api/music/user/playlists', { params: { platform: 'qq' } }),
|
||||
]
|
||||
: [
|
||||
Promise.resolve(emptyPlaylists),
|
||||
Promise.resolve(emptySongs),
|
||||
Promise.resolve(emptyPlaylists),
|
||||
];
|
||||
|
||||
const biliPromise = axios.get('/api/music/bilibili/popular?limit=12');
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
...neteasePromises,
|
||||
...qqPromises,
|
||||
biliPromise,
|
||||
]);
|
||||
|
||||
if (playlistRes.status === 'fulfilled') {
|
||||
this.recommendPlaylists = playlistRes.value.data.playlists;
|
||||
const [neRecPL, neDaily, neUserPL, qqRecPL, qqDaily, qqUserPL, bili] = results;
|
||||
|
||||
if (neRecPL.status === 'fulfilled') {
|
||||
this.recommendPlaylists.netease = neRecPL.value.data.playlists ?? [];
|
||||
}
|
||||
if (dailyRes.status === 'fulfilled') {
|
||||
this.dailySongs = dailyRes.value.data.songs;
|
||||
if (neDaily.status === 'fulfilled') {
|
||||
this.dailySongs.netease = neDaily.value.data.songs ?? [];
|
||||
}
|
||||
if (userRes.status === 'fulfilled') {
|
||||
this.userPlaylists = userRes.value.data.playlists;
|
||||
if (neUserPL.status === 'fulfilled') {
|
||||
this.userPlaylists.netease = neUserPL.value.data.playlists ?? [];
|
||||
}
|
||||
if (biliRes.status === 'fulfilled') {
|
||||
this.bilibiliPopular = biliRes.value.data.songs;
|
||||
if (qqRecPL.status === 'fulfilled') {
|
||||
this.recommendPlaylists.qq = qqRecPL.value.data.playlists ?? [];
|
||||
}
|
||||
if (qqDaily.status === 'fulfilled') {
|
||||
this.dailySongs.qq = qqDaily.value.data.songs ?? [];
|
||||
}
|
||||
if (qqUserPL.status === 'fulfilled') {
|
||||
this.userPlaylists.qq = qqUserPL.value.data.playlists ?? [];
|
||||
}
|
||||
if (bili.status === 'fulfilled') {
|
||||
this.bilibiliPopular = bili.value.data.songs ?? [];
|
||||
}
|
||||
|
||||
this.lastFetchTime = Date.now();
|
||||
|
||||
Reference in new issue
Block a user