🌈 style: 优化桌面歌词样式

This commit is contained in:
imsyy
2025-11-06 01:07:04 +08:00
parent 9fcd5b0e98
commit a556a2e102
5 changed files with 148 additions and 50 deletions

View File

@@ -62,9 +62,14 @@ const initLyricIpc = (): void => {
}); });
// 更新歌词窗口配置 // 更新歌词窗口配置
ipcMain.on("update-desktop-lyric-option", (_, option) => { ipcMain.on("update-desktop-lyric-option", (_, option, callback: boolean = false) => {
if (!option || !isWinAlive(lyricWin)) return; if (!option || !isWinAlive(lyricWin)) return;
lyricWin.webContents.send("desktop-lyric-option-change", option); store.set("lyric.config", option);
// 触发窗口更新
if (callback && isWinAlive(lyricWin)) {
lyricWin.webContents.send("update-desktop-lyric-option", option);
}
mainWin?.webContents.send("update-desktop-lyric-option", option);
}); });
// 播放状态更改 // 播放状态更改
@@ -132,18 +137,14 @@ const initLyricIpc = (): void => {
}); });
// 获取配置 // 获取配置
ipcMain.handle("get-desktop-lyric-option", () => { ipcMain.handle("request-desktop-lyric-option", () => {
return store.get("lyric"); const config = store.get("lyric.config");
}); console.log(config);
// 保存配置 if (isWinAlive(lyricWin)) {
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => { lyricWin.webContents.send("update-desktop-lyric-option", config);
store.set("lyric", option);
// 触发窗口更新
if (callback && isWinAlive(lyricWin)) {
lyricWin.webContents.send("desktop-lyric-option-change", option);
} }
mainWin?.webContents.send("desktop-lyric-option-change", option); return config;
}); });
// 发送主程序事件 // 发送主程序事件

View File

