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