feat(#119): saved queues web UI + behavior-settings toggles

- Settings → 行为设置: two new toggles (保存/加载播放清单, 单曲直接播放不清空队列)
  that round-trip savedQueuesEnabled / playKeepsQueue and keep the nav gate in sync.
- New "已存队列" page (/saved-queues): save the current queue (with a 共享 option),
  load (replace) / append / delete saved queues; renders a "feature disabled"
  state on 403 so it degrades gracefully when the flag is off.
- Nav entry gated on the savedQueuesEnabled store flag (hidden for guests).
- useSavedQueues API composable + a pure, unit-tested list/ownership helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Fable 5 committed 2026-07-17 01:29:00 +08:00
1 parent 69a2e8c264
commit 0b10f9553c
9 files changed
+488

No files matched your search

+16
View File
@@ -115,6 +115,10 @@ export const usePlayerStore = defineStore('player', {
enabledProviders: [] as string[],
defaultSource: 'netease' as string,
// Whether the admin enabled save/load queues (#119). Gates the nav entry
// and the /saved-queues page. Fetched from /api/bot/settings (non-guest).
savedQueuesEnabled: false,
// Home page cache, split by source
recommendPlaylists: { netease: [] as PlaylistItem[], qq: [] as PlaylistItem[], kugou: [] as PlaylistItem[], spotify: [] as PlaylistItem[] },
dailySongs: { netease: [] as Song[], qq: [] as Song[], kugou: [] as Song[], spotify: [] as Song[] },
@@ -595,6 +599,18 @@ export const usePlayerStore = defineStore('player', {
return this.favoritedPlaylists.some((f) => f.playlistId === playlistId && f.platform === platform);
},
/** Load global bot settings we care about client-side (currently just the
* saved-queues gate). Guests get a 403 here, which we swallow so the nav
* entry simply stays hidden for them. */
async fetchBotSettings() {
try {
const res = await axios.get('/api/bot/settings');
this.savedQueuesEnabled = res.data?.savedQueuesEnabled === true;
} catch {
// not critical (guests 403; keep previous value otherwise)
}
},
/** Load the server-side source gate (enabledProviders + default). */
async fetchProviders() {
try {