@@ -2,6 +2,7 @@ import { BrowserWindow } from "electron";
import { createWindow } from "./index"; import { createWindow } from "./index";
import { useStore } from "../store"; import { useStore } from "../store";
import { lyricWinUrl } from "../utils/config"; import { lyricWinUrl } from "../utils/config";
import mainWindow from "./main-window";
class LyricWindow { class LyricWindow {
private win: BrowserWindow | null = null; private win: BrowserWindow | null = null;
@@ -25,6 +26,11 @@ class LyricWindow {
store.set("lyric", { ...store.get("lyric"), width, height }); store.set("lyric", { ...store.get("lyric"), width, height });
} }
}); });
// 歌词窗口关闭
this.win?.on("close", () => {
const mainWin = mainWindow?.getWin();
mainWin?.webContents.send("closeDesktopLyric");
});
} }
/** /**
* 创建主窗口 * 创建主窗口

View File

@@ -331,6 +331,60 @@
@update:value="player.toggleDesktopLyric" @update:value="player.toggleDesktopLyric"
/> />
</n-card> </n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">锁定桌面歌词位置</n-text>
<n-text class="tip" :depth="3">是否锁定桌面歌词位置防止误触或遮挡内容</n-text>
</div>
<n-switch
v-model:value="desktopLyricConfig.isLock"
:round="false"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">双行歌词</n-text>
<n-text class="tip" :depth="3">是否启用双行歌词交替显示当前句和下一句</n-text>
</div>
<n-switch
v-model:value="desktopLyricConfig.isDoubleLine"
:round="false"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">限制歌词位置</n-text>
<n-text class="tip" :depth="3">是否限制桌面歌词位置在当前屏幕内</n-text>
</div>
<n-switch
v-model:value="desktopLyricConfig.limitBounds"
:round="false"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<!-- position -->
<n-card class="set-item">
<div class="label">
<n-text class="name">对齐方式</n-text>
<n-text class="tip" :depth="3">桌面歌词对齐方式</n-text>
</div>
<n-select
v-model:value="desktopLyricConfig.position"
:options="[
{ label: '左对齐', value: 'left' },
{ label: '居中对齐', value: 'center' },
{ label: '右对齐', value: 'right' },
{ label: '左右分离', value: 'both' },
]"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item"> <n-card class="set-item">
<div class="label"> <div class="label">
<n-text class="name">桌面歌词文字大小</n-text> <n-text class="name">桌面歌词文字大小</n-text>
@@ -352,11 +406,24 @@
</n-card> </n-card>
<n-card class="set-item"> <n-card class="set-item">
<div class="label"> <div class="label">
<n-text class="name">主题色</n-text> <n-text class="name">已播放文字</n-text>
<n-text class="tip" :depth="3">桌面歌词文字</n-text> <n-text class="tip" :depth="3">桌面歌词已播放文字</n-text>
</div> </div>
<n-color-picker <n-color-picker
v-model:value="desktopLyricConfig.mainColor" v-model:value="desktopLyricConfig.playedColor"
:show-alpha="false"
:modes="['hex']"
class="set"
@complete="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">未播放文字</n-text>
<n-text class="tip" :depth="3">桌面歌词未播放文字颜色</n-text>
</div>
<n-color-picker
v-model:value="desktopLyricConfig.unplayedColor"
:show-alpha="false" :show-alpha="false"
:modes="['hex']" :modes="['hex']"
class="set" class="set"
@@ -390,27 +457,34 @@
import { useSettingStore, useStatusStore } from "@/stores"; import { useSettingStore, useStatusStore } from "@/stores";
import { cloneDeep, isEqual } from "lodash-es"; import { cloneDeep, isEqual } from "lodash-es";
import { isElectron } from "@/utils/env"; import { isElectron } from "@/utils/env";
import player from "@/utils/player";
import { openLyricExclude } from "@/utils/modal"; import { openLyricExclude } from "@/utils/modal";
import player from "@/utils/player";
import { LyricConfig } from "@/types/desktop-lyric";
const statusStore = useStatusStore(); const statusStore = useStatusStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
// 桌面歌词配置 // 桌面歌词配置
const defaultDesktopLyricConfig = { const defaultDesktopLyricConfig = {
fontSize: 30, isLock: false,
mainColor: "#fff", playedColor: "#fe7971",
unplayedColor: "#ccc",
shadowColor: "rgba(0, 0, 0, 0.5)", shadowColor: "rgba(0, 0, 0, 0.5)",
}; fontFamily: "system-ui",
const desktopLyricConfig = reactive({ ...defaultDesktopLyricConfig }); fontSize: 24,
isDoubleLine: true,
position: "both",
limitBounds: false,
} as LyricConfig;
const desktopLyricConfig = reactive<LyricConfig>({ ...defaultDesktopLyricConfig });
// 获取桌面歌词配置 // 获取桌面歌词配置
const getDesktopLyricConfig = async () => { const getDesktopLyricConfig = async () => {
if (!isElectron) return; if (!isElectron) return;
const config = await window.electron.ipcRenderer.invoke("get-desktop-lyric-option"); const config = await window.electron.ipcRenderer.invoke("request-desktop-lyric-option");
if (config) Object.assign(desktopLyricConfig, config); if (config) Object.assign(desktopLyricConfig, config);
// 监听更新 // 监听更新
window.electron.ipcRenderer.on("desktop-lyric-option-change", (_, config) => { window.electron.ipcRenderer.on("update-desktop-lyric-option", (_, config) => {
if (config && !isEqual(desktopLyricConfig, config)) { if (config && !isEqual(desktopLyricConfig, config)) {
Object.assign(desktopLyricConfig, config); Object.assign(desktopLyricConfig, config);
} }
@@ -423,7 +497,7 @@ const saveDesktopLyricConfig = () => {
if (!isElectron) return; if (!isElectron) return;
console.log(cloneDeep(desktopLyricConfig)); console.log(cloneDeep(desktopLyricConfig));
window.electron.ipcRenderer.send( window.electron.ipcRenderer.send(
"set-desktop-lyric-option", "update-desktop-lyric-option",
cloneDeep(desktopLyricConfig), cloneDeep(desktopLyricConfig),
true, true,
); );
@@ -439,7 +513,11 @@ const saveDesktopLyricConfig = () => {
const restoreDesktopLyricConfig = () => { const restoreDesktopLyricConfig = () => {
try { try {
if (!isElectron) return; if (!isElectron) return;
window.electron.ipcRenderer.send("set-desktop-lyric-option", defaultDesktopLyricConfig, true); window.electron.ipcRenderer.send(
"update-desktop-lyric-option",
defaultDesktopLyricConfig,
true,
);
window.$message.success("桌面歌词配置已恢复默认"); window.$message.success("桌面歌词配置已恢复默认");
console.log(defaultDesktopLyricConfig, desktopLyricConfig); console.log(defaultDesktopLyricConfig, desktopLyricConfig);
} catch (error) { } catch (error) {

View File

@@ -23,10 +23,8 @@ export interface LyricConfig {
playedColor: string; playedColor: string;
/** 未播放颜色 */ /** 未播放颜色 */
unplayedColor: string; unplayedColor: string;
/** 描边 */ /** 阴影颜色 */
stroke: string; shadowColor: string;
/** 描边宽度 */
strokeWidth: number;
/** 字体 */ /** 字体 */
fontFamily: string; fontFamily: string;
/** 字体大小 */ /** 字体大小 */

