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

+34
View File
@@ -0,0 +1,34 @@
import { describe, it, expect } from 'vitest';
import { isShared, sortQueues, SHARED_OWNER, type SavedQueueMeta } from './savedQueues';
const meta = (over: Partial<SavedQueueMeta>): SavedQueueMeta => ({
id: 1,
ownerId: 'u1',
name: 'q',
songCount: 0,
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
...over,
});
describe('savedQueues helper', () => {
it('flags the shared owner', () => {
expect(isShared({ ownerId: SHARED_OWNER })).toBe(true);
expect(isShared({ ownerId: 'u1' })).toBe(false);
});
it('sorts by updatedAt descending (newest first)', () => {
const out = sortQueues([
meta({ id: 1, updatedAt: '2026-01-01' }),
meta({ id: 2, updatedAt: '2026-02-01' }),
meta({ id: 3, updatedAt: '2026-01-15' }),
]);
expect(out.map((q) => q.id)).toEqual([2, 3, 1]);
});
it('does not mutate the input array', () => {
const input = [meta({ id: 1, updatedAt: '2026-01-01' }), meta({ id: 2, updatedAt: '2026-02-01' })];
sortQueues(input);
expect(input.map((q) => q.id)).toEqual([1, 2]);
});
});