From e2ebbe12e4b27518dc437db7956acf987e8686b8 Mon Sep 17 00:00:00 2001 From: alger Date: Fri, 19 Dec 2025 00:14:24 +0800 Subject: [PATCH] =?UTF-8?q?=20feat=EF=BC=9A=E4=BC=98=E5=8C=96=E5=85=A8?= =?UTF-8?q?=E5=B1=8F=E6=AD=8C=E8=AF=8D=E7=95=8C=E9=9D=A2=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=83=8C=E6=99=AF=E5=92=8C=E5=AE=BD=E5=BA=A6=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/lang/en-US/settings.ts | 43 +- src/i18n/lang/ja-JP/settings.ts | 43 +- src/i18n/lang/ko-KR/settings.ts | 43 +- src/i18n/lang/zh-CN/settings.ts | 43 +- src/i18n/lang/zh-Hant/settings.ts | 101 ++ .../components/lyric/LyricSettings.vue | 917 ++++++++++++++---- src/renderer/components/lyric/MusicFull.vue | 562 +++++++---- src/renderer/types/lyric.ts | 28 +- 8 files changed, 1366 insertions(+), 414 deletions(-) diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts index 00ac37e..94b5a61 100644 --- a/src/i18n/lang/en-US/settings.ts +++ b/src/i18n/lang/en-US/settings.ts @@ -223,6 +223,7 @@ export default { display: 'Display', interface: 'Interface', typography: 'Typography', + background: 'Background', mobile: 'Mobile' }, pureMode: 'Pure Mode', @@ -257,6 +258,7 @@ export default { default: 'Default', loose: 'Loose' }, + contentWidth: 'Content Width', mobileLayout: 'Mobile Layout', layoutOptions: { default: 'Default', @@ -270,7 +272,46 @@ export default { full: 'Full Screen' }, lyricLines: 'Lyric Lines', - mobileUnavailable: 'This setting is only available on mobile devices' + mobileUnavailable: 'This setting is only available on mobile devices', + // Background settings + background: { + useCustomBackground: 'Use Custom Background', + backgroundMode: 'Background Mode', + modeOptions: { + solid: 'Solid', + gradient: 'Gradient', + image: 'Image', + css: 'Custom CSS' + }, + solidColor: 'Select Color', + presetColors: 'Preset Colors', + customColor: 'Custom Color', + gradientEditor: 'Gradient Editor', + gradientColors: 'Gradient Colors', + gradientDirection: 'Gradient Direction', + directionOptions: { + toBottom: 'Top to Bottom', + toRight: 'Left to Right', + toBottomRight: 'Top Left to Bottom Right', + angle45: '45 Degrees', + toTop: 'Bottom to Top', + toLeft: 'Right to Left' + }, + addColor: 'Add Color', + removeColor: 'Remove Color', + imageUpload: 'Upload Image', + imagePreview: 'Image Preview', + clearImage: 'Clear Image', + imageBlur: 'Blur', + imageBrightness: 'Brightness', + customCss: 'Custom CSS Style', + customCssPlaceholder: 'Enter CSS style, e.g.: background: linear-gradient(...)', + customCssHelp: 'Supports any CSS background property', + reset: 'Reset to Default', + fileSizeLimit: 'Image size limit: 20MB', + invalidImageFormat: 'Invalid image format', + imageTooLarge: 'Image too large, please select an image smaller than 20MB' + } }, translationEngine: 'Lyric Translation Engine', translationEngineOptions: { diff --git a/src/i18n/lang/ja-JP/settings.ts b/src/i18n/lang/ja-JP/settings.ts index 078b562..fe70238 100644 --- a/src/i18n/lang/ja-JP/settings.ts +++ b/src/i18n/lang/ja-JP/settings.ts @@ -222,6 +222,7 @@ export default { display: '表示', interface: 'インターフェース', typography: 'テキスト', + background: '背景', mobile: 'モバイル' }, pureMode: 'ピュアモード', @@ -256,6 +257,7 @@ export default { default: 'デフォルト', loose: 'ゆったり' }, + contentWidth: 'コンテンツ幅', mobileLayout: 'モバイルレイアウト', layoutOptions: { default: 'デフォルト', @@ -269,7 +271,46 @@ export default { full: 'フルスクリーン' }, lyricLines: '歌詞行数', - mobileUnavailable: 'この設定はモバイルでのみ利用可能です' + mobileUnavailable: 'この設定はモバイルでのみ利用可能です', + // 背景設定 + background: { + useCustomBackground: 'カスタム背景を使用', + backgroundMode: '背景モード', + modeOptions: { + solid: '単色', + gradient: 'グラデーション', + image: '画像', + css: 'カスタム CSS' + }, + solidColor: '色を選択', + presetColors: 'プリセットカラー', + customColor: 'カスタムカラー', + gradientEditor: 'グラデーションエディター', + gradientColors: 'グラデーションカラー', + gradientDirection: 'グラデーション方向', + directionOptions: { + toBottom: '上から下', + toRight: '左から右', + toBottomRight: '左上から右下', + angle45: '45度', + toTop: '下から上', + toLeft: '右から左' + }, + addColor: '色を追加', + removeColor: '色を削除', + imageUpload: '画像をアップロード', + imagePreview: '画像プレビュー', + clearImage: '画像をクリア', + imageBlur: 'ぼかし', + imageBrightness: '明るさ', + customCss: 'カスタム CSS スタイル', + customCssPlaceholder: 'CSSスタイルを入力、例: background: linear-gradient(...)', + customCssHelp: '任意のCSS background プロパティをサポート', + reset: 'デフォルトにリセット', + fileSizeLimit: '画像サイズ制限: 20MB', + invalidImageFormat: '無効な画像形式', + imageTooLarge: '画像が大きすぎます。20MB未満の画像を選択してください' + } }, translationEngine: '歌詞翻訳エンジン', translationEngineOptions: { diff --git a/src/i18n/lang/ko-KR/settings.ts b/src/i18n/lang/ko-KR/settings.ts index f665e94..f993446 100644 --- a/src/i18n/lang/ko-KR/settings.ts +++ b/src/i18n/lang/ko-KR/settings.ts @@ -223,6 +223,7 @@ export default { display: '표시', interface: '인터페이스', typography: '텍스트', + background: '배경', mobile: '모바일' }, pureMode: '순수 모드', @@ -257,6 +258,7 @@ export default { default: '기본', loose: '넓음' }, + contentWidth: '콘텐츠 너비', mobileLayout: '모바일 레이아웃', layoutOptions: { default: '기본', @@ -270,7 +272,46 @@ export default { full: '전체화면' }, lyricLines: '가사 줄 수', - mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다' + mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다', + // 배경 설정 + background: { + useCustomBackground: '사용자 정의 배경 사용', + backgroundMode: '배경 모드', + modeOptions: { + solid: '단색', + gradient: '그라데이션', + image: '이미지', + css: '사용자 정의 CSS' + }, + solidColor: '색상 선택', + presetColors: '프리셋 색상', + customColor: '사용자 정의 색상', + gradientEditor: '그라데이션 편집기', + gradientColors: '그라데이션 색상', + gradientDirection: '그라데이션 방향', + directionOptions: { + toBottom: '위에서 아래로', + toRight: '왼쪽에서 오른쪽으로', + toBottomRight: '왼쪽 위에서 오른쪽 아래로', + angle45: '45도', + toTop: '아래에서 위로', + toLeft: '오른쪽에서 왼쪽으로' + }, + addColor: '색상 추가', + removeColor: '색상 제거', + imageUpload: '이미지 업로드', + imagePreview: '이미지 미리보기', + clearImage: '이미지 지우기', + imageBlur: '흐림', + imageBrightness: '밝기', + customCss: '사용자 정의 CSS 스타일', + customCssPlaceholder: 'CSS 스타일 입력, 예: background: linear-gradient(...)', + customCssHelp: '모든 CSS background 속성 지원', + reset: '기본값으로 재설정', + fileSizeLimit: '이미지 크기 제한: 20MB', + invalidImageFormat: '잘못된 이미지 형식', + imageTooLarge: '이미지가 너무 큽니다. 20MB 미만의 이미지를 선택하세요' + } }, translationEngine: '가사 번역 엔진', translationEngineOptions: { diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts index de444fe..8c31947 100644 --- a/src/i18n/lang/zh-CN/settings.ts +++ b/src/i18n/lang/zh-CN/settings.ts @@ -220,6 +220,7 @@ export default { display: '显示', interface: '界面', typography: '文字', + background: '背景', mobile: '移动端' }, pureMode: '纯净模式', @@ -254,6 +255,7 @@ export default { default: '默认', loose: '宽松' }, + contentWidth: '内容区宽度', mobileLayout: '移动端布局', layoutOptions: { default: '默认', @@ -267,7 +269,46 @@ export default { full: '全屏' }, lyricLines: '歌词行数', - mobileUnavailable: '此设置仅在移动端可用' + mobileUnavailable: '此设置仅在移动端可用', + // 背景设置 + background: { + useCustomBackground: '使用自定义背景', + backgroundMode: '背景模式', + modeOptions: { + solid: '纯色', + gradient: '渐变', + image: '图片', + css: '自定义 CSS' + }, + solidColor: '选择颜色', + presetColors: '预设颜色', + customColor: '自定义颜色', + gradientEditor: '渐变编辑器', + gradientColors: '渐变颜色', + gradientDirection: '渐变方向', + directionOptions: { + toBottom: '上到下', + toRight: '左到右', + toBottomRight: '左上到右下', + angle45: '45度', + toTop: '下到上', + toLeft: '右到左' + }, + addColor: '添加颜色', + removeColor: '移除颜色', + imageUpload: '上传图片', + imagePreview: '图片预览', + clearImage: '清除图片', + imageBlur: '模糊度', + imageBrightness: '明暗度', + customCss: '自定义 CSS 样式', + customCssPlaceholder: '输入 CSS 样式,如: background: linear-gradient(...)', + customCssHelp: '支持任意 CSS background 属性', + reset: '重置为默认', + fileSizeLimit: '图片大小限制: 20MB', + invalidImageFormat: '无效的图片格式', + imageTooLarge: '图片过大,请选择小于 20MB 的图片' + } }, translationEngine: '歌詞翻譯引擎', translationEngineOptions: { diff --git a/src/i18n/lang/zh-Hant/settings.ts b/src/i18n/lang/zh-Hant/settings.ts index d97f874..8d0094b 100644 --- a/src/i18n/lang/zh-Hant/settings.ts +++ b/src/i18n/lang/zh-Hant/settings.ts @@ -217,6 +217,7 @@ export default { display: '顯示', interface: '介面', typography: '文字', + background: '背景', mobile: '行動端' }, pureMode: '純淨模式', @@ -244,6 +245,66 @@ export default { compact: '緊湊', default: '預設', loose: '寬鬆' + }, + lineHeight: '行高', + lineHeightMarks: { + compact: '緊湊', + default: '預設', + loose: '寬鬆' + }, + contentWidth: '內容區寬度', + mobileLayout: '行動端佈局', + layoutOptions: { + default: '預設', + ios: 'iOS 風格', + android: 'Android 風格' + }, + mobileCoverStyle: '封面風格', + coverOptions: { + record: '唱片', + square: '方形', + full: '全螢幕' + }, + lyricLines: '歌詞行數', + mobileUnavailable: '此設定僅在行動端可用', + // 背景設定 + background: { + useCustomBackground: '使用自訂背景', + backgroundMode: '背景模式', + modeOptions: { + solid: '純色', + gradient: '漸層', + image: '圖片', + css: '自訂 CSS' + }, + solidColor: '選擇顏色', + presetColors: '預設顏色', + customColor: '自訂顏色', + gradientEditor: '漸層編輯器', + gradientColors: '漸層顏色', + gradientDirection: '漸層方向', + directionOptions: { + toBottom: '上到下', + toRight: '左到右', + toBottomRight: '左上到右下', + angle45: '45度', + toTop: '下到上', + toLeft: '右到左' + }, + addColor: '新增顏色', + removeColor: '移除顏色', + imageUpload: '上傳圖片', + imagePreview: '圖片預覽', + clearImage: '清除圖片', + imageBlur: '模糊度', + imageBrightness: '明暗度', + customCss: '自訂 CSS 樣式', + customCssPlaceholder: '輸入 CSS 樣式,如: background: linear-gradient(...)', + customCssHelp: '支援任意 CSS background 屬性', + reset: '重設為預設', + fileSizeLimit: '圖片大小限制: 20MB', + invalidImageFormat: '無效的圖片格式', + imageTooLarge: '圖片過大,請選擇小於 20MB 的圖片' } }, themeColor: { @@ -271,6 +332,46 @@ export default { none: '關閉', opencc: 'OpenCC 繁化' }, + shortcutSettings: { + title: '快捷鍵設定', + shortcut: '快捷鍵', + shortcutDesc: '自訂快捷鍵', + shortcutConflict: '快捷鍵衝突', + inputPlaceholder: '點擊輸入快捷鍵', + resetShortcuts: '恢復預設', + disableAll: '全部停用', + enableAll: '全部啟用', + togglePlay: '播放/暫停', + prevPlay: '上一首', + nextPlay: '下一首', + volumeUp: '增加音量', + volumeDown: '減少音量', + toggleFavorite: '收藏/取消收藏', + toggleWindow: '顯示/隱藏視窗', + scopeGlobal: '全域', + scopeApp: '應用程式內', + enabled: '已啟用', + disabled: '已停用', + messages: { + resetSuccess: '已恢復預設快捷鍵,請記得儲存', + conflict: '存在快捷鍵衝突,請重新設定', + saveSuccess: '快捷鍵設定已儲存', + saveError: '快捷鍵儲存失敗,請重試', + cancelEdit: '已取消修改', + disableAll: '已停用所有快捷鍵,請記得儲存', + enableAll: '已啟用所有快捷鍵,請記得儲存' + } + }, + remoteControl: { + title: '遠端控制', + enable: '啟用遠端控制', + port: '服務連接埠', + allowedIps: '允許的 IP 位址', + addIp: '新增 IP', + emptyListHint: '空白清單表示允許所有 IP 存取', + saveSuccess: '遠端控制設定已儲存', + accessInfo: '遠端控制存取位址:' + }, cookie: { title: 'Cookie設定', description: '請輸入網易雲音樂的Cookie:', diff --git a/src/renderer/components/lyric/LyricSettings.vue b/src/renderer/components/lyric/LyricSettings.vue index 80bdc71..b69d328 100644 --- a/src/renderer/components/lyric/LyricSettings.vue +++ b/src/renderer/components/lyric/LyricSettings.vue @@ -1,116 +1,365 @@ @@ -124,26 +373,82 @@ import { DEFAULT_LYRIC_CONFIG, LyricConfig } from '@/types/lyric'; const { t } = useI18n(); const config = ref({ ...DEFAULT_LYRIC_CONFIG }); const emit = defineEmits(['themeChange']); +const message = window.$message; +const activeTab = ref('display'); +const fileInput = ref(); + +const tabs = computed(() => [ + { key: 'display', label: t('settings.lyricSettings.tabs.display') }, + { key: 'interface', label: t('settings.lyricSettings.tabs.interface') }, + { key: 'typography', label: t('settings.lyricSettings.tabs.typography') }, + { key: 'background', label: t('settings.lyricSettings.tabs.background') } +]); -// 显示mini播放栏开关 const showMiniPlayBar = computed({ get: () => !config.value.hideMiniPlayBar, set: (value: boolean) => { - if (value) { - // 显示mini播放栏,隐藏普通播放栏 - config.value.hideMiniPlayBar = false; - config.value.hidePlayBar = true; - } else { - // 显示普通播放栏,隐藏mini播放栏 - config.value.hideMiniPlayBar = true; - config.value.hidePlayBar = false; - } + config.value.hideMiniPlayBar = !value; + config.value.hidePlayBar = value; } }); +const gradientDirectionOptions = computed(() => [ + { label: t('settings.lyricSettings.background.directionOptions.toBottom'), value: 'to bottom' }, + { label: t('settings.lyricSettings.background.directionOptions.toTop'), value: 'to top' }, + { label: t('settings.lyricSettings.background.directionOptions.toRight'), value: 'to right' }, + { label: t('settings.lyricSettings.background.directionOptions.toLeft'), value: 'to left' }, + { + label: t('settings.lyricSettings.background.directionOptions.toBottomRight'), + value: 'to bottom right' + }, + { label: t('settings.lyricSettings.background.directionOptions.angle45'), value: '45deg' } +]); + +const addGradientColor = () => { + if (config.value.gradientColors.colors.length < 5) { + config.value.gradientColors.colors.push('#666666'); + } +}; + +const removeGradientColor = (index: number) => { + if (config.value.gradientColors.colors.length > 2) { + config.value.gradientColors.colors.splice(index, 1); + } +}; + +const handleImageChange = (event: Event) => { + const target = event.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) return; + + if (!file.type.startsWith('image/')) { + message?.error(t('settings.lyricSettings.background.invalidImageFormat')); + return; + } + + if (file.size > 20 * 1024 * 1024) { + message?.error(t('settings.lyricSettings.background.imageTooLarge')); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + config.value.backgroundImage = e.target?.result as string; + }; + reader.readAsDataURL(file); +}; + +const clearBackgroundImage = () => { + config.value.backgroundImage = undefined; + if (fileInput.value) { + fileInput.value.value = ''; + } +}; + watch( () => config.value, (newConfig) => { + localStorage.setItem('music-full-config', JSON.stringify(newConfig)); updateCSSVariables(newConfig); }, { deep: true } @@ -175,98 +480,304 @@ defineExpose({ }); - diff --git a/src/renderer/components/lyric/MusicFull.vue b/src/renderer/components/lyric/MusicFull.vue index ed18295..7893b91 100644 --- a/src/renderer/components/lyric/MusicFull.vue +++ b/src/renderer/components/lyric/MusicFull.vue @@ -3,19 +3,34 @@ v-model:show="isVisible" height="100%" placement="bottom" - :style="{ background: currentBackground || background }" + :style="drawerBaseStyle" :to="`#layout-main`" :z-index="9998" > -
+ +
+
+
+
+ +
-
-
-
- -
-
-
-
- - - {{ item.name }} - {{ index < artistList.length - 1 ? ' / ' : '' }} - - -
- +
+
-
- + +
- -
-
-
-
+
+ +
+
+
+
+ -
-
- -
- {{ t('player.lrc.noAutoScroll') }} -
-
- -
- -
- - {{ item.text }} -
- {{ item.trText }} -
-
- - -
- {{ t('player.lrc.noLrc') }} +
+
- - - +
+ + +
+ + +
+
+
+
+ + {{ item.name }} + {{ index < artistList.length - 1 ? ' / ' : '' }} + +
+
+ +
+ {{ t('player.lrc.noAutoScroll') }} +
+
+ +
+ +
+ + {{ item.text }} +
+ {{ item.trText }} +
+
+ + +
+ {{ t('player.lrc.noLrc') }} +
+
+ + +
+
@@ -197,9 +216,57 @@ const lrcContainer = ref(null); const currentBackground = ref(''); const animationFrame = ref(null); const isDark = ref(false); + +// 计算自定义背景样式 +const customBackgroundStyle = computed(() => { + if (!config.value.useCustomBackground) { + return null; + } + + switch (config.value.backgroundMode) { + case 'solid': + return config.value.solidColor; + case 'gradient': { + const { colors, direction } = config.value.gradientColors; + return `linear-gradient(${direction}, ${colors.join(', ')})`; + } + case 'image': + if (!config.value.backgroundImage) return null; + // 构建完整的背景样式,包括滤镜效果 + return config.value.backgroundImage; + case 'css': + return config.value.customCss || null; + default: + return null; + } +}); + +// drawer 基础样式(非图片模式) +const drawerBaseStyle = computed(() => { + // 图片模式时不设置背景,使用单独的背景层 + if (config.value.useCustomBackground && config.value.backgroundMode === 'image') { + return { background: 'transparent' }; + } + // 其他模式正常设置背景 + if (config.value.useCustomBackground && customBackgroundStyle.value) { + return { background: customBackgroundStyle.value }; + } + return { background: currentBackground.value || props.background }; +}); + +// 背景图片层样式(只在图片模式下使用) +const backgroundImageStyle = computed(() => { + const blur = config.value.imageBlur || 0; + const brightness = config.value.imageBrightness || 100; + return { + backgroundImage: `url(${config.value.backgroundImage})`, + filter: `blur(${blur}px) brightness(${brightness}%)` + }; +}); const showStickyHeader = ref(false); const lyricSettingsRef = ref>(); const isSongChanging = ref(false); +const isFullScreen = ref(false); const config = ref({ ...DEFAULT_LYRIC_CONFIG }); @@ -355,7 +422,12 @@ const setTextColors = (background: string) => { watch( () => props.background, (newBg) => { - if (config.value.theme === 'default') { + if (config.value.useCustomBackground) { + // 使用自定义背景时,根据自定义背景计算文字颜色 + if (customBackgroundStyle.value) { + setTextColors(customBackgroundStyle.value); + } + } else if (config.value.theme === 'default') { setTextColors(newBg); } else { setTextColors(themeMusic[config.value.theme] || props.background); @@ -364,6 +436,24 @@ watch( { immediate: true } ); +// 监听自定义背景配置变化 +watch( + () => [config.value.useCustomBackground, customBackgroundStyle.value] as const, + ([useCustom, customBg]) => { + if (useCustom && customBg && typeof customBg === 'string') { + setTextColors(customBg); + } else { + // 回退到主题模式 + if (config.value.theme === 'default') { + setTextColors(props.background); + } else { + setTextColors(themeMusic[config.value.theme] || props.background); + } + } + }, + { deep: true } +); + const { getLrcStyle: originalLrcStyle } = useLyricProgress(); const getLrcStyle = (index: number) => { @@ -519,18 +609,45 @@ const handleScroll = () => { const playerStore = usePlayerStore(); const closeMusicFull = () => { + // 退出全屏模式 + if (isFullScreen.value && document.fullscreenElement) { + document.exitFullscreen(); + } isVisible.value = false; playerStore.setMusicFull(false); }; -// 添加滚动监听 +// 全屏切换方法 +const toggleFullScreen = async () => { + try { + if (!document.fullscreenElement) { + // 进入全屏 + await document.documentElement.requestFullscreen(); + isFullScreen.value = true; + } else { + // 退出全屏 + await document.exitFullscreen(); + isFullScreen.value = false; + } + } catch (error) { + console.error('全屏切换失败:', error); + } +}; + +// 监听全屏状态变化 +const handleFullScreenChange = () => { + isFullScreen.value = !!document.fullscreenElement; +}; + +// 添加滚动监听和全屏状态监听 onMounted(() => { if (lrcSider.value?.$el) { lrcSider.value.$el.addEventListener('scroll', handleScroll); } + document.addEventListener('fullscreenchange', handleFullScreenChange); }); -// 移除滚动监听 +// 移除滚动监听和全屏状态监听 onBeforeUnmount(() => { if (animationFrame.value) { cancelAnimationFrame(animationFrame.value); @@ -538,6 +655,11 @@ onBeforeUnmount(() => { if (lrcSider.value?.$el) { lrcSider.value.$el.removeEventListener('scroll', handleScroll); } + document.removeEventListener('fullscreenchange', handleFullScreenChange); + // 退出全屏模式 + if (document.fullscreenElement) { + document.exitFullscreen(); + } }); // 监听字体大小变化 @@ -621,6 +743,18 @@ defineExpose({ } } +.background-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: 0; +} + .drawer-back { @apply absolute bg-cover bg-center; z-index: -1; @@ -635,127 +769,138 @@ defineExpose({ } #drawer-target { - @apply top-0 left-0 absolute overflow-hidden rounded px-24 flex items-center justify-center w-full h-full py-8; + @apply top-0 left-0 absolute overflow-hidden rounded w-full h-full; animation-duration: 300ms; - .music-img { - @apply flex-1 flex justify-center mr-16 flex-col items-center; - max-width: 360px; - max-height: 360px; + .content-wrapper { + @apply grid items-center mx-auto h-full; + grid-template-columns: minmax(300px, 40%) 1fr; + gap: 4rem; + max-width: 1600px; + padding: 2rem; + transition: width 0.3s ease; + + @media (max-width: 1024px) { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + gap: 2rem; + } + } + + .left-side { + @apply flex flex-col items-center justify-center h-full; transition: all 0.3s ease; &.only-cover { - @apply mr-0 flex-initial; - max-width: none; - max-height: none; + @apply col-span-2; .img-container { - @apply w-[50vh] h-[50vh] mb-8; + @apply w-[60vh] aspect-square; } .music-info { - @apply text-center w-[600px]; - - .music-content-name { - @apply text-4xl mb-4 line-clamp-2; - color: var(--text-color-active); - } - - .music-content-singer { - @apply text-xl mb-8 opacity-80; - color: var(--text-color-primary); - } + @apply max-w-[800px]; } } .img-container { - @apply relative w-full h-full; + @apply relative w-[45vh] mb-8 aspect-square; + max-width: 100%; } .music-info { - @apply w-full mt-4; + @apply w-full text-center max-w-[400px]; .music-content-name { - @apply text-2xl font-bold; + @apply text-3xl font-bold mb-2 line-clamp-2; color: var(--text-color-active); } .music-content-singer { - @apply text-base mt-2 opacity-80; + @apply text-lg opacity-80; color: var(--text-color-primary); } } } - .music-content { - @apply flex flex-col justify-center items-center relative; - width: 500px; - transition: all 0.3s ease; + .right-side { + @apply flex flex-col justify-center h-full relative overflow-hidden; + + &.full-width { + @apply col-span-2; + } &.center { - @apply w-auto; - .music-lrc { - @apply w-full max-w-3xl mx-auto; + @apply w-full mx-auto text-center; } .music-lrc-text { @apply text-center; + transform-origin: center center; + } + + .word-by-word-lyric { + @apply justify-center; } } &.hide { @apply hidden; } - } - .music-content-time { - display: none; - @apply flex justify-center items-center; - } + .music-lrc { + @apply w-full h-full bg-transparent; + mask-image: linear-gradient( + to bottom, + transparent 0%, + black 15%, + black 85%, + transparent 100% + ); + -webkit-mask-image: linear-gradient( + to bottom, + transparent 0%, + black 15%, + black 85%, + transparent 100% + ); - .music-lrc-container { - padding-top: 30vh; - .music-lrc-text:last-child { - margin-bottom: 200px; - } - } + .music-info-header { + @apply mb-8; - .music-lrc { - background-color: inherit; - width: 500px; - height: 550px; - position: relative; - mask-image: linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%); - -webkit-mask-image: linear-gradient( - to bottom, - transparent 0%, - black 10%, - black 90%, - transparent 100% - ); + .music-info-name { + @apply text-4xl font-bold mb-2 line-clamp-2; + color: var(--text-color-active); + } - .music-info-header { - @apply mb-8; - - .music-info-name { - @apply text-4xl font-bold mb-2 line-clamp-2; - color: var(--text-color-active); - } - - .music-info-singer { - @apply text-base; - color: var(--text-color-primary); + .music-info-singer { + @apply text-xl opacity-80; + color: var(--text-color-primary); + } } } - &-text { - @apply text-2xl cursor-pointer font-bold px-2 py-4; + .music-lrc-container { + padding: 50vh 0; + min-height: 100%; + } + + .music-lrc-text { + @apply text-2xl cursor-pointer font-bold px-4 py-3; + font-family: var(--current-font-family); transition: all 0.3s ease; background-color: transparent; font-size: var(--lyric-font-size, 22px) !important; letter-spacing: var(--lyric-letter-spacing, 0) !important; line-height: var(--lyric-line-height, 2) !important; + opacity: 0.6; + transform-origin: left center; + + &.now-text { + opacity: 1; + transform: scale(1.05); + } &.no-scroll-tip { @apply text-base opacity-60 cursor-default py-2; @@ -819,7 +964,11 @@ defineExpose({ .mobile { #drawer-target { - @apply flex-col p-4 pt-8 justify-start; + @apply p-4 pt-8; + + .content-wrapper { + @apply flex-col justify-start p-0; + } .music-img { display: none; @@ -856,21 +1005,13 @@ defineExpose({ } // 添加全局字体样式 +// 字体设置已移至上方或不再需要单独的 drawer-target 块 :root { --current-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } -#drawer-target { - @apply top-0 left-0 absolute overflow-hidden rounded px-24 flex items-center justify-center w-full h-full py-8; - animation-duration: 300ms; - - .music-lrc-text { - font-family: var(--current-font-family); - } -} - .close-btn { opacity: 0.3; transition: opacity 0.3s ease; @@ -880,20 +1021,19 @@ defineExpose({ } } -.control-buttons-container { - @apply flex justify-between items-start z-[9999]; - +.control-left, +.control-right { &.pure-mode { - @apply pointer-events-auto; /* 容器需要能接收hover事件 */ + @apply pointer-events-auto; .control-btn { @apply opacity-0 transition-all duration-300; - pointer-events: none; /* 按钮隐藏时不接收事件 */ + pointer-events: none; } &:hover .control-btn { @apply opacity-100; - pointer-events: auto; /* hover时按钮可以点击 */ + pointer-events: auto; } } @@ -902,6 +1042,10 @@ defineExpose({ } } +.control-right { + @apply flex items-center gap-2; +} + .control-btn { @apply w-9 h-9 flex items-center justify-center rounded cursor-pointer transition-all duration-300; background: rgba(142, 142, 142, 0.192); @@ -927,4 +1071,10 @@ defineExpose({ pointer-events: auto !important; } } + +/* 移除 Popover padding */ +:deep(.n-popover) { + padding: 0 !important; + background-color: transparent !important; +} diff --git a/src/renderer/types/lyric.ts b/src/renderer/types/lyric.ts index 8294744..1d3a4bc 100644 --- a/src/renderer/types/lyric.ts +++ b/src/renderer/types/lyric.ts @@ -11,10 +11,23 @@ export interface LyricConfig { pureModeEnabled: boolean; hideMiniPlayBar: boolean; hideLyrics: boolean; + contentWidth: number; // 内容区域宽度百分比 // 移动端配置 mobileLayout: 'default' | 'ios' | 'android'; mobileCoverStyle: 'record' | 'square' | 'full'; mobileShowLyricLines: number; + // 背景自定义功能 + useCustomBackground: boolean; // 是否使用自定义背景 + backgroundMode: 'solid' | 'gradient' | 'image' | 'css'; // 背景模式 + solidColor: string; // 纯色背景颜色值 + gradientColors: { + colors: string[]; // 渐变颜色数组 + direction: string; // 渐变方向 + }; + backgroundImage?: string; // 图片背景 (Base64 或 URL) + imageBlur: number; // 图片模糊度 (0-20px) + imageBrightness: number; // 图片明暗度 (0-200%, 100为正常) + customCss?: string; // 自定义 CSS 样式 } export const DEFAULT_LYRIC_CONFIG: LyricConfig = { @@ -29,12 +42,25 @@ export const DEFAULT_LYRIC_CONFIG: LyricConfig = { hideMiniPlayBar: false, pureModeEnabled: false, hideLyrics: false, + contentWidth: 100, // 默认100%宽度 // 移动端默认配置 mobileLayout: 'ios', mobileCoverStyle: 'full', mobileShowLyricLines: 3, // 翻译引擎: 'none' or 'opencc' - translationEngine: 'none' + translationEngine: 'none', + // 背景自定义功能默认值 + useCustomBackground: false, + backgroundMode: 'solid', + solidColor: '#1a1a1a', + gradientColors: { + colors: ['#1a1a1a', '#000000'], + direction: 'to bottom' + }, + backgroundImage: undefined, + imageBlur: 0, + imageBrightness: 100, + customCss: undefined }; export interface ILyric {