mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 10:27:30 +08:00
feat:优化全屏歌词界面 添加背景和宽度设置
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user