You've already forked iFlow-Settings-Editor-GUI
新增 亚克力透明度调节功能:支持在浅色主题下调整背景模糊效果强度,范围0-100%
This commit is contained in:
58
src/App.vue
58
src/App.vue
@@ -74,6 +74,7 @@ const settings = ref({
|
|||||||
cna: '',
|
cna: '',
|
||||||
currentApiProfile: 'default',
|
currentApiProfile: 'default',
|
||||||
apiProfiles: { default: {} },
|
apiProfiles: { default: {} },
|
||||||
|
acrylicIntensity: 50,
|
||||||
})
|
})
|
||||||
|
|
||||||
const originalSettings = ref({})
|
const originalSettings = ref({})
|
||||||
@@ -272,6 +273,7 @@ const loadSettings = async () => {
|
|||||||
if (data.cna === undefined) data.cna = ''
|
if (data.cna === undefined) data.cna = ''
|
||||||
if (!data.apiProfiles) data.apiProfiles = { default: {} }
|
if (!data.apiProfiles) data.apiProfiles = { default: {} }
|
||||||
if (!data.currentApiProfile) data.currentApiProfile = 'default'
|
if (!data.currentApiProfile) data.currentApiProfile = 'default'
|
||||||
|
if (data.acrylicIntensity === undefined) data.acrylicIntensity = 50
|
||||||
settings.value = data
|
settings.value = data
|
||||||
originalSettings.value = JSON.parse(JSON.stringify(data))
|
originalSettings.value = JSON.parse(JSON.stringify(data))
|
||||||
modified.value = false
|
modified.value = false
|
||||||
@@ -312,6 +314,31 @@ const themeClass = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const acrylicStyle = computed(() => {
|
||||||
|
const intensity = settings.value.acrylicIntensity
|
||||||
|
if (intensity === undefined || intensity === null) return {}
|
||||||
|
const opacity = 1 - intensity / 100
|
||||||
|
const isDark = settings.value.theme === 'Dark'
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
return {
|
||||||
|
'--bg-primary': `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`,
|
||||||
|
'--bg-secondary': `rgba(45, 45, 45, ${Math.max(0.05, opacity * 0.7)})`,
|
||||||
|
'--bg-elevated': `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.95)})`,
|
||||||
|
'--bg-mica': `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`,
|
||||||
|
'--control-fill': `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.85)})`,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'--bg-primary': `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.85)})`,
|
||||||
|
'--bg-secondary': `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.7)})`,
|
||||||
|
'--bg-elevated': `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.95)})`,
|
||||||
|
'--bg-mica': `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.473)})`,
|
||||||
|
'--control-fill': `rgba(249, 249, 249, ${Math.max(0.05, opacity * 0.85)})`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const selectServer = name => {
|
const selectServer = name => {
|
||||||
currentServerName.value = name
|
currentServerName.value = name
|
||||||
openEditServerPanel(name)
|
openEditServerPanel(name)
|
||||||
@@ -454,9 +481,39 @@ watch(
|
|||||||
} else {
|
} else {
|
||||||
document.body.classList.remove('dark', 'solarized-dark')
|
document.body.classList.remove('dark', 'solarized-dark')
|
||||||
}
|
}
|
||||||
|
applyAcrylicStyle()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => settings.value.acrylicIntensity,
|
||||||
|
() => {
|
||||||
|
applyAcrylicStyle()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const applyAcrylicStyle = () => {
|
||||||
|
const intensity = settings.value.acrylicIntensity
|
||||||
|
if (intensity === undefined || intensity === null) return
|
||||||
|
const opacity = 1 - intensity / 100
|
||||||
|
const isDark = settings.value.theme === 'Dark'
|
||||||
|
const root = document.documentElement
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
root.style.setProperty('--bg-primary', `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`)
|
||||||
|
root.style.setProperty('--bg-secondary', `rgba(45, 45, 45, ${Math.max(0.05, opacity * 0.7)})`)
|
||||||
|
root.style.setProperty('--bg-elevated', `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.95)})`)
|
||||||
|
root.style.setProperty('--bg-mica', `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`)
|
||||||
|
root.style.setProperty('--control-fill', `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.85)})`)
|
||||||
|
} else {
|
||||||
|
root.style.setProperty('--bg-primary', `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.85)})`)
|
||||||
|
root.style.setProperty('--bg-secondary', `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.7)})`)
|
||||||
|
root.style.setProperty('--bg-elevated', `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.95)})`)
|
||||||
|
root.style.setProperty('--bg-mica', `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.473)})`)
|
||||||
|
root.style.setProperty('--control-fill', `rgba(249, 249, 249, ${Math.max(0.05, opacity * 0.85)})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadApiProfiles()
|
await loadApiProfiles()
|
||||||
await loadSettings()
|
await loadSettings()
|
||||||
@@ -465,6 +522,7 @@ onMounted(async () => {
|
|||||||
if (cls) {
|
if (cls) {
|
||||||
document.body.classList.add(cls)
|
document.body.classList.add(cls)
|
||||||
}
|
}
|
||||||
|
applyAcrylicStyle()
|
||||||
window.electronAPI.onApiProfileSwitched(async profileName => {
|
window.electronAPI.onApiProfileSwitched(async profileName => {
|
||||||
currentApiProfile.value = profileName
|
currentApiProfile.value = profileName
|
||||||
await loadSettings()
|
await loadSettings()
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ export default {
|
|||||||
bootAnimationNotShown: 'Not Shown',
|
bootAnimationNotShown: 'Not Shown',
|
||||||
checkpointing: 'Checkpointing',
|
checkpointing: 'Checkpointing',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
disabled: 'Disabled'
|
disabled: 'Disabled',
|
||||||
|
acrylicEffect: 'Acrylic Effect',
|
||||||
|
acrylicMin: 'Opaque',
|
||||||
|
acrylicMax: 'Transparent'
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
xcode: 'Xcode',
|
xcode: 'Xcode',
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ export default {
|
|||||||
bootAnimationNotShown: '未显示',
|
bootAnimationNotShown: '未显示',
|
||||||
checkpointing: '检查点保存',
|
checkpointing: '检查点保存',
|
||||||
enabled: '已启用',
|
enabled: '已启用',
|
||||||
disabled: '已禁用'
|
disabled: '已禁用',
|
||||||
|
acrylicEffect: '亚克力效果',
|
||||||
|
acrylicMin: '不透明',
|
||||||
|
acrylicMax: '透明'
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
xcode: 'Xcode',
|
xcode: 'Xcode',
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ export default {
|
|||||||
bootAnimationNotShown: '未表示',
|
bootAnimationNotShown: '未表示',
|
||||||
checkpointing: 'チェックポイント保存',
|
checkpointing: 'チェックポイント保存',
|
||||||
enabled: '有効',
|
enabled: '有効',
|
||||||
disabled: '無効'
|
disabled: '無効',
|
||||||
|
acrylicEffect: 'アクリリック効果',
|
||||||
|
acrylicMin: '不透明',
|
||||||
|
acrylicMax: '透明'
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
xcode: 'Xcode',
|
xcode: 'Xcode',
|
||||||
|
|||||||
@@ -136,49 +136,6 @@
|
|||||||
--shadow-xl: 0 16px 32px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.3);
|
--shadow-xl: 0 16px 32px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solarized Dark Mode
|
|
||||||
.solarized-dark {
|
|
||||||
--bg-primary: #002b36;
|
|
||||||
--bg-secondary: #073642;
|
|
||||||
--bg-tertiary: #094856;
|
|
||||||
--bg-elevated: #0a4a5c;
|
|
||||||
--bg-mica: rgba(0, 43, 54, 0.9);
|
|
||||||
|
|
||||||
--text-primary: #839496;
|
|
||||||
--text-secondary: #93a1a1;
|
|
||||||
--text-tertiary: #586e75;
|
|
||||||
--text-disabled: #3d5a64;
|
|
||||||
|
|
||||||
--accent: #268bd2;
|
|
||||||
--accent-hover: #2d9cdb;
|
|
||||||
--accent-pressed: #1a73c0;
|
|
||||||
--accent-light: rgba(38, 139, 210, 0.15);
|
|
||||||
--accent-text: #268bd2;
|
|
||||||
|
|
||||||
--border: #1d3a47;
|
|
||||||
--border-light: #0d3a47;
|
|
||||||
--border-strong: #2d5a6f;
|
|
||||||
|
|
||||||
--success: #2aa198;
|
|
||||||
--success-bg: rgba(42, 161, 152, 0.15);
|
|
||||||
--danger: #dc322f;
|
|
||||||
--danger-bg: rgba(220, 50, 47, 0.15);
|
|
||||||
--warning: #b58900;
|
|
||||||
--warning-bg: rgba(181, 137, 0, 0.15);
|
|
||||||
--info: #268bd2;
|
|
||||||
--info-bg: rgba(38, 139, 210, 0.15);
|
|
||||||
|
|
||||||
--control-fill: #073642;
|
|
||||||
--control-fill-hover: #0a4a5c;
|
|
||||||
--control-fill-pressed: #0d5a70;
|
|
||||||
--control-fill-disabled: #053845;
|
|
||||||
|
|
||||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
||||||
--shadow: 0 4px 8px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
||||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5), 0 4px 8px rgba(0, 0, 0, 0.35);
|
|
||||||
--shadow-xl: 0 16px 32px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Animations
|
// Animations
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -21,10 +21,8 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">{{ $t('general.theme') }}</label>
|
<label class="form-label">{{ $t('general.theme') }}</label>
|
||||||
<select class="form-select" v-model="localSettings.theme">
|
<select class="form-select" v-model="localSettings.theme">
|
||||||
<option value="Xcode">{{ $t('theme.xcode') }}</option>
|
|
||||||
<option value="Dark">{{ $t('theme.dark') }}</option>
|
|
||||||
<option value="Light">{{ $t('theme.light') }}</option>
|
<option value="Light">{{ $t('theme.light') }}</option>
|
||||||
<option value="Solarized Dark">{{ $t('theme.solarizedDark') }}</option>
|
<option value="Dark">{{ $t('theme.dark') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +48,23 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row" v-if="supportsAcrylic">
|
||||||
|
<div class="form-group form-group-full">
|
||||||
|
<label class="form-label">{{ $t('general.acrylicEffect') }}: {{ localSettings.acrylicIntensity }}%</label>
|
||||||
|
<div class="slider-container">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
class="form-slider"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
:value="localSettings.acrylicIntensity"
|
||||||
|
@input="updateSliderStyle"
|
||||||
|
ref="sliderRef"
|
||||||
|
/>
|
||||||
|
<span class="slider-hint">{{ $t('general.acrylicMin') }} — {{ $t('general.acrylicMax') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,12 +81,105 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:settings'])
|
const emit = defineEmits(['update:settings'])
|
||||||
|
|
||||||
import { computed } from 'vue'
|
import { computed, ref, onMounted, watch, nextTick } from 'vue'
|
||||||
|
|
||||||
const localSettings = computed({
|
const localSettings = computed({
|
||||||
get: () => props.settings,
|
get: () => props.settings,
|
||||||
set: val => emit('update:settings', val),
|
set: val => emit('update:settings', val),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const supportsAcrylic = computed(() => {
|
||||||
|
return typeof document !== 'undefined' && 'backdropFilter' in document.documentElement.style && props.settings.theme !== 'Dark'
|
||||||
|
})
|
||||||
|
|
||||||
|
const sliderRef = ref(null)
|
||||||
|
|
||||||
|
const updateSliderStyle = e => {
|
||||||
|
const value = e.target.value
|
||||||
|
const percent = ((value - 0) / (100 - 0)) * 100
|
||||||
|
e.target.style.backgroundSize = `${percent}% 100%`
|
||||||
|
emit('update:settings', { ...props.settings, acrylicIntensity: Number(value) })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (sliderRef.value) {
|
||||||
|
const percent = ((props.settings.acrylicIntensity - 0) / (100 - 0)) * 100
|
||||||
|
sliderRef.value.style.backgroundSize = `${percent}% 100%`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.settings.theme,
|
||||||
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (sliderRef.value && supportsAcrylic.value) {
|
||||||
|
const percent = ((props.settings.acrylicIntensity - 0) / (100 - 0)) * 100
|
||||||
|
sliderRef.value.style.backgroundSize = `${percent}% 100%`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
.form-group-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-slider {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-image: linear-gradient(var(--accent), var(--accent));
|
||||||
|
background-size: var(--slider-progress, 50%) 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: transform 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-slider::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-slider::-webkit-slider-thumb:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-slider::-moz-range-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-hint {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user