实现透明主题的透明度和模糊度设置功能

This commit is contained in:
jxxghp
2025-07-29 08:20:16 +08:00
parent 62f9243714
commit 1c4a2176e9
5 changed files with 274 additions and 29 deletions

View File

@@ -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>

View File

@@ -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',

View File

@@ -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: '当前设备不支持监听系统主题变化',

View File

@@ -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: '當前設備不支持監聽系統主題變化',

View File

@@ -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));
}
}
}