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: '',
|
||||
currentApiProfile: 'default',
|
||||
apiProfiles: { default: {} },
|
||||
acrylicIntensity: 50,
|
||||
})
|
||||
|
||||
const originalSettings = ref({})
|
||||
@@ -272,6 +273,7 @@ const loadSettings = async () => {
|
||||
if (data.cna === undefined) data.cna = ''
|
||||
if (!data.apiProfiles) data.apiProfiles = { default: {} }
|
||||
if (!data.currentApiProfile) data.currentApiProfile = 'default'
|
||||
if (data.acrylicIntensity === undefined) data.acrylicIntensity = 50
|
||||
settings.value = data
|
||||
originalSettings.value = JSON.parse(JSON.stringify(data))
|
||||
modified.value = false
|
||||
@@ -312,6 +314,31 @@ const themeClass = computed(() => {
|
||||
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 => {
|
||||
currentServerName.value = name
|
||||
openEditServerPanel(name)
|
||||
@@ -454,9 +481,39 @@ watch(
|
||||
} else {
|
||||
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 () => {
|
||||
await loadApiProfiles()
|
||||
await loadSettings()
|
||||
@@ -465,6 +522,7 @@ onMounted(async () => {
|
||||
if (cls) {
|
||||
document.body.classList.add(cls)
|
||||
}
|
||||
applyAcrylicStyle()
|
||||
window.electronAPI.onApiProfileSwitched(async profileName => {
|
||||
currentApiProfile.value = profileName
|
||||
await loadSettings()
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: 'Not Shown',
|
||||
checkpointing: 'Checkpointing',
|
||||
enabled: 'Enabled',
|
||||
disabled: 'Disabled'
|
||||
disabled: 'Disabled',
|
||||
acrylicEffect: 'Acrylic Effect',
|
||||
acrylicMin: 'Opaque',
|
||||
acrylicMax: 'Transparent'
|
||||
},
|
||||
theme: {
|
||||
xcode: 'Xcode',
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: '未显示',
|
||||
checkpointing: '检查点保存',
|
||||
enabled: '已启用',
|
||||
disabled: '已禁用'
|
||||
disabled: '已禁用',
|
||||
acrylicEffect: '亚克力效果',
|
||||
acrylicMin: '不透明',
|
||||
acrylicMax: '透明'
|
||||
},
|
||||
theme: {
|
||||
xcode: 'Xcode',
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: '未表示',
|
||||
checkpointing: 'チェックポイント保存',
|
||||
enabled: '有効',
|
||||
disabled: '無効'
|
||||
disabled: '無効',
|
||||
acrylicEffect: 'アクリリック効果',
|
||||
acrylicMin: '不透明',
|
||||
acrylicMax: '透明'
|
||||
},
|
||||
theme: {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// =============================================================================
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('general.theme') }}</label>
|
||||
<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="Solarized Dark">{{ $t('theme.solarizedDark') }}</option>
|
||||
<option value="Dark">{{ $t('theme.dark') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,6 +48,23 @@
|
||||
</select>
|
||||
</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>
|
||||
</section>
|
||||
</template>
|
||||
@@ -66,12 +81,105 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:settings'])
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, onMounted, watch, nextTick } from 'vue'
|
||||
|
||||
const localSettings = computed({
|
||||
get: () => props.settings,
|
||||
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>
|
||||
|
||||
<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