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`;
+};