新增 跟随系统主题切换功能,深色模式自动隐藏亚克力效果

This commit is contained in:
2026-04-18 23:12:08 +08:00
parent fa255f78b4
commit 902013f22f
9 changed files with 55 additions and 17 deletions

View File

@@ -97,7 +97,7 @@ npm run test:run
### 主题系统
支持种主题:`Light` (浅色) / `Dark` (深色)
支持种主题:`Light` (浅色) / `Dark` (深色) / `System` (跟随系统)
CSS 变量定义在 `src/styles/global.less`,包括:
- `--bg-primary/secondary/elevated` - 背景层级

View File

@@ -9,7 +9,7 @@
- 📝 **API 配置管理** - 支持多环境配置文件切换、创建、编辑、复制和删除
- 🖥️ **MCP 服务器管理** - 便捷的 Model Context Protocol 服务器配置界面
- 🎨 **Windows 11 设计风格** - 采用 Fluent Design 设计规范
- 🌈 **多主题支持** - Light / Dark 种主题
- 🌈 **多主题支持** - Light / Dark / System (跟随系统) 三种主题
- 🌍 **国际化** - 支持简体中文、English、日語
- 💧 **亚克力效果** - 可调节透明度的现代视觉效果
- 📦 **系统托盘** - 最小化到托盘,快速切换 API 配置

View File

@@ -1,6 +1,6 @@
{
"name": "iflow-settings-editor",
"version": "1.6.0",
"version": "1.6.1",
"description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。",
"main": "main.js",
"author": "上海潘哆呐科技有限公司",

View File

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

View File

@@ -33,7 +33,8 @@ export default {
},
theme: {
dark: 'Dark',
light: 'Light'
light: 'Light',
system: 'System'
},
api: {
title: 'API Configuration',

View File

@@ -33,7 +33,8 @@ export default {
},
theme: {
dark: '深色',
light: '浅色'
light: '浅色',
system: '跟随系统'
},
api: {
title: 'API 配置',

View File

@@ -33,7 +33,8 @@ export default {
},
theme: {
dark: 'ダーク',
light: 'ライト'
light: 'ライト',
system: 'システム'
},
api: {
title: 'API 設定',

View File

@@ -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 () => {

View File

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