mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-28 19:11:36 +08:00
实现透明主题的透明度和模糊度设置功能
This commit is contained in:
@@ -46,6 +46,33 @@ const showLanguageMenu = ref(false)
|
||||
// 自定义CSS
|
||||
const customCSS = ref('')
|
||||
|
||||
// 透明度相关
|
||||
const transparencyOpacity = ref(parseFloat(localStorage.getItem('transparency-opacity') || '0.3'))
|
||||
const transparencyBlur = ref(parseFloat(localStorage.getItem('transparency-blur') || '10'))
|
||||
const transparencyLevel = ref(localStorage.getItem('transparency-level') || 'medium')
|
||||
const isTransparentTheme = computed(() => currentThemeName.value === 'transparent')
|
||||
const showTransparencyDialog = ref(false)
|
||||
|
||||
// 预设值配置
|
||||
const transparencyPresets = {
|
||||
low: { opacity: 0.1, blur: 5 },
|
||||
medium: { opacity: 0.3, blur: 10 },
|
||||
high: { opacity: 0.6, blur: 15 },
|
||||
}
|
||||
|
||||
// 判断当前值是否匹配预设值
|
||||
const currentPresetLevel = computed(() => {
|
||||
for (const [level, preset] of Object.entries(transparencyPresets)) {
|
||||
if (
|
||||
Math.abs(transparencyOpacity.value - preset.opacity) < 0.01 &&
|
||||
Math.abs(transparencyBlur.value - preset.blur) < 0.1
|
||||
) {
|
||||
return level
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
// 重启轮询控制标识
|
||||
const restartPollingId = ref<number | null>(null)
|
||||
const isRestarting = ref(false)
|
||||
@@ -251,6 +278,11 @@ async function changeTheme(theme: string) {
|
||||
// 立即更新主题(不再刷新页面)
|
||||
await updateTheme()
|
||||
|
||||
// 如果是透明主题,应用透明度设置
|
||||
if (theme === 'transparent') {
|
||||
applyTransparencySettings()
|
||||
}
|
||||
|
||||
// 保存主题到服务端
|
||||
try {
|
||||
api.post('/user/config/Layout', {
|
||||
@@ -294,11 +326,78 @@ async function saveCustomCSS() {
|
||||
}
|
||||
}
|
||||
|
||||
// 应用透明度设置
|
||||
function applyTransparencySettings() {
|
||||
const root = document.documentElement
|
||||
|
||||
// 设置CSS变量
|
||||
root.style.setProperty('--transparent-opacity', transparencyOpacity.value.toString())
|
||||
root.style.setProperty('--transparent-opacity-light', (transparencyOpacity.value * 0.67).toString())
|
||||
root.style.setProperty('--transparent-opacity-heavy', (transparencyOpacity.value * 1.67).toString())
|
||||
root.style.setProperty('--transparent-blur', `${transparencyBlur.value}px`)
|
||||
root.style.setProperty('--transparent-blur-heavy', `${transparencyBlur.value * 1.6}px`)
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('transparency-opacity', transparencyOpacity.value.toString())
|
||||
localStorage.setItem('transparency-blur', transparencyBlur.value.toString())
|
||||
}
|
||||
|
||||
// 调整透明度预设
|
||||
function adjustTransparency(level: string) {
|
||||
transparencyLevel.value = level
|
||||
localStorage.setItem('transparency-level', level)
|
||||
|
||||
// 设置预设值
|
||||
switch (level) {
|
||||
case 'low':
|
||||
transparencyOpacity.value = 0.1
|
||||
transparencyBlur.value = 5
|
||||
break
|
||||
case 'medium':
|
||||
transparencyOpacity.value = 0.3
|
||||
transparencyBlur.value = 10
|
||||
break
|
||||
case 'high':
|
||||
transparencyOpacity.value = 0.6
|
||||
transparencyBlur.value = 15
|
||||
break
|
||||
}
|
||||
|
||||
applyTransparencySettings()
|
||||
}
|
||||
|
||||
// 透明度变化处理
|
||||
function onOpacityChange() {
|
||||
applyTransparencySettings()
|
||||
// 清除预设级别,因为用户手动调整了
|
||||
transparencyLevel.value = ''
|
||||
}
|
||||
|
||||
// 模糊度变化处理
|
||||
function onBlurChange() {
|
||||
applyTransparencySettings()
|
||||
// 清除预设级别,因为用户手动调整了
|
||||
transparencyLevel.value = ''
|
||||
}
|
||||
|
||||
// 重置透明度设置
|
||||
function resetTransparencySettings() {
|
||||
transparencyOpacity.value = 0.3
|
||||
transparencyBlur.value = 10
|
||||
transparencyLevel.value = 'medium'
|
||||
applyTransparencySettings()
|
||||
}
|
||||
|
||||
// 监听主题变化
|
||||
watch(
|
||||
() => currentThemeName.value,
|
||||
async () => {
|
||||
await updateTheme()
|
||||
|
||||
// 如果切换到透明主题,应用透明度设置
|
||||
if (currentThemeName.value === 'transparent') {
|
||||
applyTransparencySettings()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -351,6 +450,11 @@ const getThemeIcon = computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
getCustomCSS()
|
||||
|
||||
// 初始化透明度设置
|
||||
if (isTransparentTheme.value) {
|
||||
applyTransparencySettings()
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时清理轮询
|
||||
@@ -456,6 +560,20 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<VListItemTitle>{{ t('theme.custom') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 透明度调整 - 仅在透明主题下显示 -->
|
||||
<template v-if="isTransparentTheme">
|
||||
<VDivider class="my-2" />
|
||||
<VListItem @click="showTransparencyDialog = true">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-opacity" />
|
||||
</template>
|
||||
<VListItemTitle>{{ t('theme.transparencyAdjust') }}</VListItemTitle>
|
||||
<template #append>
|
||||
<VIcon icon="mdi-chevron-right" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
</VList>
|
||||
</VMenu>
|
||||
|
||||
@@ -553,6 +671,98 @@ onUnmounted(() => {
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</DialogWrapper>
|
||||
|
||||
<!-- 透明度调整对话框 -->
|
||||
<DialogWrapper v-if="showTransparencyDialog" v-model="showTransparencyDialog" max-width="30rem">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-opacity" class="me-2" />
|
||||
{{ t('theme.transparencyAdjust') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="showTransparencyDialog = false" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<div class="space-y-6">
|
||||
<!-- 透明度滑动条 -->
|
||||
<div>
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<span class="text-body-2">{{ t('theme.transparencyOpacity') }}</span>
|
||||
<span class="text-caption">{{ Math.round(transparencyOpacity * 100) }}%</span>
|
||||
</div>
|
||||
<VSlider
|
||||
v-model="transparencyOpacity"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
color="primary"
|
||||
@update:model-value="onOpacityChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 模糊度滑动条 -->
|
||||
<div>
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<span class="text-body-2">{{ t('theme.transparencyBlur') }}</span>
|
||||
<span class="text-caption">{{ transparencyBlur }}px</span>
|
||||
</div>
|
||||
<VSlider
|
||||
v-model="transparencyBlur"
|
||||
:min="0"
|
||||
:max="30"
|
||||
:step="1"
|
||||
color="primary"
|
||||
@update:model-value="onBlurChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 预设按钮 -->
|
||||
<div>
|
||||
<span class="text-body-2 d-block mb-2">{{ t('common.preset') }}</span>
|
||||
<VBtnGroup density="compact" variant="outlined" class="w-full">
|
||||
<VBtn
|
||||
size="small"
|
||||
:color="currentPresetLevel === 'low' ? 'primary' : undefined"
|
||||
@click="adjustTransparency('low')"
|
||||
class="flex-1"
|
||||
>
|
||||
{{ t('theme.transparencyLow') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
size="small"
|
||||
:color="currentPresetLevel === 'medium' ? 'primary' : undefined"
|
||||
@click="adjustTransparency('medium')"
|
||||
class="flex-1"
|
||||
>
|
||||
{{ t('theme.transparencyMedium') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
size="small"
|
||||
:color="currentPresetLevel === 'high' ? 'primary' : undefined"
|
||||
@click="adjustTransparency('high')"
|
||||
class="flex-1"
|
||||
>
|
||||
{{ t('theme.transparencyHigh') }}
|
||||
</VBtn>
|
||||
</VBtnGroup>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
<VCardText class="text-center">
|
||||
<VBtn @click="resetTransparencySettings" variant="outlined" class="me-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-refresh" />
|
||||
</template>
|
||||
{{ t('theme.transparencyReset') }}
|
||||
</VBtn>
|
||||
<VBtn @click="showTransparencyDialog = false" color="primary">
|
||||
{{ t('common.confirm') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</DialogWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
serviceAvailable: 'Service Available',
|
||||
serviceUnavailable: 'Service Unavailable',
|
||||
status: 'Status',
|
||||
preset: 'Preset',
|
||||
},
|
||||
mediaType: {
|
||||
movie: 'Movie',
|
||||
@@ -127,7 +128,15 @@ export default {
|
||||
auto: 'Follow System',
|
||||
transparent: 'Transparent',
|
||||
purple: 'Purple',
|
||||
custom: 'Custom Theme',
|
||||
custom: 'Custom Style',
|
||||
transparency: 'Transparency',
|
||||
transparencyAdjust: 'Transparency Adjustment',
|
||||
transparencyOpacity: 'Opacity',
|
||||
transparencyBlur: 'Blur',
|
||||
transparencyReset: 'Reset',
|
||||
transparencyLow: 'Low Transparency',
|
||||
transparencyMedium: 'Medium Transparency',
|
||||
transparencyHigh: 'High Transparency',
|
||||
customCssSaveSuccess: 'Custom CSS saved successfully, please refresh the page to take effect!',
|
||||
customCssSaveFailed: 'Failed to save custom CSS to server',
|
||||
deviceNotSupport: 'Current device does not support monitoring system theme changes',
|
||||
@@ -1587,7 +1596,8 @@ export default {
|
||||
timedSearch: 'Subscription Scheduled Search',
|
||||
timedSearchHint: 'Search all sites every 24 hours to supplement resources that may be missed by subscription',
|
||||
checkLocalMedia: 'Check File System Resources',
|
||||
checkLocalMediaHint: 'Scan the storage directory for existing resource files to avoid duplicate downloads; regardless of whether it is enabled, the media server will be checked',
|
||||
checkLocalMediaHint:
|
||||
'Scan the storage directory for existing resource files to avoid duplicate downloads; regardless of whether it is enabled, the media server will be checked',
|
||||
modes: {
|
||||
auto: 'Auto',
|
||||
rss: 'Site RSS',
|
||||
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
serviceAvailable: '服务可用',
|
||||
serviceUnavailable: '服务不可用',
|
||||
status: '状态',
|
||||
preset: '预设',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '电影',
|
||||
@@ -127,7 +128,15 @@ export default {
|
||||
auto: '跟随系统',
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '自定义主题',
|
||||
custom: '附加样式',
|
||||
transparency: '透明度',
|
||||
transparencyAdjust: '透明度调整',
|
||||
transparencyOpacity: '透明度',
|
||||
transparencyBlur: '模糊度',
|
||||
transparencyReset: '重置',
|
||||
transparencyLow: '低透明度',
|
||||
transparencyMedium: '中等透明度',
|
||||
transparencyHigh: '高透明度',
|
||||
customCssSaveSuccess: '自定义CSS保存成功,请刷新页面生效!',
|
||||
customCssSaveFailed: '保存自定义CSS到服务端失败',
|
||||
deviceNotSupport: '当前设备不支持监听系统主题变化',
|
||||
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
serviceAvailable: '服務可用',
|
||||
serviceUnavailable: '服務不可用',
|
||||
status: '狀態',
|
||||
preset: '預設',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '電影',
|
||||
@@ -127,7 +128,15 @@ export default {
|
||||
auto: '跟隨系統',
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '自定義主題',
|
||||
custom: '附加樣式',
|
||||
transparency: '透明度',
|
||||
transparencyAdjust: '透明度調整',
|
||||
transparencyOpacity: '透明度',
|
||||
transparencyBlur: '模糊度',
|
||||
transparencyReset: '重置',
|
||||
transparencyLow: '低透明度',
|
||||
transparencyMedium: '中等透明度',
|
||||
transparencyHigh: '高透明度',
|
||||
customCssSaveSuccess: '自定義CSS保存成功,請刷新頁面生效!',
|
||||
customCssSaveFailed: '保存自定義CSS到服務端失敗',
|
||||
deviceNotSupport: '當前設備不支持監聽系統主題變化',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
// 透明主题专用样式
|
||||
html[data-theme="transparent"] {
|
||||
// 定义透明度变量
|
||||
--transparent-opacity: 0.3;
|
||||
--transparent-opacity-light: 0.2;
|
||||
--transparent-opacity-heavy: 0.5;
|
||||
--transparent-blur: 10px;
|
||||
--transparent-blur-heavy: 16px;
|
||||
|
||||
// 应用、布局、主内容区域
|
||||
.v-application, .v-layout, .v-main, .layout-page-content {
|
||||
background: transparent;
|
||||
@@ -7,21 +14,21 @@ html[data-theme="transparent"] {
|
||||
|
||||
// 侧边导航栏
|
||||
.layout-vertical-nav {
|
||||
backdrop-filter: blur(16px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.2);
|
||||
backdrop-filter: blur(var(--transparent-blur-heavy));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-light));
|
||||
border-inline-end: 1px solid rgba(var(--v-theme-on-surface), 0.05);
|
||||
}
|
||||
|
||||
// 列表
|
||||
.v-list {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// 卡片
|
||||
.v-card:not(.no-blur) {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
|
||||
.v-list {
|
||||
backdrop-filter: none;
|
||||
@@ -31,8 +38,8 @@ html[data-theme="transparent"] {
|
||||
|
||||
// 工具栏
|
||||
.v-toolbar {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// 表格
|
||||
@@ -41,20 +48,20 @@ html[data-theme="transparent"] {
|
||||
background-color: rgba(var(--v-theme-surface), 0);
|
||||
|
||||
.v-table__wrapper > table > thead {
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
// 页脚
|
||||
.v-footer {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// Sheet
|
||||
.v-sheet {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// 页面容器
|
||||
@@ -74,13 +81,13 @@ html[data-theme="transparent"] {
|
||||
|
||||
// 折叠面板
|
||||
.v-expansion-panel {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// 加载占位
|
||||
.v-skeleton-loader {
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
|
||||
// 输入框和搜索框
|
||||
@@ -91,20 +98,20 @@ html[data-theme="transparent"] {
|
||||
// 弹出层内容
|
||||
.v-overlay__content {
|
||||
border-radius: 12px !important;
|
||||
backdrop-filter: blur(10px) !important;
|
||||
|
||||
.v-list {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgb(var(--v-theme-surface), 0.5) !important;
|
||||
}
|
||||
backdrop-filter: blur(var(--transparent-blur)) !important;
|
||||
|
||||
.v-card:not(.bg-primary) {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgb(var(--v-theme-surface), 0.5) !important;
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy)) !important;
|
||||
}
|
||||
|
||||
.v-list {
|
||||
backdrop-filter: blur(var(--transparent-blur));
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy)) !important;
|
||||
}
|
||||
|
||||
.v-table__wrapper table thead {
|
||||
background-color: rgba(var(--v-theme-surface), 0.3);
|
||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user