feat: 重构首页Hero、导航菜单与页面布局统一

HomeHero:
- 重建每日推荐(左)+私人FM(右)双栏布局
- FM播放/暂停切换、不喜欢/下一首、背景流动动画、均衡器特效
- 修复FM数据获取(res.data.data双层结构)
- 歌单预加载改为hover懒加载避免502

导航优化:
- SearchBar顶部菜单: 首页/歌单/专辑/排行榜/MV/本地音乐
- 侧边栏隐藏MV和本地音乐(hideInSidebar)
- 修复搜索类型切换时失焦收起(@mousedown.prevent)

页面统一:
- 新建StickyTabPage通用布局组件(标题+吸顶tabs+内容slot)
- 歌单/专辑/MV/播客页面统一使用StickyTabPage重构
- CategorySelector第一项添加ml-0.5防scale裁切

播客优化:
- RadioCard简化去除订阅按钮、容忍radio为undefined
- 去除最近播放section、loadDashboard包含loadSubscribedRadios

i18n: 新碟上架→专辑(5语言)、新增fmTrash/fmNext(5语言)
This commit is contained in:
alger
2026-03-16 23:22:35 +08:00
parent 68b3700f3f
commit a3f91c45f0
17 changed files with 1184 additions and 1130 deletions
+25 -16
View File
@@ -66,8 +66,9 @@
trigger="hover"
:options="searchTypeOptions"
@select="selectSearchType"
@mousedown.prevent
>
<div class="type-chip">
<div class="type-chip" @mousedown.prevent>
<span>{{
searchTypeOptions.find((i) => i.key === searchStore.searchType)?.label
}}</span>
@@ -238,21 +239,28 @@ const showBackButton = computed(() => {
const goBack = () => router.back();
// ── Tabs ──────────────────────────────────────────────
const tabs = computed(() => [
{
key: 'playlist',
label: t('comp.searchBar.tabPlaylist'),
path: '/',
icon: 'ri-play-list-2-fill'
},
{ key: 'mv', label: t('comp.searchBar.tabMv'), path: '/mv', icon: 'ri-movie-2-fill' },
{
key: 'charts',
label: t('comp.searchBar.tabCharts'),
path: '/toplist',
icon: 'ri-bar-chart-grouped-fill'
}
]);
const tabs = computed(() => {
const items = [
{ key: 'home', label: t('comp.home'), path: '/', icon: 'ri-home-4-fill' },
{ key: 'playlist', label: t('comp.list'), path: '/list', icon: 'ri-play-list-2-fill' },
{ key: 'album', label: t('comp.newAlbum.title'), path: '/album', icon: 'ri-album-fill' },
{
key: 'charts',
label: t('comp.toplist'),
path: '/toplist',
icon: 'ri-bar-chart-grouped-fill'
},
{ key: 'mv', label: t('comp.mv'), path: '/mv', icon: 'ri-movie-2-fill' },
{
key: 'localMusic',
label: t('comp.localMusic'),
path: '/local-music',
icon: 'ri-folder-music-fill',
electronOnly: true
}
];
return items.filter((tab) => !tab.electronOnly || isElectron);
});
const isTabActive = (path: string) => route.path === path;
// Sliding pill
@@ -323,6 +331,7 @@ const selectSearchType = (key: number) => {
searchStore.searchType = key;
if (searchValue.value)
router.push({ path: '/search-result', query: { keyword: searchValue.value, type: key } });
nextTick(() => inputRef.value?.focus());
};
const rawSearchTypes = ref(SEARCH_TYPES);