diff --git a/electron/main/windows/taskbar-lyric-window.ts b/electron/main/windows/taskbar-lyric-window.ts index 6a49f1f2e..22b2f8be5 100644 --- a/electron/main/windows/taskbar-lyric-window.ts +++ b/electron/main/windows/taskbar-lyric-window.ts @@ -9,10 +9,10 @@ import { app, type BrowserWindow, ipcMain, nativeTheme, screen } from "electron" import { debounce } from "lodash-es"; import { join } from "node:path"; import { processLog } from "../logger"; +import { useStore } from "../store"; import { isDev, port } from "../utils/config"; import { loadNativeModule } from "../utils/native-loader"; import { createWindow } from "./index"; -import { useStore } from "../store"; type taskbarLyricModule = typeof import("@native/taskbar-lyric"); @@ -113,6 +113,19 @@ class TaskbarLyricWindow { this.win.loadURL(taskbarLyricUrl); + // 因为任务栏窗口非常小,默认嵌入的开发者工具完全无法使用, + // 所以监听 F12 并按分离模式打开开发者工具 + this.win.webContents.on("before-input-event", (event, input) => { + if (input.key === "F12" && input.type === "keyDown") { + if (this.win?.webContents.isDevToolsOpened()) { + this.win?.webContents.closeDevTools(); + } else { + this.win?.webContents.openDevTools({ mode: "detach" }); + } + event.preventDefault(); + } + }); + const sendTheme = () => { if (this.win && !this.win.isDestroyed()) { const isDark = nativeTheme.shouldUseDarkColors; diff --git a/src/components/Player/PlayerLyric/AMLyric.vue b/src/components/Player/PlayerLyric/AMLyric.vue index c3cc64047..108fee9a6 100644 --- a/src/components/Player/PlayerLyric/AMLyric.vue +++ b/src/components/Player/PlayerLyric/AMLyric.vue @@ -39,7 +39,7 @@ :wordFadeWidth="settingStore.wordFadeWidth" :style="{ '--display-count-down-show': settingStore.countDownShow ? 'flex' : 'none', - '--amll-lp-font-size': settingStore.lyricFontSize + 'px', + '--amll-lp-font-size': getFontSize(settingStore.lyricFontSize, settingStore.lyricFontSizeMode), 'font-weight': settingStore.lyricFontWeight, 'font-family': settingStore.LyricFont !== 'follow' ? settingStore.LyricFont : '', ...lyricLangFontStyle(settingStore), @@ -58,6 +58,7 @@ import { getLyricLanguage } from "@/utils/format"; import { usePlayerController } from "@/core/player/PlayerController"; import { cloneDeep } from "lodash-es"; import { lyricLangFontStyle } from "@/utils/lyric/lyricFontConfig"; +import { getFontSize } from "@/utils/style"; defineProps({ currentTime: { diff --git a/src/components/Player/PlayerLyric/DefaultLyric.vue b/src/components/Player/PlayerLyric/DefaultLyric.vue index 1aa4dd241..0d2e37ceb 100644 --- a/src/components/Player/PlayerLyric/DefaultLyric.vue +++ b/src/components/Player/PlayerLyric/DefaultLyric.vue @@ -2,9 +2,9 @@
import { useSettingStore } from "@/stores"; +import { getFontSize } from "@/utils/style"; const settingStore = useSettingStore(); @@ -72,15 +73,15 @@ const romaFontSize = fontSizeComputed("lyricRomaFontSize"); &:nth-of-type(1) { font-weight: var(--font-weight); - font-size: calc(var(--font-size) * 1px); + font-size: var(--font-size); } &:nth-of-type(2) { opacity: 0.6; - font-size: calc(var(--font-tran-size) * 1px); + font-size: var(--font-tran-size); } &:nth-of-type(3) { opacity: 0.6; - font-size: calc(var(--font-roma-size) * 1px); + font-size: var(--font-roma-size); } } } diff --git a/src/components/Setting/config/lyric.ts b/src/components/Setting/config/lyric.ts index e33fc1dd3..b76e2d02e 100644 --- a/src/components/Setting/config/lyric.ts +++ b/src/components/Setting/config/lyric.ts @@ -93,11 +93,25 @@ export const useLyricSettings = (): SettingConfig => { noWrapper: true, component: markRaw(LyricPreview), }, + { + key: "lyricFontSizeMode", + label: "自适应歌词大小", + type: "switch", + description: "开启后歌词大小将根据窗口高度自动缩放,避免全屏时过小或窗口时过大", + value: computed({ + get: () => settingStore.lyricFontSizeMode === "adaptive", + set: (v) => (settingStore.lyricFontSizeMode = v ? "adaptive" : "fixed"), + }), + }, { key: "lyricFontSize", label: "歌词字体大小", type: "input-number", - description: "单位 px,最小 12,最大 60", + description: computed(() => + settingStore.lyricFontSizeMode === "adaptive" + ? "作为基准大小 (以 1080p 高度为准)" + : "单位 px,最小 12,最大 60", + ), min: 12, max: 60, suffix: "px", @@ -111,7 +125,11 @@ export const useLyricSettings = (): SettingConfig => { key: "lyricTranFontSize", label: "翻译歌词大小", type: "input-number", - description: "单位 px,最小 5,最大 40", + description: computed(() => + settingStore.lyricFontSizeMode === "adaptive" + ? "作为基准大小 (以 1080p 高度为准)" + : "单位 px,最小 5,最大 40", + ), min: 5, max: 40, suffix: "px", @@ -130,7 +148,11 @@ export const useLyricSettings = (): SettingConfig => { key: "lyricRomaFontSize", label: "音译歌词大小", type: "input-number", - description: "单位 px,最小 5,最大 40", + description: computed(() => + settingStore.lyricFontSizeMode === "adaptive" + ? "作为基准大小 (以 1080p 高度为准)" + : "单位 px,最小 5,最大 40", + ), min: 5, max: 40, suffix: "px", diff --git a/src/stores/setting.ts b/src/stores/setting.ts index d35ba0777..3d73edfd9 100644 --- a/src/stores/setting.ts +++ b/src/stores/setting.ts @@ -70,6 +70,8 @@ export interface SettingState { updateChannel: "stable" | "nightly"; /** 隐藏 VIP 标签 */ hideVipTag: boolean; + /** 歌词字体大小模式 */ + lyricFontSizeMode: "fixed" | "adaptive"; /** 歌词字体大小 */ lyricFontSize: number; /** 歌词翻译字体大小 */ @@ -549,6 +551,7 @@ export const useSettingStore = defineStore("setting", { playSongDemo: false, scrobbleSong: false, dynamicCover: false, + lyricFontSizeMode: "adaptive", lyricFontSize: 46, lyricTranFontSize: 22, lyricRomaFontSize: 18, diff --git a/src/utils/lyric/qrc-parser.ts b/src/utils/lyric/qrc-parser.ts index 322070e06..12dbcb57e 100644 --- a/src/utils/lyric/qrc-parser.ts +++ b/src/utils/lyric/qrc-parser.ts @@ -26,12 +26,37 @@ const domParser: QRCParserFn = (xmlStr) => { } }; +const decodeXml = (str: string) => { + return str + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&"); +}; + // 实现正则策略 const regexParser: QRCParserFn = (xmlStr) => { if (!xmlStr) return ""; + + // 尝试贪婪匹配 (处理 LyricContent="... "..." ..." 这种非标准 XML 引号未转义的情况) + // 匹配从 LyricContent=" 开始,直到标签结束前的最后一个引号 + const greedyMatch = /LyricContent\s*=\s*"([\s\S]*)"\s*\/?>/.exec(xmlStr); + if (greedyMatch) { + const content = greedyMatch[1]; + // 启发式检查:如果提取的内容中包含类似 ` Attribute="` 的结构,说明贪婪匹配吃掉了其他属性 + // 这种情况下回退到非贪婪匹配 + if (!/\s+\w+\s*=\s*"/.test(content)) { + return decodeXml(content); + } + } + + // 标准非贪婪匹配 (兼容标准 XML 属性) const match = /LyricContent\s*=\s*"([^"]*)"/.exec(xmlStr); + // 如果没有匹配到 XML 结构,假设输入本身就是内容 (保持原有逻辑的兼容性) - return match?.[1] || xmlStr; + const result = match?.[1] || xmlStr; + return decodeXml(result); }; // 避免每次调用时的运行时检查 diff --git a/src/utils/style.ts b/src/utils/style.ts new file mode 100644 index 000000000..faa5085e6 --- /dev/null +++ b/src/utils/style.ts @@ -0,0 +1,13 @@ + +/** + * 获取自适应或固定像素的字体大小 + * @param size 字体大小数值 + * @param mode 字体大小模式 ('adaptive' | 'fixed') + * @returns CSS font-size 字符串 + */ +export const getFontSize = (size: number, mode: string) => { + if (mode === "adaptive") { + return `calc(${size} / 1080 * 100vh)`; + } + return `${size}px`; +};