# Multi-Source Tabs for Recommend / User Playlists / Daily Songs **Date:** 2026-05-06 **Status:** Spec — pending implementation ## Problem Home 和 Library 页面的"推荐歌单 / 每日推荐 / 我的歌单"这三类内容当前硬编码只走网易云。当用户同时登录了网易云和 QQ 音乐时,无法在 Web UI 上看到 QQ 侧的对应内容、也无法切换查看。 ## Goal 在以下 4 个 section 上提供"网易云 / QQ"来源切换 tab,桌面端和移动端均可用: - `Home.vue` — 推荐歌单 - `Home.vue` — 每日推荐 - `Home.vue` — 我的歌单 - `Library.vue` — 我的歌单 切换为纯前端动作(数据已预先 fetch),无加载闪烁。各 section 的选择独立持久化。 ## Out of Scope - 私人 FM(QQ 无对应概念) - B 站热门(独立第三来源,不属于网易/QQ 切换语义) - 最近播放(bot 维度的播放历史,与音乐源无关) - Library 现有的"我的收藏"段落 —— 当前调用的 `/api/music/user/liked` 端点不存在,是死代码,本次顺手移除 - 登录状态实时同步(用户在 Settings 登录后需手动刷新 Home/Library 才能看到 QQ tab) - Tab 排序、隐藏、拖动等高级配置 ## Non-functional Constraints - 桌面端(>768px)和移动端(≤768px)布局均可用,tab 与 section title 同行排布;空间不足时允许 flex-wrap - Tab 触控区域有效高度 ≥36px - 现有 5 分钟 home data cache 行为保留 - 不引入新的后端端点(后端已通过 `?platform=` 参数支持多源) ## Architecture ### 数据层(`web/src/stores/player.ts`) 字段从单平台改为按 platform 切分: ```ts // 前 recommendPlaylists: PlaylistItem[] userPlaylists: PlaylistItem[] dailySongs: Song[] // 后 recommendPlaylists: { netease: PlaylistItem[]; qq: PlaylistItem[] } userPlaylists: { netease: PlaylistItem[]; qq: PlaylistItem[] } dailySongs: { netease: Song[]; qq: Song[] } // 新增 authStatus: { netease: boolean; qq: boolean } ``` `fetchHomeData()` 改写: 1. 并发调用 `/api/auth/status?platform=netease` 与 `?platform=qq`,写入 `authStatus` 2. 网易云的三类数据照常 fetch(推荐歌单匿名可访问;每日推荐和我的歌单需登录,未登录时 API 自然返回空或失败,`Promise.allSettled` 已隔离) 3. QQ 的三类数据**仅在 QQ 登录时** fetch,未登录则为空数组 4. B 站热门保持原样 5. 5 分钟缓存 TTL 不变 ### UI 组件 新增 `web/src/components/SourceTabs.vue`: ```vue ``` Props: - `sources: ('netease' | 'qq')[]` — 由父组件根据 auth 状态过滤后传入 - `modelValue: 'netease' | 'qq'` — v-model 绑定 行为: - `sources.length < 2` 时组件**自身不渲染**(返回空),父组件无需 v-if 包装 - 文字标签:`{ netease: '网易云', qq: 'QQ' }` - 视觉:水平排列,激活态用主色(`var(--color-primary)`)下划线 + 加粗,未激活态使用次要文字色 - 紧贴 section-title 右侧,使用 `display: inline-flex`,移动端 padding/font-size 缩小 ### 各 section 接入模板 ```vue

推荐歌单

{{ pl.name }}
``` 每个 section 在 `