feat: 桌面歌词添加主题颜色面板组件

This commit is contained in:
alger
2025-07-23 22:47:22 +08:00
parent d5ba218b10
commit d1f5c8af84
6 changed files with 1092 additions and 20 deletions
+306 -20
View File
@@ -37,6 +37,13 @@
<i v-if="lyricSetting.theme === 'light'" class="ri-sun-line"></i>
<i v-else class="ri-moon-line"></i>
</div>
<div
class="control-button theme-color-button"
:class="{ active: showThemeColorPanel }"
@click="toggleThemeColorPanel"
>
<i class="ri-palette-line"></i>
</div>
<!-- <div class="control-button" @click="handleTop">
<i class="ri-pushpin-line" :class="{ active: lyricSetting.isTop }"></i>
</div> -->
@@ -50,6 +57,15 @@
</div>
</div>
<!-- 主题色选择面板 -->
<ThemeColorPanel
:visible="showThemeColorPanel"
:current-color="currentHighlightColor"
:theme="lyricSetting.theme"
@color-change="handleColorChange"
@close="handleThemeColorPanelClose"
/>
<!-- 歌词显示区域 -->
<div ref="containerRef" class="lyric-container">
<div class="lyric-scroll">
@@ -91,6 +107,13 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { SongResult } from '@/type/music';
import ThemeColorPanel from '@/components/lyric/ThemeColorPanel.vue';
import {
getCurrentLyricThemeColor,
loadLyricThemeColor,
saveLyricThemeColor,
validateColor
} from '@/utils/linearColor';
defineOptions({
name: 'Lyric'
@@ -127,20 +150,51 @@ const dynamicData = ref({
isPlay: true
});
const lyricSetting = ref({
...(localStorage.getItem('lyricData')
? JSON.parse(localStorage.getItem('lyricData') || '')
: {
isTop: false,
theme: 'dark',
isLock: false
})
});
// 安全加载歌词设置
const loadLyricSettings = () => {
try {
const stored = localStorage.getItem('lyricData');
if (stored) {
const parsed = JSON.parse(stored);
// 验证 highlightColor 字段
let validatedHighlightColor = parsed.highlightColor;
if (validatedHighlightColor && !validateColor(validatedHighlightColor)) {
console.warn('Invalid stored highlight color, removing it');
validatedHighlightColor = undefined;
}
// 确保所有必需字段存在并有效
return {
isTop: parsed.isTop ?? false,
theme: (parsed.theme === 'light' || parsed.theme === 'dark') ? parsed.theme : 'dark',
isLock: parsed.isLock ?? false,
highlightColor: validatedHighlightColor
};
}
} catch (error) {
console.error('Failed to load lyric settings:', error);
}
// 返回默认设置
return {
isTop: false,
theme: 'dark' as 'light' | 'dark',
isLock: false,
highlightColor: undefined as string | undefined
};
};
const lyricSetting = ref(loadLyricSettings());
let hideControlsTimer: number | null = null;
const isHovering = ref(false);
// 主题色相关状态
const showThemeColorPanel = ref(false);
const currentHighlightColor = ref('#1db954');
// 计算是否栏
const showControls = computed(() => {
if (lyricSetting.value.isLock) {
@@ -364,11 +418,20 @@ const getLyricStyle = (index: number) => {
if (index !== currentIndex.value) return {};
const progress = currentProgress.value * 100;
// 使用更清晰的渐变实现
return {
background: `linear-gradient(to right, var(--highlight-color) ${progress}%, var(--text-color) ${progress}%)`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
transition: 'all 0.1s linear'
// 优化字体渲染,减少发虚
textRendering: 'optimizeLegibility' as const,
WebkitFontSmoothing: 'antialiased' as const,
MozOsxFontSmoothing: 'grayscale' as const,
// 使用 transform 而不是直接的 transition 来提高性能
transform: 'translateZ(0)', // 启用硬件加速
backfaceVisibility: 'hidden' as const, // 减少渲染问题
transition: 'background 0.1s linear'
};
};
@@ -520,6 +583,140 @@ const checkTheme = () => {
}
};
// 主题色相关函数
const toggleThemeColorPanel = () => {
showThemeColorPanel.value = !showThemeColorPanel.value;
};
const handleColorChange = (color: string) => {
// 验证颜色有效性
if (!validateColor(color)) {
console.error('Invalid color received:', color);
return;
}
try {
currentHighlightColor.value = color;
updateThemeColorWithTransition(color);
// 更新 lyricSetting 中的 highlightColor
lyricSetting.value.highlightColor = color;
// 同时保存到专用的主题色存储
saveLyricThemeColor(color);
} catch (error) {
console.error('Failed to handle color change:', error);
// 恢复到默认颜色
const defaultColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
currentHighlightColor.value = defaultColor;
updateThemeColorWithTransition(defaultColor);
}
};
const handleThemeColorPanelClose = () => {
showThemeColorPanel.value = false;
};
// 导出重置函数以供将来使用
const resetThemeColor = () => {
// 重置到默认颜色
const defaultColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
// 更新所有相关状态
currentHighlightColor.value = defaultColor;
lyricSetting.value.highlightColor = undefined;
updateThemeColorWithTransition(defaultColor);
// 清除专用存储
try {
const settings = loadLyricSettings();
delete settings.highlightColor;
saveLyricSettings(settings);
} catch (error) {
console.error('Failed to reset theme color:', error);
}
};
// 验证和修复颜色设置
const validateAndFixColorSettings = () => {
try {
// 检查当前高亮颜色是否有效
if (currentHighlightColor.value && !validateColor(currentHighlightColor.value)) {
console.warn('Current highlight color is invalid, resetting to default');
const defaultColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
currentHighlightColor.value = defaultColor;
lyricSetting.value.highlightColor = undefined;
updateCSSVariable('--lyric-highlight-color', defaultColor);
}
// 检查 lyricSetting 中的颜色是否有效
if (lyricSetting.value.highlightColor && !validateColor(lyricSetting.value.highlightColor)) {
console.warn('Stored highlight color is invalid, removing it');
lyricSetting.value.highlightColor = undefined;
}
} catch (error) {
console.error('Failed to validate color settings:', error);
// 完全重置到默认状态
const defaultColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
currentHighlightColor.value = defaultColor;
lyricSetting.value.highlightColor = undefined;
updateCSSVariable('--lyric-highlight-color', defaultColor);
}
};
// 暴露函数
defineExpose({
resetThemeColor,
validateAndFixColorSettings
});
const updateCSSVariable = (name: string, value: string) => {
document.documentElement.style.setProperty(name, value);
};
const updateThemeColorWithTransition = (newColor: string) => {
// 添加过渡类
const lyricWindow = document.querySelector('.lyric-window');
if (lyricWindow) {
lyricWindow.classList.add('color-transitioning');
}
// 更新CSS变量
updateCSSVariable('--lyric-highlight-color', newColor);
// 移除过渡类
setTimeout(() => {
if (lyricWindow) {
lyricWindow.classList.remove('color-transitioning');
}
}, 300);
};
const initializeThemeColor = () => {
// 优先从 lyricSetting 中读取颜色
let savedColor = lyricSetting.value.highlightColor;
// 如果 lyricSetting 中没有,则从专用存储中读取
if (!savedColor) {
savedColor = loadLyricThemeColor();
// 如果从专用存储中读取到了颜色,同步到 lyricSetting
if (savedColor) {
lyricSetting.value.highlightColor = savedColor;
}
}
if (savedColor) {
const optimizedColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
currentHighlightColor.value = optimizedColor;
updateCSSVariable('--lyric-highlight-color', optimizedColor);
} else {
// 如果没有保存的颜色,使用默认颜色
const defaultColor = getCurrentLyricThemeColor(lyricSetting.value.theme);
currentHighlightColor.value = defaultColor;
updateCSSVariable('--lyric-highlight-color', defaultColor);
}
};
// const handleTop = () => {
// lyricSetting.value.isTop = !lyricSetting.value.isTop;
// windowData.electron.ipcRenderer.send('top-lyric', lyricSetting.value.isTop);
@@ -534,14 +731,35 @@ const handleClose = () => {
windowData.electron.ipcRenderer.send('close-lyric');
};
// 安全保存歌词设置
const saveLyricSettings = (settings: typeof lyricSetting.value) => {
try {
localStorage.setItem('lyricData', JSON.stringify(settings));
} catch (error) {
console.error('Failed to save lyric settings:', error);
}
};
watch(
() => lyricSetting.value,
(newValue: any) => {
localStorage.setItem('lyricData', JSON.stringify(newValue));
(newValue) => {
saveLyricSettings(newValue);
},
{ deep: true }
);
// 监听主题切换,自动调整颜色
watch(
() => lyricSetting.value.theme,
(newTheme) => {
if (currentHighlightColor.value) {
const optimizedColor = getCurrentLyricThemeColor(newTheme);
currentHighlightColor.value = optimizedColor;
updateThemeColorWithTransition(optimizedColor);
}
}
);
// 添加拖动相关变量
const isDragging = ref(false);
const startPosition = ref({ x: 0, y: 0 });
@@ -626,6 +844,12 @@ onMounted(() => {
}
};
}
// 初始化主题色
initializeThemeColor();
// 验证和修复颜色设置
validateAndFixColorSettings();
});
// 添加播放控制相关的函数
@@ -663,6 +887,18 @@ body,
transition: background-color 0.3s ease;
cursor: default;
border-radius: 14px;
&.color-transitioning {
.lyric-text-inner {
transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
.control-button {
i {
transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
}
}
&:hover {
.control-bar {
@@ -680,7 +916,7 @@ body,
&.dark {
--text-color: #ffffff;
--text-secondary: #ffffffea;
--highlight-color: #1db954;
--highlight-color: var(--lyric-highlight-color, #1ed760);
--control-bg: rgba(124, 124, 124, 0.3);
&:hover:not(.lyric_lock) {
background: rgba(44, 44, 44, 0.466) !important;
@@ -688,10 +924,10 @@ body,
}
&.light {
--text-color: #333333;
--text-secondary: #39393989;
--highlight-color: #1db954;
--control-bg: rgba(255, 255, 255, 0.3);
--text-color: #383838;
--text-secondary: #282828ae;
--highlight-color: var(--lyric-highlight-color, #1db954);
--control-bg: rgba(38, 38, 38, 0.532);
&:hover:not(.lyric_lock) {
background: rgba(0, 0, 0, 0.434) !important;
}
@@ -775,6 +1011,16 @@ body,
color: var(--highlight-color);
}
}
&.theme-color-button {
&.active {
background: var(--control-bg);
i {
color: var(--highlight-color);
}
}
}
}
.lyric-container {
@@ -816,6 +1062,19 @@ body,
&.lyric-line-current {
transform: scale(1.05);
opacity: 1;
// 当前播放歌词的特殊样式
.lyric-text {
// 移除阴影,避免干扰渐变效果
text-shadow: none;
.lyric-text-inner {
// 为渐变文字添加轻微的外发光
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
// 确保渐变效果清晰
-webkit-font-smoothing: antialiased;
}
}
}
&.lyric-line-passed {
@@ -829,9 +1088,22 @@ body,
color: var(--text-color);
white-space: pre-wrap;
word-break: break-all;
transition: all 0.2s ease;
transition: transform 0.2s ease;
line-height: 1.4;
-webkit-text-stroke: 0.5px #0000008a;
// 优化字体渲染
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
// 为非当前播放的歌词添加阴影效果
text-shadow:
0 0 2px rgba(0, 0, 0, 0.8),
0 1px 1px rgba(0, 0, 0, 0.6),
0 0 4px rgba(255, 255, 255, 0.2);
.lyric-text-inner {
transition: background 0.3s ease;
}
}
.lyric-translation {
@@ -839,7 +1111,15 @@ body,
white-space: pre-wrap;
word-break: break-all;
transition: font-size 0.2s ease;
line-height: 1.4; // 添加行高比例
line-height: 1.4;
// 为翻译文本也添加阴影效果,但稍微轻一些
text-shadow:
0 0 2px rgba(0, 0, 0, 0.7),
0 1px 1px rgba(0, 0, 0, 0.5),
0 0 4px rgba(255, 255, 255, 0.2),
1px 1px 1px rgba(0, 0, 0, 0.4),
-1px -1px 1px rgba(0, 0, 0, 0.4);
}
.lyric-empty {
@@ -847,6 +1127,12 @@ body,
color: var(--text-secondary);
font-size: 16px;
padding: 20px;
// 为空歌词提示也添加阴影效果
text-shadow:
0 0 2px rgba(0, 0, 0, 0.7),
0 1px 1px rgba(0, 0, 0, 0.5),
0 0 4px rgba(255, 255, 255, 0.2);
}
body {