feat: 皮肤系统重构、seek暂停修复、本地音乐优化、外观一体化

- 重构皮肤系统:提取 skins.ts 管理预设皮肤,CSS 变量由 JS 动态设置

- 提取公共 color.ts 工具函数(hexToRgba/toHex),消除重复定义

- 修复 seek 时暂停状态丢失的 bug(后端 audio_paused 状态保留)

- 本地音乐页面:循环排序切换、三点菜单、打开所在文件夹

- 本地音乐文件夹管理:支持启用/禁用切换,兼容旧数据迁移

- 新增 show_item_in_folder 命令(Windows/macOS/Linux 跨平台)

- 外观一体化:有壁纸时 TitleBar/Sidebar 透明,PlayerBar 统一透明度+backdrop-blur

- 进度条外层直角、内层填充圆角

- 滚动条默认透明,悬停时显示

- 移除 PageHeader 粘性栏

- 内存优化:keep-alive TTL 5min、pageCache TTL 30min/上限30条、colorCache 上限200

- recentLocal 防抖写入、播放器 tick interval 500ms
This commit is contained in:
2026-06-07 07:45:41 +08:00
parent 3535e2e8a0
commit dcfada6940
27 changed files with 1736 additions and 731 deletions
+72 -5
View File
@@ -1,5 +1,24 @@
<template>
<div class="flex flex-col h-screen bg-base text-content overflow-hidden">
<!-- 壁纸层fixed 全屏最底层 -->
<div
v-if="settings.currentWallpaper.path"
class="fixed inset-0 z-0 pointer-events-none overflow-hidden"
>
<div
class="absolute inset-[-20px] bg-cover bg-center bg-no-repeat"
:style="wallpaperStyle"
></div>
</div>
<!-- 主题色遮罩层半透明主题色覆盖壁纸保证文字可读 -->
<div
v-if="settings.currentWallpaper.path"
class="fixed inset-0 z-[1] pointer-events-none"
:style="overlayStyle"
></div>
<!-- 主容器 -->
<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">
@@ -37,7 +56,7 @@
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useUserStore } from './stores/user';
import { useSettingsStore, type CloseAction } from './stores/settings';
@@ -56,6 +75,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
import { listen } from '@tauri-apps/api/event';
import { register, unregister } from '@tauri-apps/plugin-global-shortcut';
import { MusicApi, AudioApi, DeviceApi, AppApi } from './api';
import { hexToRgba } from './utils/color';
const userStore = useUserStore();
const player = usePlayerStore();
@@ -84,7 +104,7 @@ const ROUTE_COMPONENT: Record<string, string> = {
};
const ALL_CACHEABLE = [...new Set(Object.values(ROUTE_COMPONENT))];
const PERMANENT = new Set(['FavoriteSongsView']);
const CACHE_TTL = 30_000;
const CACHE_TTL = 300_000;
const lastActivatedAt: Record<string, number> = {};
const navStack = ref<string[]>([]);
@@ -125,10 +145,57 @@ let cleanupTimer: ReturnType<typeof setInterval>;
function startCleanup() { cleanupTimer = setInterval(() => { keepAliveInclude.value = computeInclude(); }, 10_000); }
function stopCleanup() { clearInterval(cleanupTimer); }
watch(() => settings.dataTheme, (val) => {
document.documentElement.setAttribute('data-theme', val);
watch(() => settings.skin, () => {
settings.applySkin();
}, { immediate: true });
// 壁纸样式:通过 Rust 命令读取本地图片转 base64 data URL
const wallpaperDataUrl = ref('');
const wallpaperStyle = computed(() => {
if (!wallpaperDataUrl.value) return {};
const wp = settings.currentWallpaper;
return {
backgroundImage: `url(${wallpaperDataUrl.value})`,
filter: `blur(${wp.blur}px)`,
opacity: wp.opacity,
};
});
// 监听壁纸路径变化,异步加载图片
watch(() => settings.currentWallpaper.path, async (path) => {
if (!path) {
wallpaperDataUrl.value = '';
return;
}
try {
wallpaperDataUrl.value = await AppApi.readImageAsDataUrl(path);
} catch (e) {
console.error('加载壁纸失败:', e);
wallpaperDataUrl.value = '';
}
}, { immediate: true });
// 根容器背景:有壁纸时透明(遮罩层已保证文字可读),无壁纸时不透明
const rootBgStyle = computed(() => {
const wp = settings.currentWallpaper;
if (wp.path) {
return {}; // 透明,遮罩层统一处理
}
return {
backgroundColor: 'var(--c-bg)',
};
});
// 主题色遮罩层:用 --c-bg 的半透明版本覆盖壁纸,保证文字对比度
// 这是网易云式设计的核心:壁纸色调透出遮罩,文字始终清晰
const overlayStyle = computed(() => {
const bgColor = settings.currentColors.bg;
const rgba = hexToRgba(bgColor, 0.82);
return {
backgroundColor: rgba,
};
});
watch(() => userStore.isLoggedIn, (val) => {
if (val) {
player.loadLikedIds();