View File

@@ -1,7 +1,7 @@
<template> <template>
<n-config-provider :theme="null"> <n-config-provider :theme="null">
<div <div
ref="desktopLyricsRef" ref="desktopLyricRef"
:class="[ :class="[
'desktop-lyric', 'desktop-lyric',
{ {
@@ -23,17 +23,17 @@
<span class="song-name">{{ lyricData.playName }}</span> <span class="song-name">{{ lyricData.playName }}</span>
</n-flex> </n-flex>
<n-flex :wrap="false" align="center" justify="center" size="small" @pointerdown.stop> <n-flex :wrap="false" align="center" justify="center" size="small" @pointerdown.stop>
<div class="menu-btn" title="上一曲" @click.stop="sendToMain('playPrev')"> <div class="menu-btn" title="上一曲" @click.stop="sendToMainWin('playPrev')">
<SvgIcon name="SkipPrev" /> <SvgIcon name="SkipPrev" />
</div> </div>
<div <div
class="menu-btn" class="menu-btn"
:title="lyricData.playStatus ? '暂停' : '播放'" :title="lyricData.playStatus ? '暂停' : '播放'"
@click.stop="sendToMain('playOrPause')" @click.stop="sendToMainWin('playOrPause')"
> >
<SvgIcon :name="lyricData.playStatus ? 'Pause' : 'Play'" /> <SvgIcon :name="lyricData.playStatus ? 'Pause' : 'Play'" />
</div> </div>
<div class="menu-btn" title="下一曲" @click.stop="sendToMain('playNext')"> <div class="menu-btn" title="下一曲" @click.stop="sendToMainWin('playNext')">
<SvgIcon name="SkipNext" /> <SvgIcon name="SkipNext" />
</div> </div>
</n-flex> </n-flex>
@@ -44,7 +44,7 @@
<div class="menu-btn" title="解锁"> <div class="menu-btn" title="解锁">
<SvgIcon name="LockOpen" /> <SvgIcon name="LockOpen" />
</div> </div>
<div class="menu-btn" title="关闭"> <div class="menu-btn" title="关闭" @click.stop="sendToMain('closeDesktopLyric')">
<SvgIcon name="Close" /> <SvgIcon name="Close" />
</div> </div>
</n-flex> </n-flex>
@@ -53,6 +53,7 @@
:style="{ :style="{
fontSize: lyricConfig.fontSize + 'px', fontSize: lyricConfig.fontSize + 'px',
fontFamily: lyricConfig.fontFamily, fontFamily: lyricConfig.fontFamily,
textShadow: `0 0 4px ${lyricConfig.shadowColor}`,
}" }"
:class="['lyric-container', lyricConfig.position]" :class="['lyric-container', lyricConfig.position]"
vertical vertical
@@ -67,6 +68,10 @@
> >
{{ line.text }} {{ line.text }}
</span> </span>
<!-- 占位 -->
<span v-if="lyricConfig.isDoubleLine && renderLyricLines.length === 1" class="lyric-line">
&nbsp;
</span>
</n-flex> </n-flex>
</div> </div>
</n-config-provider> </n-config-provider>
@@ -91,8 +96,7 @@ const lyricConfig = reactive<LyricConfig>({
isLock: false, isLock: false,
playedColor: "#fe7971", playedColor: "#fe7971",
unplayedColor: "#ccc", unplayedColor: "#ccc",
stroke: "#000", shadowColor: "rgba(0, 0, 0, 0.5)",
strokeWidth: 2,
fontFamily: "system-ui", fontFamily: "system-ui",
fontSize: 24, fontSize: 24,
isDoubleLine: true, isDoubleLine: true,
@@ -101,7 +105,7 @@ const lyricConfig = reactive<LyricConfig>({
}); });
// 桌面歌词元素 // 桌面歌词元素
const desktopLyricsRef = useTemplateRef<HTMLElement>("desktopLyricsRef"); const desktopLyricRef = ref<HTMLElement>();
/** /**
* 渲染的歌词行 * 渲染的歌词行
@@ -207,7 +211,7 @@ const lyricDragMove = async (_position: Position, event: PointerEvent) => {
}; };
// 监听桌面歌词拖动 // 监听桌面歌词拖动
useDraggable(desktopLyricsRef, { useDraggable(desktopLyricRef, {
onStart: (position, event) => { onStart: (position, event) => {
lyricDragStart(position, event); lyricDragStart(position, event);
}, },
@@ -219,28 +223,27 @@ useDraggable(desktopLyricsRef, {
}, },
}); });
// 发送至主窗口 // 发送至主进程
const sendToMain = (eventName: string, ...args: any[]) => { const sendToMain = (eventName: string, ...args: any[]) => {
// 特殊处理 window.electron.ipcRenderer.send(eventName, ...args);
if (eventName === "win-show") {
window.electron.ipcRenderer.send("win-show");
return;
}
window.electron.ipcRenderer.send("send-to-main", eventName, ...args);
}; };
// 处理歌词数据 // 发送至主窗口
const handleLyricData = (data: LyricData) => { const sendToMainWin = (eventName: string, ...args: any[]) => {
Object.assign(lyricData, data); window.electron.ipcRenderer.send("send-to-main", eventName, ...args);
}; };
onMounted(() => { onMounted(() => {
// 接收歌词数据 // 接收歌词数据
window.electron.ipcRenderer.on("update-desktop-lyric-data", (_event, data: LyricData) => { window.electron.ipcRenderer.on("update-desktop-lyric-data", (_event, data: LyricData) => {
handleLyricData(data); Object.assign(lyricData, data);
});
window.electron.ipcRenderer.on("update-desktop-lyric-option", (_event, config: LyricConfig) => {
Object.assign(lyricConfig, config);
}); });
// 请求歌词数据及配置 // 请求歌词数据及配置
window.electron.ipcRenderer.send("request-desktop-lyric-data"); window.electron.ipcRenderer.send("request-desktop-lyric-data");
window.electron.ipcRenderer.invoke("request-desktop-lyric-option");
}); });
</script> </script>
@@ -254,7 +257,6 @@ onMounted(() => {
background-color: transparent; background-color: transparent;
padding: 12px; padding: 12px;
border-radius: 12px; border-radius: 12px;
height: 100%;
overflow: hidden; overflow: hidden;
transition: background-color 0.3s; transition: background-color 0.3s;
cursor: move; cursor: move;
@@ -305,16 +307,29 @@ onMounted(() => {
} }
.lyric-container { .lyric-container {
padding: 0 8px; padding: 0 8px;
.lyric-line {
// 单行
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.center { &.center {
align-items: center; align-items: center;
.lyric-line {
text-align: center;
}
} }
&.right { &.right {
align-items: flex-end; align-items: flex-end;
.lyric-line {
text-align: right;
}
} }
&.both { &.both {
.lyric-line { .lyric-line {
&:nth-child(2n) { &:nth-child(2n) {
margin-left: auto; text-align: right;
} }
} }
} }