mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(spotify): frontend plumbing (source tab, badge, auth status)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e1171dbcfe
commit
50bdaae26a
5 files changed
+24
-11
No files matched your search
@@ -7,8 +7,8 @@
|
|||||||
<span class="song-name">{{ song.name }}</span>
|
<span class="song-name">{{ song.name }}</span>
|
||||||
<span
|
<span
|
||||||
class="platform-badge"
|
class="platform-badge"
|
||||||
:class="song.platform === 'bilibili' ? 'badge-bilibili' : song.platform === 'qq' ? 'badge-qq' : song.platform === 'youtube' ? 'badge-youtube' : song.platform === 'local' ? 'badge-local' : song.platform === 'kugou' ? 'badge-kugou' : 'badge-netease'"
|
:class="song.platform === 'bilibili' ? 'badge-bilibili' : song.platform === 'qq' ? 'badge-qq' : song.platform === 'youtube' ? 'badge-youtube' : song.platform === 'local' ? 'badge-local' : song.platform === 'kugou' ? 'badge-kugou' : song.platform === 'spotify' ? 'badge-spotify' : 'badge-netease'"
|
||||||
>{{ song.platform === 'bilibili' ? 'B站' : song.platform === 'qq' ? 'QQ' : song.platform === 'youtube' ? 'YouTube' : song.platform === 'local' ? '本地' : song.platform === 'kugou' ? '酷狗' : '网易云' }}</span>
|
>{{ song.platform === 'bilibili' ? 'B站' : song.platform === 'qq' ? 'QQ' : song.platform === 'youtube' ? 'YouTube' : song.platform === 'local' ? '本地' : song.platform === 'kugou' ? '酷狗' : song.platform === 'spotify' ? 'Spotify' : '网易云' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-artist">{{ song.artist }}</div>
|
<div class="song-artist">{{ song.artist }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,6 +145,11 @@ function formatDuration(seconds: number): string {
|
|||||||
color: var(--brand-kugou);
|
color: var(--brand-kugou);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-spotify {
|
||||||
|
background: var(--brand-spotify-12);
|
||||||
|
color: var(--brand-spotify);
|
||||||
|
}
|
||||||
|
|
||||||
.song-artist {
|
.song-artist {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const LABELS: Record<Source, string> = {
|
|||||||
netease: '网易云',
|
netease: '网易云',
|
||||||
qq: 'QQ',
|
qq: 'QQ',
|
||||||
kugou: '酷狗',
|
kugou: '酷狗',
|
||||||
|
spotify: 'Spotify',
|
||||||
};
|
};
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export interface Song {
|
|||||||
album: string;
|
album: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
coverUrl: string;
|
coverUrl: string;
|
||||||
platform: 'netease' | 'qq' | 'bilibili' | 'youtube' | 'local' | 'kugou';
|
platform: 'netease' | 'qq' | 'bilibili' | 'youtube' | 'local' | 'kugou' | 'spotify';
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Source = 'netease' | 'qq' | 'kugou';
|
export type Source = 'netease' | 'qq' | 'kugou' | 'spotify';
|
||||||
|
|
||||||
export interface BotStatus {
|
export interface BotStatus {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -100,11 +100,11 @@ export const usePlayerStore = defineStore('player', {
|
|||||||
theme: 'dark' as 'dark' | 'light',
|
theme: 'dark' as 'dark' | 'light',
|
||||||
|
|
||||||
// Home page cache, split by source
|
// Home page cache, split by source
|
||||||
recommendPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[], kugou: [] as PlaylistItem[] },
|
recommendPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[], kugou: [] as PlaylistItem[], spotify: [] as PlaylistItem[] },
|
||||||
dailySongs: { netease: [] as Song[], qq: [] as Song[], kugou: [] as Song[] },
|
dailySongs: { netease: [] as Song[], qq: [] as Song[], kugou: [] as Song[], spotify: [] as Song[] },
|
||||||
userPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[], kugou: [] as PlaylistItem[] },
|
userPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[], kugou: [] as PlaylistItem[], spotify: [] as PlaylistItem[] },
|
||||||
bilibiliPopular: [] as Song[],
|
bilibiliPopular: [] as Song[],
|
||||||
authStatus: { netease: false, qq: false, kugou: false },
|
authStatus: { netease: false, qq: false, kugou: false, spotify: false },
|
||||||
lastFetchTime: 0,
|
lastFetchTime: 0,
|
||||||
|
|
||||||
// Favorited playlists (fetched from server, isolated per WebUI user)
|
// Favorited playlists (fetched from server, isolated per WebUI user)
|
||||||
@@ -158,6 +158,7 @@ export const usePlayerStore = defineStore('player', {
|
|||||||
if (this.authStatus.netease) s.push('netease');
|
if (this.authStatus.netease) s.push('netease');
|
||||||
if (this.authStatus.qq) s.push('qq');
|
if (this.authStatus.qq) s.push('qq');
|
||||||
if (this.authStatus.kugou) s.push('kugou');
|
if (this.authStatus.kugou) s.push('kugou');
|
||||||
|
if (this.authStatus.spotify) s.push('spotify');
|
||||||
return s;
|
return s;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -572,23 +573,27 @@ export const usePlayerStore = defineStore('player', {
|
|||||||
// Always check auth status first — if it changed since the cached
|
// Always check auth status first — if it changed since the cached
|
||||||
// fetch (e.g., user logged in/out as a different account), the
|
// fetch (e.g., user logged in/out as a different account), the
|
||||||
// cached playlists belong to a different user and we MUST refetch.
|
// cached playlists belong to a different user and we MUST refetch.
|
||||||
const [neAuthRes, qqAuthRes, kugouAuthRes] = await Promise.allSettled([
|
const [neAuthRes, qqAuthRes, kugouAuthRes, spAuthRes] = await Promise.allSettled([
|
||||||
axios.get('/api/auth/status', { params: { platform: 'netease' } }),
|
axios.get('/api/auth/status', { params: { platform: 'netease' } }),
|
||||||
axios.get('/api/auth/status', { params: { platform: 'qq' } }),
|
axios.get('/api/auth/status', { params: { platform: 'qq' } }),
|
||||||
axios.get('/api/auth/status', { params: { platform: 'kugou' } }),
|
axios.get('/api/auth/status', { params: { platform: 'kugou' } }),
|
||||||
|
axios.get('/api/auth/status', { params: { platform: 'spotify' } }),
|
||||||
]);
|
]);
|
||||||
const newAuth = {
|
const newAuth = {
|
||||||
netease: neAuthRes.status === 'fulfilled' && !!neAuthRes.value.data?.loggedIn,
|
netease: neAuthRes.status === 'fulfilled' && !!neAuthRes.value.data?.loggedIn,
|
||||||
qq: qqAuthRes.status === 'fulfilled' && !!qqAuthRes.value.data?.loggedIn,
|
qq: qqAuthRes.status === 'fulfilled' && !!qqAuthRes.value.data?.loggedIn,
|
||||||
kugou: kugouAuthRes.status === 'fulfilled' && !!kugouAuthRes.value.data?.loggedIn,
|
kugou: kugouAuthRes.status === 'fulfilled' && !!kugouAuthRes.value.data?.loggedIn,
|
||||||
|
spotify: spAuthRes.status === 'fulfilled' && !!spAuthRes.value.data?.loggedIn,
|
||||||
};
|
};
|
||||||
const authChanged =
|
const authChanged =
|
||||||
newAuth.netease !== this.authStatus.netease ||
|
newAuth.netease !== this.authStatus.netease ||
|
||||||
newAuth.qq !== this.authStatus.qq ||
|
newAuth.qq !== this.authStatus.qq ||
|
||||||
newAuth.kugou !== this.authStatus.kugou;
|
newAuth.kugou !== this.authStatus.kugou ||
|
||||||
|
newAuth.spotify !== this.authStatus.spotify;
|
||||||
this.authStatus.netease = newAuth.netease;
|
this.authStatus.netease = newAuth.netease;
|
||||||
this.authStatus.qq = newAuth.qq;
|
this.authStatus.qq = newAuth.qq;
|
||||||
this.authStatus.kugou = newAuth.kugou;
|
this.authStatus.kugou = newAuth.kugou;
|
||||||
|
this.authStatus.spotify = newAuth.spotify;
|
||||||
|
|
||||||
// Favorites are user-local and cheap; always refresh them, even on a
|
// Favorites are user-local and cheap; always refresh them, even on a
|
||||||
// home-data cache hit, so hearts stay correct across tabs/sessions.
|
// home-data cache hit, so hearts stay correct across tabs/sessions.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function readAll(): Partial<Record<TabKey, Source>> {
|
|||||||
export function loadTabSource(key: TabKey, fallback: Source = 'netease'): Source {
|
export function loadTabSource(key: TabKey, fallback: Source = 'netease'): Source {
|
||||||
const all = readAll();
|
const all = readAll();
|
||||||
const v = all[key];
|
const v = all[key];
|
||||||
return v === 'netease' || v === 'qq' || v === 'kugou' ? v : fallback;
|
return v === 'netease' || v === 'qq' || v === 'kugou' || v === 'spotify' ? v : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveTabSource(key: TabKey, value: Source): void {
|
export function saveTabSource(key: TabKey, value: Source): void {
|
||||||
|
|||||||
@@ -82,6 +82,8 @@
|
|||||||
--brand-youtube-12: rgba(255, 0, 0, 0.12);
|
--brand-youtube-12: rgba(255, 0, 0, 0.12);
|
||||||
--brand-kugou: #2ca2f9;
|
--brand-kugou: #2ca2f9;
|
||||||
--brand-kugou-12: rgba(44, 162, 249, 0.12);
|
--brand-kugou-12: rgba(44, 162, 249, 0.12);
|
||||||
|
--brand-spotify: #1DB954;
|
||||||
|
--brand-spotify-12: rgba(29, 185, 84, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dark theme (default)
|
// Dark theme (default)
|
||||||
|
|||||||
Reference in new issue
Block a user