mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
feat:优化全屏歌词界面 添加背景和宽度设置
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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:',
|
||||
|
||||
@@ -1,116 +1,365 @@
|
||||
<template>
|
||||
<div class="settings-panel transparent-popover">
|
||||
<div class="settings-title">{{ t('settings.lyricSettings.title') }}</div>
|
||||
<div class="settings-content">
|
||||
<n-tabs type="line" animated size="small">
|
||||
<!-- 显示设置 -->
|
||||
<n-tab-pane :name="'display'" :tab="t('settings.lyricSettings.tabs.display')">
|
||||
<div class="tab-content">
|
||||
<div class="settings-grid">
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.pureMode') }}</span>
|
||||
<n-switch v-model:value="config.pureModeEnabled" />
|
||||
<div
|
||||
class="w-96 rounded-2xl bg-white/5 backdrop-blur-3xl border border-white/10 shadow-2xl overflow-hidden"
|
||||
>
|
||||
<!-- 标题栏 -->
|
||||
<div class="px-6 py-4 border-b border-white/5">
|
||||
<h2 class="text-lg font-semibold tracking-tight" style="color: var(--text-color-active)">
|
||||
{{ t('settings.lyricSettings.title') }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- 标签页导航 -->
|
||||
<div class="px-4 pt-3 pb-2">
|
||||
<div class="flex gap-1 p-1 bg-black/20 rounded-xl">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
@click="activeTab = tab.key"
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
activeTab === tab.key
|
||||
? 'bg-emerald-500 text-white shadow-lg shadow-emerald-500/30'
|
||||
: 'hover:bg-white/5'
|
||||
]"
|
||||
:style="activeTab !== tab.key ? 'color: var(--text-color-primary); opacity: 0.7' : ''"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div
|
||||
class="px-4 pb-4 max-h-[500px] overflow-y-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent"
|
||||
>
|
||||
<!-- 显示设置 -->
|
||||
<div v-show="activeTab === 'display'" class="space-y-3 pt-3">
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.pureMode') }}</span>
|
||||
<input type="checkbox" v-model="config.pureModeEnabled" class="toggle-switch" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.hideCover') }}</span>
|
||||
<input type="checkbox" v-model="config.hideCover" class="toggle-switch" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.centerDisplay') }}</span>
|
||||
<input type="checkbox" v-model="config.centerLyrics" class="toggle-switch" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.showTranslation') }}</span>
|
||||
<input type="checkbox" v-model="config.showTranslation" class="toggle-switch" />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.hideLyrics') }}</span>
|
||||
<input type="checkbox" v-model="config.hideLyrics" class="toggle-switch" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 界面设置 -->
|
||||
<div v-show="activeTab === 'interface'" class="space-y-4 pt-3">
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.showMiniPlayBar') }}</span>
|
||||
<input type="checkbox" v-model="showMiniPlayBar" class="toggle-switch" />
|
||||
</div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{ t('settings.lyricSettings.contentWidth') }}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.contentWidth"
|
||||
min="50"
|
||||
max="100"
|
||||
step="5"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>50%</span>
|
||||
<span>75%</span>
|
||||
<span>100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文字设置 -->
|
||||
<div v-show="activeTab === 'typography'" class="space-y-4 pt-3">
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{ t('settings.lyricSettings.fontSize') }}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.fontSize"
|
||||
min="12"
|
||||
max="32"
|
||||
step="1"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>{{ t('settings.lyricSettings.fontSizeMarks.small') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.fontSizeMarks.medium') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.fontSizeMarks.large') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{ t('settings.lyricSettings.letterSpacing') }}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.letterSpacing"
|
||||
min="-2"
|
||||
max="10"
|
||||
step="0.2"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>{{ t('settings.lyricSettings.letterSpacingMarks.compact') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.letterSpacingMarks.default') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.letterSpacingMarks.loose') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{ t('settings.lyricSettings.lineHeight') }}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.lineHeight"
|
||||
min="1"
|
||||
max="3"
|
||||
step="0.1"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>{{ t('settings.lyricSettings.lineHeightMarks.compact') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.lineHeightMarks.default') }}</span>
|
||||
<span>{{ t('settings.lyricSettings.lineHeightMarks.loose') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景设置 -->
|
||||
<div v-show="activeTab === 'background'" class="space-y-4 pt-3">
|
||||
<div class="setting-item">
|
||||
<span>{{ t('settings.lyricSettings.background.useCustomBackground') }}</span>
|
||||
<input type="checkbox" v-model="config.useCustomBackground" class="toggle-switch" />
|
||||
</div>
|
||||
|
||||
<!-- 主题选择 -->
|
||||
<div v-if="!config.useCustomBackground" class="radio-group">
|
||||
<label class="radio-label">{{ t('settings.lyricSettings.backgroundTheme') }}</label>
|
||||
<div class="space-y-2">
|
||||
<label class="radio-item">
|
||||
<input type="radio" v-model="config.theme" value="default" class="radio-input" />
|
||||
<span>{{ t('settings.lyricSettings.themeOptions.default') }}</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" v-model="config.theme" value="light" class="radio-input" />
|
||||
<span>{{ t('settings.lyricSettings.themeOptions.light') }}</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" v-model="config.theme" value="dark" class="radio-input" />
|
||||
<span>{{ t('settings.lyricSettings.themeOptions.dark') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景模式选择 -->
|
||||
<div v-if="config.useCustomBackground" class="radio-group">
|
||||
<label class="radio-label">{{
|
||||
t('settings.lyricSettings.background.backgroundMode')
|
||||
}}</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label class="radio-item-compact">
|
||||
<input
|
||||
type="radio"
|
||||
v-model="config.backgroundMode"
|
||||
value="solid"
|
||||
class="radio-input"
|
||||
/>
|
||||
<span>{{ t('settings.lyricSettings.background.modeOptions.solid') }}</span>
|
||||
</label>
|
||||
<label class="radio-item-compact">
|
||||
<input
|
||||
type="radio"
|
||||
v-model="config.backgroundMode"
|
||||
value="gradient"
|
||||
class="radio-input"
|
||||
/>
|
||||
<span>{{ t('settings.lyricSettings.background.modeOptions.gradient') }}</span>
|
||||
</label>
|
||||
<label class="radio-item-compact">
|
||||
<input
|
||||
type="radio"
|
||||
v-model="config.backgroundMode"
|
||||
value="image"
|
||||
class="radio-input"
|
||||
/>
|
||||
<span>{{ t('settings.lyricSettings.background.modeOptions.image') }}</span>
|
||||
</label>
|
||||
<label class="radio-item-compact">
|
||||
<input type="radio" v-model="config.backgroundMode" value="css" class="radio-input" />
|
||||
<span>{{ t('settings.lyricSettings.background.modeOptions.css') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 纯色模式 -->
|
||||
<div
|
||||
v-if="config.useCustomBackground && config.backgroundMode === 'solid'"
|
||||
class="color-picker-group"
|
||||
>
|
||||
<label class="color-picker-label">{{
|
||||
t('settings.lyricSettings.background.solidColor')
|
||||
}}</label>
|
||||
<input type="color" v-model="config.solidColor" class="color-picker" />
|
||||
</div>
|
||||
|
||||
<!-- 渐变模式 -->
|
||||
<div
|
||||
v-if="config.useCustomBackground && config.backgroundMode === 'gradient'"
|
||||
class="space-y-3"
|
||||
>
|
||||
<label class="color-picker-label">{{
|
||||
t('settings.lyricSettings.background.gradientEditor')
|
||||
}}</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div v-for="(_, index) in config.gradientColors.colors" :key="index" class="relative">
|
||||
<input
|
||||
type="color"
|
||||
v-model="config.gradientColors.colors[index]"
|
||||
class="color-picker-small"
|
||||
/>
|
||||
<button
|
||||
v-if="config.gradientColors.colors.length > 2"
|
||||
@click="removeGradientColor(index)"
|
||||
class="absolute -top-1 -right-1 w-5 h-5 flex items-center justify-center rounded-full bg-red-500 text-white text-xs hover:bg-red-600 transition-colors"
|
||||
>
|
||||
<i class="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="config.gradientColors.colors.length < 5"
|
||||
@click="addGradientColor"
|
||||
class="w-full py-2 px-4 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 transition-colors text-sm font-medium flex items-center justify-center gap-2"
|
||||
style="color: var(--text-color-active)"
|
||||
>
|
||||
<i class="ri-add-line"></i>
|
||||
{{ t('settings.lyricSettings.background.addColor') }}
|
||||
</button>
|
||||
|
||||
<div class="select-group">
|
||||
<label class="select-label">{{
|
||||
t('settings.lyricSettings.background.gradientDirection')
|
||||
}}</label>
|
||||
<select v-model="config.gradientColors.direction" class="select-input">
|
||||
<option v-for="opt in gradientDirectionOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片模式 -->
|
||||
<div
|
||||
v-if="config.useCustomBackground && config.backgroundMode === 'image'"
|
||||
class="space-y-3"
|
||||
>
|
||||
<label class="color-picker-label">{{
|
||||
t('settings.lyricSettings.background.imageUpload')
|
||||
}}</label>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="handleImageChange"
|
||||
class="hidden"
|
||||
ref="fileInput"
|
||||
/>
|
||||
<button
|
||||
@click="fileInput?.click()"
|
||||
class="w-full py-2 px-4 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 transition-colors text-sm font-medium flex items-center justify-center gap-2"
|
||||
style="color: var(--text-color-active)"
|
||||
>
|
||||
<i class="ri-image-add-line"></i>
|
||||
{{ t('settings.lyricSettings.background.imageUpload') }}
|
||||
</button>
|
||||
|
||||
<div v-if="config.backgroundImage" class="space-y-3">
|
||||
<div class="relative rounded-lg overflow-hidden border border-white/10">
|
||||
<img
|
||||
:src="config.backgroundImage"
|
||||
class="w-full max-h-40 object-cover"
|
||||
alt="Preview"
|
||||
/>
|
||||
<button
|
||||
@click="clearBackgroundImage"
|
||||
class="absolute top-2 right-2 p-2 rounded-lg bg-red-500/80 text-white hover:bg-red-500 transition-colors"
|
||||
>
|
||||
<i class="ri-delete-bin-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{
|
||||
t('settings.lyricSettings.background.imageBlur')
|
||||
}}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.imageBlur"
|
||||
min="0"
|
||||
max="20"
|
||||
step="1"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>0</span>
|
||||
<span>10</span>
|
||||
<span>20px</span>
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.hideCover') }}</span>
|
||||
<n-switch v-model:value="config.hideCover" />
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.centerDisplay') }}</span>
|
||||
<n-switch v-model:value="config.centerLyrics" />
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.showTranslation') }}</span>
|
||||
<n-switch v-model:value="config.showTranslation" />
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.hideLyrics') }}</span>
|
||||
<n-switch v-model:value="config.hideLyrics" />
|
||||
</div>
|
||||
|
||||
<div class="slider-group">
|
||||
<label class="slider-label">{{
|
||||
t('settings.lyricSettings.background.imageBrightness')
|
||||
}}</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="config.imageBrightness"
|
||||
min="0"
|
||||
max="200"
|
||||
step="5"
|
||||
class="slider-emerald"
|
||||
/>
|
||||
<div class="slider-marks">
|
||||
<span>暗</span>
|
||||
<span>正常</span>
|
||||
<span>亮</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 界面设置 -->
|
||||
<n-tab-pane :name="'interface'" :tab="t('settings.lyricSettings.tabs.interface')">
|
||||
<div class="tab-content">
|
||||
<div class="settings-grid">
|
||||
<div class="settings-item">
|
||||
<span>{{ t('settings.lyricSettings.showMiniPlayBar') }}</span>
|
||||
<n-switch v-model:value="showMiniPlayBar" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-section">
|
||||
<div class="section-title">{{ t('settings.lyricSettings.backgroundTheme') }}</div>
|
||||
<n-radio-group v-model:value="config.theme" name="theme" class="theme-radio-group">
|
||||
<n-space>
|
||||
<n-radio value="default">{{
|
||||
t('settings.lyricSettings.themeOptions.default')
|
||||
}}</n-radio>
|
||||
<n-radio value="light">{{
|
||||
t('settings.lyricSettings.themeOptions.light')
|
||||
}}</n-radio>
|
||||
<n-radio value="dark">{{
|
||||
t('settings.lyricSettings.themeOptions.dark')
|
||||
}}</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
<p class="text-xs" style="color: var(--text-color-primary); opacity: 0.5">
|
||||
{{ t('settings.lyricSettings.background.fileSizeLimit') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 文字设置 -->
|
||||
<n-tab-pane :name="'typography'" :tab="t('settings.lyricSettings.tabs.typography')">
|
||||
<div class="tab-content">
|
||||
<div class="slider-section">
|
||||
<div class="slider-item">
|
||||
<span>{{ t('settings.lyricSettings.fontSize') }}</span>
|
||||
<n-slider
|
||||
v-model:value="config.fontSize"
|
||||
:step="1"
|
||||
:min="12"
|
||||
:max="32"
|
||||
:marks="{
|
||||
12: t('settings.lyricSettings.fontSizeMarks.small'),
|
||||
22: t('settings.lyricSettings.fontSizeMarks.medium'),
|
||||
32: t('settings.lyricSettings.fontSizeMarks.large')
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="slider-item">
|
||||
<span>{{ t('settings.lyricSettings.letterSpacing') }}</span>
|
||||
<n-slider
|
||||
v-model:value="config.letterSpacing"
|
||||
:step="0.2"
|
||||
:min="-2"
|
||||
:max="10"
|
||||
:marks="{
|
||||
'-2': t('settings.lyricSettings.letterSpacingMarks.compact'),
|
||||
0: t('settings.lyricSettings.letterSpacingMarks.default'),
|
||||
10: t('settings.lyricSettings.letterSpacingMarks.loose')
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="slider-item">
|
||||
<span>{{ t('settings.lyricSettings.lineHeight') }}</span>
|
||||
<n-slider
|
||||
v-model:value="config.lineHeight"
|
||||
:step="0.1"
|
||||
:min="1"
|
||||
:max="3"
|
||||
:marks="{
|
||||
1: t('settings.lyricSettings.lineHeightMarks.compact'),
|
||||
1.5: t('settings.lyricSettings.lineHeightMarks.default'),
|
||||
3: t('settings.lyricSettings.lineHeightMarks.loose')
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
<!-- CSS 模式 -->
|
||||
<div v-if="config.useCustomBackground && config.backgroundMode === 'css'" class="space-y-2">
|
||||
<label class="color-picker-label">{{
|
||||
t('settings.lyricSettings.background.customCss')
|
||||
}}</label>
|
||||
<textarea
|
||||
v-model="config.customCss"
|
||||
:placeholder="t('settings.lyricSettings.background.customCssPlaceholder')"
|
||||
rows="4"
|
||||
class="w-full px-3 py-2 bg-black/20 border border-white/10 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/50 font-mono"
|
||||
style="color: var(--text-color-primary)"
|
||||
></textarea>
|
||||
<p class="text-xs" style="color: var(--text-color-primary); opacity: 0.5">
|
||||
{{ t('settings.lyricSettings.background.customCssHelp') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -124,26 +373,82 @@ import { DEFAULT_LYRIC_CONFIG, LyricConfig } from '@/types/lyric';
|
||||
const { t } = useI18n();
|
||||
const config = ref<LyricConfig>({ ...DEFAULT_LYRIC_CONFIG });
|
||||
const emit = defineEmits(['themeChange']);
|
||||
const message = window.$message;
|
||||
const activeTab = ref('display');
|
||||
const fileInput = ref<HTMLInputElement>();
|
||||
|
||||
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({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settings-panel {
|
||||
@apply p-4 w-80 rounded-lg relative overflow-hidden backdrop-blur-lg bg-black/10;
|
||||
|
||||
.settings-title {
|
||||
@apply text-base font-bold mb-4;
|
||||
color: var(--text-color-active);
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
:deep(.n-tabs-nav) {
|
||||
@apply mb-3;
|
||||
}
|
||||
|
||||
:deep(.n-tab-pane) {
|
||||
@apply p-0;
|
||||
}
|
||||
|
||||
:deep(.n-tabs-tab) {
|
||||
@apply text-xs;
|
||||
color: var(--text-color-primary);
|
||||
|
||||
&.n-tabs-tab--active {
|
||||
color: var(--text-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-tabs-tab-wrapper) {
|
||||
@apply pb-0;
|
||||
}
|
||||
|
||||
:deep(.n-tabs-pane-wrapper) {
|
||||
@apply px-2;
|
||||
}
|
||||
|
||||
:deep(.n-tabs-bar) {
|
||||
background-color: var(--text-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
@apply py-2;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
@apply grid grid-cols-1 gap-3;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
@apply flex items-center justify-between;
|
||||
span {
|
||||
@apply text-sm;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@apply text-sm font-medium mb-2;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
|
||||
.theme-section {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.slider-section {
|
||||
@apply space-y-6;
|
||||
}
|
||||
|
||||
.slider-item {
|
||||
@apply space-y-2 mb-10 !important;
|
||||
span {
|
||||
@apply text-sm;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-radio-group {
|
||||
@apply flex;
|
||||
}
|
||||
<style scoped>
|
||||
/* 设置项 */
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
|
||||
:deep(.n-slider-mark) {
|
||||
color: var(--text-color-primary) !important;
|
||||
.setting-item:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
:deep(.n-radio__label) {
|
||||
color: var(--text-color-active) !important;
|
||||
@apply text-xs;
|
||||
/* 切换开关 */
|
||||
.toggle-switch {
|
||||
appearance: none;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.mobile-unavailable {
|
||||
@apply text-center py-4 text-gray-500 text-sm;
|
||||
.toggle-switch::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.toggle-switch:checked {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.toggle-switch:checked::before {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
/* 滑块组 */
|
||||
.slider-group {
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.slider-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.slider-emerald {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.slider-emerald::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #10b981;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.slider-emerald::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #10b981;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.slider-marks {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* 单选框组 */
|
||||
.radio-group {
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
color: var(--text-color-primary);
|
||||
}
|
||||
|
||||
.radio-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 紧凑版单选项(用于横向布局) */
|
||||
.radio-item-compact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 13px;
|
||||
color: var(--text-color-primary);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.radio-item-compact:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.radio-input {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid var(--text-color-primary);
|
||||
opacity: 0.4;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.radio-input:checked {
|
||||
border-color: #10b981;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.radio-input:checked::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #10b981;
|
||||
border-radius: 50%;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
/* 颜色选择器 */
|
||||
.color-picker-group {
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.color-picker-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.color-picker::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.color-picker::-webkit-color-swatch {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 小尺寸颜色选择器(用于渐变) */
|
||||
.color-picker-small {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.color-picker-small::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.color-picker-small::-webkit-color-swatch {
|
||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* 下拉选择 */
|
||||
.select-group {
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.select-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.select-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
color: var(--text-color-primary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.select-input:focus {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
|
||||
/* 滚动条 */
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,19 +3,34 @@
|
||||
v-model:show="isVisible"
|
||||
height="100%"
|
||||
placement="bottom"
|
||||
:style="{ background: currentBackground || background }"
|
||||
:style="drawerBaseStyle"
|
||||
:to="`#layout-main`"
|
||||
:z-index="9998"
|
||||
>
|
||||
<div id="drawer-target" :class="[config.theme]">
|
||||
<!-- 背景层(用于图片模糊和明暗效果) -->
|
||||
<div
|
||||
v-if="
|
||||
config.useCustomBackground && config.backgroundMode === 'image' && config.backgroundImage
|
||||
"
|
||||
class="background-layer"
|
||||
:style="backgroundImageStyle"
|
||||
></div>
|
||||
<div id="drawer-target" :class="[config.theme]" class="relative z-10">
|
||||
<!-- 左侧关闭按钮 -->
|
||||
<div
|
||||
class="control-buttons-container absolute top-8 left-8 right-8"
|
||||
class="control-left absolute top-8 left-8 z-[9999]"
|
||||
:class="{ 'pure-mode': config.pureModeEnabled }"
|
||||
>
|
||||
<div class="control-btn" @click="closeMusicFull">
|
||||
<i class="ri-arrow-down-s-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧功能按钮组 -->
|
||||
<div
|
||||
class="control-right absolute top-8 right-8 z-[9999]"
|
||||
:class="{ 'pure-mode': config.pureModeEnabled }"
|
||||
>
|
||||
<n-popover trigger="click" placement="bottom">
|
||||
<template #trigger>
|
||||
<div class="control-btn">
|
||||
@@ -24,82 +39,40 @@
|
||||
</template>
|
||||
<lyric-settings ref="lyricSettingsRef" />
|
||||
</n-popover>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!config.hideCover"
|
||||
class="music-img"
|
||||
:class="{ 'only-cover': config.hideLyrics }"
|
||||
:style="{ color: textColors.theme === 'dark' ? '#000000' : '#ffffff' }"
|
||||
>
|
||||
<div class="img-container">
|
||||
<cover3-d
|
||||
ref="PicImgRef"
|
||||
:src="getImgUrl(playMusic?.picUrl, '500y500')"
|
||||
:loading="playMusic?.playLoading"
|
||||
:max-tilt="12"
|
||||
:scale="1.03"
|
||||
:shine-intensity="0.25"
|
||||
/>
|
||||
</div>
|
||||
<div class="music-info">
|
||||
<div class="music-content-name" v-html="playMusic.name"></div>
|
||||
<div class="music-content-singer">
|
||||
<n-ellipsis
|
||||
class="text-ellipsis"
|
||||
line-clamp="2"
|
||||
:tooltip="{
|
||||
contentStyle: { maxWidth: '600px' },
|
||||
zIndex: 99999
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in artistList"
|
||||
:key="index"
|
||||
class="cursor-pointer hover:text-green-500"
|
||||
@click="handleArtistClick(item.id)"
|
||||
>
|
||||
{{ item.name }}
|
||||
{{ index < artistList.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
<simple-play-bar
|
||||
v-if="!config.hideMiniPlayBar"
|
||||
class="mt-4"
|
||||
:pure-mode-enabled="config.pureModeEnabled"
|
||||
:isDark="textColors.theme === 'dark'"
|
||||
/>
|
||||
<div class="control-btn" @click="toggleFullScreen">
|
||||
<i :class="isFullScreen ? 'ri-fullscreen-exit-line' : 'ri-fullscreen-line'"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="music-content"
|
||||
:class="{
|
||||
center: config.centerLyrics,
|
||||
hide: config.hideLyrics
|
||||
}"
|
||||
>
|
||||
<n-layout
|
||||
ref="lrcSider"
|
||||
class="music-lrc"
|
||||
:style="{
|
||||
height: config.hidePlayBar ? '85vh' : '65vh',
|
||||
width: isMobile ? '100vw' : config.hideCover ? '50vw' : '500px'
|
||||
}"
|
||||
:native-scrollbar="false"
|
||||
@mouseover="mouseOverLayout"
|
||||
@mouseleave="mouseLeaveLayout"
|
||||
<div class="content-wrapper" :style="{ width: `${config.contentWidth}%` }">
|
||||
<!-- 左侧:封面区域 -->
|
||||
<div
|
||||
v-if="!config.hideCover"
|
||||
class="left-side"
|
||||
:class="{ 'only-cover': config.hideLyrics }"
|
||||
>
|
||||
<!-- 歌曲信息 -->
|
||||
<div ref="lrcContainer" class="music-lrc-container">
|
||||
<div
|
||||
v-if="config.hideCover"
|
||||
class="music-info-header"
|
||||
:style="{ textAlign: config.centerLyrics ? 'center' : 'left' }"
|
||||
>
|
||||
<div class="music-info-name" v-html="playMusic.name"></div>
|
||||
<div class="music-info-singer">
|
||||
<div class="img-container">
|
||||
<cover3-d
|
||||
ref="PicImgRef"
|
||||
:src="getImgUrl(playMusic?.picUrl, '500y500')"
|
||||
:loading="playMusic?.playLoading"
|
||||
:max-tilt="12"
|
||||
:scale="1.03"
|
||||
:shine-intensity="0.25"
|
||||
/>
|
||||
</div>
|
||||
<div class="music-info">
|
||||
<div class="music-content-name" v-html="playMusic.name"></div>
|
||||
<div class="music-content-singer">
|
||||
<n-ellipsis
|
||||
class="text-ellipsis"
|
||||
line-clamp="2"
|
||||
:tooltip="{
|
||||
contentStyle: { maxWidth: '600px' },
|
||||
zIndex: 99999
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in artistList"
|
||||
:key="index"
|
||||
@@ -109,53 +82,99 @@
|
||||
{{ item.name }}
|
||||
{{ index < artistList.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 无时间戳歌词提示 -->
|
||||
<div v-if="!supportAutoScroll" class="music-lrc-text no-scroll-tip">
|
||||
<span>{{ t('player.lrc.noAutoScroll') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in lrcArray"
|
||||
:id="`music-lrc-text-${index}`"
|
||||
:key="index"
|
||||
class="music-lrc-text"
|
||||
:class="{
|
||||
'now-text': index === nowIndex,
|
||||
'hover-text': item.text && item.startTime !== -1
|
||||
}"
|
||||
@click="item.startTime !== -1 ? setAudioTime(index) : null"
|
||||
>
|
||||
<!-- 逐字歌词显示 -->
|
||||
<div
|
||||
v-if="item.hasWordByWord && item.words && item.words.length > 0"
|
||||
class="word-by-word-lyric"
|
||||
>
|
||||
<template v-for="(word, wordIndex) in item.words" :key="wordIndex">
|
||||
<span class="lyric-word" :style="getWordStyle(index, wordIndex, word)">
|
||||
{{ word.text }} </span
|
||||
><span class="lyric-word" v-if="word.space"> </span></template
|
||||
>
|
||||
</div>
|
||||
<!-- 普通歌词显示 -->
|
||||
<span v-else :style="getLrcStyle(index)">{{ item.text }}</span>
|
||||
<div v-show="config.showTranslation" class="music-lrc-text-tr">
|
||||
{{ item.trText }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无歌词 -->
|
||||
<div v-if="!lrcArray.length" class="music-lrc-text">
|
||||
<span>{{ t('player.lrc.noLrc') }}</span>
|
||||
</n-ellipsis>
|
||||
</div>
|
||||
<simple-play-bar
|
||||
v-if="!config.hideMiniPlayBar"
|
||||
class="mt-4"
|
||||
:pure-mode-enabled="config.pureModeEnabled"
|
||||
:isDark="textColors.theme === 'dark'"
|
||||
/>
|
||||
</div>
|
||||
<!-- 歌词右下角矫正按钮组件 -->
|
||||
<lyric-correction-control
|
||||
v-if="!isMobile"
|
||||
:correction-time="correctionTime"
|
||||
@adjust="adjustCorrectionTime"
|
||||
/>
|
||||
</n-layout>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:歌词区域 -->
|
||||
<div
|
||||
class="right-side"
|
||||
:class="{
|
||||
center: config.centerLyrics,
|
||||
hide: config.hideLyrics,
|
||||
'full-width': config.hideCover
|
||||
}"
|
||||
>
|
||||
<n-layout
|
||||
ref="lrcSider"
|
||||
class="music-lrc"
|
||||
:native-scrollbar="false"
|
||||
@mouseover="mouseOverLayout"
|
||||
@mouseleave="mouseLeaveLayout"
|
||||
>
|
||||
<!-- 歌曲信息 -->
|
||||
<div ref="lrcContainer" class="music-lrc-container">
|
||||
<div
|
||||
v-if="config.hideCover"
|
||||
class="music-info-header"
|
||||
:style="{ textAlign: config.centerLyrics ? 'center' : 'left' }"
|
||||
>
|
||||
<div class="music-info-name" v-html="playMusic.name"></div>
|
||||
<div class="music-info-singer">
|
||||
<span
|
||||
v-for="(item, index) in artistList"
|
||||
:key="index"
|
||||
class="cursor-pointer hover:text-green-500"
|
||||
@click="handleArtistClick(item.id)"
|
||||
>
|
||||
{{ item.name }}
|
||||
{{ index < artistList.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 无时间戳歌词提示 -->
|
||||
<div v-if="!supportAutoScroll" class="music-lrc-text no-scroll-tip">
|
||||
<span>{{ t('player.lrc.noAutoScroll') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in lrcArray"
|
||||
:id="`music-lrc-text-${index}`"
|
||||
:key="index"
|
||||
class="music-lrc-text"
|
||||
:class="{
|
||||
'now-text': index === nowIndex,
|
||||
'hover-text': item.text && item.startTime !== -1
|
||||
}"
|
||||
@click="item.startTime !== -1 ? setAudioTime(index) : null"
|
||||
>
|
||||
<!-- 逐字歌词显示 -->
|
||||
<div
|
||||
v-if="item.hasWordByWord && item.words && item.words.length > 0"
|
||||
class="word-by-word-lyric"
|
||||
>
|
||||
<template v-for="(word, wordIndex) in item.words" :key="wordIndex">
|
||||
<span class="lyric-word" :style="getWordStyle(index, wordIndex, word)">
|
||||
{{ word.text }} </span
|
||||
><span class="lyric-word" v-if="word.space"> </span></template
|
||||
>
|
||||
</div>
|
||||
<!-- 普通歌词显示 -->
|
||||
<span v-else :style="getLrcStyle(index)">{{ item.text }}</span>
|
||||
<div v-show="config.showTranslation" class="music-lrc-text-tr">
|
||||
{{ item.trText }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无歌词 -->
|
||||
<div v-if="!lrcArray.length" class="music-lrc-text">
|
||||
<span>{{ t('player.lrc.noLrc') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 歌词右下角矫正按钮组件 -->
|
||||
<lyric-correction-control
|
||||
v-if="!isMobile"
|
||||
:correction-time="correctionTime"
|
||||
@adjust="adjustCorrectionTime"
|
||||
/>
|
||||
</n-layout>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-drawer>
|
||||
@@ -197,9 +216,57 @@ const lrcContainer = ref<HTMLElement | null>(null);
|
||||
const currentBackground = ref('');
|
||||
const animationFrame = ref<number | null>(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<InstanceType<typeof LyricSettings>>();
|
||||
const isSongChanging = ref(false);
|
||||
const isFullScreen = ref(false);
|
||||
|
||||
const config = ref<LyricConfig>({ ...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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user