You've already forked iFlow-Settings-Editor-GUI
新增 跟随系统主题切换功能,深色模式自动隐藏亚克力效果
This commit is contained in:
@@ -97,7 +97,7 @@ npm run test:run
|
||||
|
||||
### 主题系统
|
||||
|
||||
支持两种主题:`Light` (浅色) / `Dark` (深色)
|
||||
支持三种主题:`Light` (浅色) / `Dark` (深色) / `System` (跟随系统)
|
||||
|
||||
CSS 变量定义在 `src/styles/global.less`,包括:
|
||||
- `--bg-primary/secondary/elevated` - 背景层级
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- 📝 **API 配置管理** - 支持多环境配置文件切换、创建、编辑、复制和删除
|
||||
- 🖥️ **MCP 服务器管理** - 便捷的 Model Context Protocol 服务器配置界面
|
||||
- 🎨 **Windows 11 设计风格** - 采用 Fluent Design 设计规范
|
||||
- 🌈 **多主题支持** - Light / Dark 两种主题
|
||||
- 🌈 **多主题支持** - Light / Dark / System (跟随系统) 三种主题
|
||||
- 🌍 **国际化** - 支持简体中文、English、日語
|
||||
- 💧 **亚克力效果** - 可调节透明度的现代视觉效果
|
||||
- 📦 **系统托盘** - 最小化到托盘,快速切换 API 配置
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "iflow-settings-editor",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。",
|
||||
"main": "main.js",
|
||||
"author": "上海潘哆呐科技有限公司",
|
||||
|
||||
39
src/App.vue
39
src/App.vue
@@ -84,6 +84,7 @@ const currentServerName = ref(null)
|
||||
const isLoading = ref(true)
|
||||
const apiProfiles = ref([])
|
||||
const currentApiProfile = ref('default')
|
||||
const systemTheme = ref('Light')
|
||||
|
||||
const showInputDialog = ref({ show: false, title: '', placeholder: '', callback: null, isConfirm: false, defaultValue: '' })
|
||||
const showMessageDialog = ref({ show: false, type: 'info', title: '', message: '', callback: null })
|
||||
@@ -307,8 +308,14 @@ const showSection = section => {
|
||||
|
||||
const serverCount = computed(() => (settings.value.mcpServers ? Object.keys(settings.value.mcpServers).length : 0))
|
||||
|
||||
const themeClass = computed(() => {
|
||||
const getEffectiveTheme = () => {
|
||||
const theme = settings.value.uiTheme
|
||||
if (theme === 'System') return systemTheme.value
|
||||
return theme
|
||||
}
|
||||
|
||||
const themeClass = computed(() => {
|
||||
const theme = getEffectiveTheme()
|
||||
if (theme === 'Dark') return 'dark'
|
||||
return ''
|
||||
})
|
||||
@@ -471,14 +478,10 @@ const closeMessageDialog = () => {
|
||||
|
||||
watch(
|
||||
() => settings.value.uiTheme,
|
||||
theme => {
|
||||
() => {
|
||||
document.body.classList.remove('dark')
|
||||
const cls = themeClass.value
|
||||
if (cls) {
|
||||
document.body.classList.add(cls)
|
||||
if (cls === 'dark') document.body.classList.remove('solarized-dark')
|
||||
} else {
|
||||
document.body.classList.remove('dark', 'solarized-dark')
|
||||
}
|
||||
if (cls) document.body.classList.add(cls)
|
||||
applyAcrylicStyle()
|
||||
},
|
||||
)
|
||||
@@ -494,7 +497,7 @@ const applyAcrylicStyle = () => {
|
||||
const intensity = settings.value.acrylicIntensity
|
||||
if (intensity === undefined || intensity === null) return
|
||||
const opacity = 1 - intensity / 100
|
||||
const isDark = settings.value.uiTheme === 'Dark'
|
||||
const isDark = getEffectiveTheme() === 'Dark'
|
||||
const root = document.documentElement
|
||||
|
||||
if (isDark) {
|
||||
@@ -512,10 +515,28 @@ const applyAcrylicStyle = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateSystemTheme = () => {
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
systemTheme.value = isDark ? 'Dark' : 'Light'
|
||||
if (settings.value.uiTheme === 'System') {
|
||||
const cls = themeClass.value
|
||||
document.body.classList.remove('dark')
|
||||
if (cls) document.body.classList.add(cls)
|
||||
applyAcrylicStyle()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadApiProfiles()
|
||||
await loadSettings()
|
||||
locale.value = settings.value.language
|
||||
|
||||
// 初始化系统主题
|
||||
updateSystemTheme()
|
||||
|
||||
// 监听系统主题变化
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateSystemTheme)
|
||||
|
||||
const cls = themeClass.value
|
||||
if (cls) {
|
||||
document.body.classList.add(cls)
|
||||
|
||||
@@ -33,7 +33,8 @@ export default {
|
||||
},
|
||||
theme: {
|
||||
dark: 'Dark',
|
||||
light: 'Light'
|
||||
light: 'Light',
|
||||
system: 'System'
|
||||
},
|
||||
api: {
|
||||
title: 'API Configuration',
|
||||
|
||||
@@ -33,7 +33,8 @@ export default {
|
||||
},
|
||||
theme: {
|
||||
dark: '深色',
|
||||
light: '浅色'
|
||||
light: '浅色',
|
||||
system: '跟随系统'
|
||||
},
|
||||
api: {
|
||||
title: 'API 配置',
|
||||
|
||||
@@ -33,7 +33,8 @@ export default {
|
||||
},
|
||||
theme: {
|
||||
dark: 'ダーク',
|
||||
light: 'ライト'
|
||||
light: 'ライト',
|
||||
system: 'システム'
|
||||
},
|
||||
api: {
|
||||
title: 'API 設定',
|
||||
|
||||
@@ -61,9 +61,10 @@ describe('GeneralSettings.vue', () => {
|
||||
});
|
||||
|
||||
const themeOptions = wrapper.findAll('.form-select')[1].findAll('option');
|
||||
expect(themeOptions.length).toBe(2);
|
||||
expect(themeOptions.length).toBe(3);
|
||||
expect(themeOptions[0].attributes('value')).toBe('Light');
|
||||
expect(themeOptions[1].attributes('value')).toBe('Dark');
|
||||
expect(themeOptions[2].attributes('value')).toBe('System');
|
||||
});
|
||||
|
||||
it('reflects current settings in form controls', async () => {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<select class="form-select" v-model="localSettings.uiTheme">
|
||||
<option value="Light">{{ $t('theme.light') }}</option>
|
||||
<option value="Dark">{{ $t('theme.dark') }}</option>
|
||||
<option value="System">{{ $t('theme.system') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,12 +91,24 @@ const localSettings = computed({
|
||||
set: val => emit('update:settings', val),
|
||||
})
|
||||
|
||||
const systemTheme = ref('Light')
|
||||
|
||||
const supportsAcrylic = computed(() => {
|
||||
return typeof document !== 'undefined' && 'backdropFilter' in document.documentElement.style && props.settings.uiTheme !== 'Dark'
|
||||
if (typeof document === 'undefined' || !('backdropFilter' in document.documentElement.style)) return false
|
||||
const effectiveTheme = props.settings.uiTheme === 'System' ? systemTheme.value : props.settings.uiTheme
|
||||
return effectiveTheme !== 'Dark'
|
||||
})
|
||||
|
||||
const sliderWrapper = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
systemTheme.value = isDark ? 'Dark' : 'Light'
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
systemTheme.value = e.matches ? 'Dark' : 'Light'
|
||||
})
|
||||
})
|
||||
|
||||
const updateSliderValue = e => {
|
||||
const value = Number(e.target.value)
|
||||
emit('update:settings', { ...props.settings, acrylicIntensity: value })
|
||||
|
||||
Reference in New Issue
Block a user