feat: v0.8.0 详情页架构重构、皮肤系统、侧边栏交互升级、底层模块拆分

- 详情页统一 DetailLayout 组件(常驻头部 + 独立滚动 + 紧凑态动画)
- 歌曲/评论 tab 切换(懒加载 + 保活),专辑模糊搜索
- 皮肤系统 skins.ts(19 语义色 + 14 预设 + 自定义皮肤 + 壁纸)
- 设置页皮肤模块(外观/主题色/皮肤编辑器/壁纸选择)
- 可折叠侧边栏 + TopBar + 折叠态歌单浮层(高度不越 PlayerBar)
- 首页扩展至 6 区块 + 通用卡片组件 + 虚拟歌曲列表
- audio.rs 拆为 8 模块,api.ts 拆为 api/ 目录,player store 拆为 4 store
- 修复播放竞态、seek 暂停、评论提前加载、紧凑态卡死等
This commit is contained in:
2026-06-26 21:05:51 +08:00
parent dcfada6940
commit 263e9b31d9
74 changed files with 5843 additions and 2449 deletions
+58 -12
View File
@@ -19,21 +19,25 @@
<!-- 主容器 -->
<div class="flex flex-col h-screen text-content overflow-hidden relative z-[2]" :style="rootBgStyle">
<TitleBar @close="closeWindow" />
<div class="flex flex-1 overflow-hidden" v-if="windowVisible">
<Sidebar />
<Sidebar :mode="ui.sidebarMode" />
<main class="flex-1 overflow-y-auto pb-24">
<router-view v-slot="{ Component }">
<keep-alive :max="10" :include="keepAliveInclude">
<component :is="Component" />
</keep-alive>
</router-view>
</main>
<div class="flex flex-col flex-1 overflow-hidden">
<TopBar @close="closeWindow" />
<main class="flex-1 overflow-hidden flex flex-col" :style="{ paddingBottom: player.currentSong ? '70px' : '0px' }">
<div ref="mainScrollRef" class="flex-1 overflow-y-auto min-h-0 relative">
<router-view v-slot="{ Component }">
<keep-alive :max="10" :include="keepAliveInclude">
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</main>
</div>
</div>
<RoamDrawer :visible="windowVisible && player.showRoamDrawer" />
<RoamDrawer :visible="windowVisible && ui.showRoamDrawer" />
<PlayerBar v-if="player.currentSong" />
<ToastContainer />
@@ -61,7 +65,8 @@ import { useRoute } from 'vue-router';
import { useUserStore } from './stores/user';
import { useSettingsStore, type CloseAction } from './stores/settings';
import { usePlayerStore } from './stores/player';
import TitleBar from './components/TitleBar.vue';
import { useUiStore } from './stores/ui';
import TopBar from './components/TopBar.vue';
import Sidebar from './components/Sidebar.vue';
import RoamDrawer from './components/RoamDrawer.vue';
import PlayerBar from './components/PlayerBar.vue';
@@ -79,6 +84,7 @@ import { hexToRgba } from './utils/color';
const userStore = useUserStore();
const player = usePlayerStore();
const ui = useUiStore();
const settings = useSettingsStore();
const updater = useUpdater();
const { isOnline } = useOnlineStatus();
@@ -95,6 +101,22 @@ const windowVisible = ref(true);
// 规则:30秒未访问的页面自动清除缓存;多级跳转时保留导航链上的页面;FavoriteSongs 常驻
const route = useRoute();
// 主滚动容器 ref(路由切换时重置滚动位置)
const mainScrollRef = ref<HTMLElement | null>(null);
// 路由切换时重置主滚动容器到顶部
// keep-alive 缓存会保留滚动位置,详情页等需要主动重置
watch(() => route.path, () => {
// 双重保险:nextTick + 延迟,确保 DOM 更新和缓存恢复后再滚动
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (mainScrollRef.value) {
mainScrollRef.value.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}
});
});
});
const ROUTE_COMPONENT: Record<string, string> = {
home: 'HomeView', discover: 'DiscoverView', search: 'DiscoverView',
favorites: 'FavoriteSongsView', daily: 'DailySongsView',
@@ -175,6 +197,26 @@ watch(() => settings.currentWallpaper.path, async (path) => {
}
}, { immediate: true });
// --- 响应式侧边栏:窄屏自动切抽屉模式 ---
const DRAWER_BREAKPOINT = 768; // px
let savedSidebarMode: 'expanded' | 'collapsed' = 'expanded';
function applyResponsiveSidebar() {
const width = window.innerWidth;
if (width < DRAWER_BREAKPOINT) {
// 窄屏:切抽屉模式(不持久化,保留用户原选择)
if (ui.sidebarMode !== 'drawer') {
savedSidebarMode = ui.sidebarMode as 'expanded' | 'collapsed';
ui.setSidebarMode('drawer');
}
} else {
// 宽屏:恢复用户选择
if (ui.sidebarMode === 'drawer') {
ui.setSidebarMode(savedSidebarMode);
}
}
}
// 根容器背景:有壁纸时透明(遮罩层已保证文字可读),无壁纸时不透明
const rootBgStyle = computed(() => {
const wp = settings.currentWallpaper;
@@ -206,6 +248,10 @@ onMounted(() => {
document.addEventListener('contextmenu', (e) => e.preventDefault());
startCleanup();
// 响应式侧边栏
applyResponsiveSidebar();
window.addEventListener('resize', applyResponsiveSidebar);
AudioApi.stopAudio().catch(() => {});
if (userStore.isLoggedIn) {