diff --git a/packages/gui/src/main.js b/packages/gui/src/main.js index 0f067b140e..f90d56c889 100644 --- a/packages/gui/src/main.js +++ b/packages/gui/src/main.js @@ -7,8 +7,8 @@ import App from './view/App.vue' import DsContainer from './view/components/container' import routes from './view/router' import 'ant-design-vue/dist/reset.css' +import './view/style/theme/variables.scss' import './view/style/index.scss' -import './view/style/theme/dark.scss' // 暗色主题 try { window.onerror = (message, source, lineno, colno, error) => { diff --git a/packages/gui/src/view/App.vue b/packages/gui/src/view/App.vue index de21789538..0d570d85df 100644 --- a/packages/gui/src/view/App.vue +++ b/packages/gui/src/view/App.vue @@ -5,7 +5,7 @@ import * as Icons from '@ant-design/icons-vue'; import { ipcRenderer } from 'electron' import createMenus from '@/view/router/menu' import zhCN from 'ant-design-vue/es/locale/zh_CN' -import { colorTheme } from './composables/theme' +import { appliedTheme, initTheme, getAntThemeConfig } from './composables/theme' export default { name: 'App', @@ -32,11 +32,11 @@ export default { }, computed: { - themeClass() { - return `theme-${this.theme}` - }, theme() { - return colorTheme.value + return appliedTheme.value + }, + themeConfig() { + return getAntThemeConfig(this.theme === 'dark') }, // 将菜单数据转换为 items 格式 menuItems() { @@ -125,13 +125,10 @@ export default { await this.configReadyPromise } + // 初始化主题系统 const appConfig = (this.config && this.config.app) || (this.$global && this.$global.config && this.$global.config.app) || {} - let theme = appConfig.theme || 'dark' - if (appConfig.theme === 'system') { - theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' - } - - colorTheme.value = theme + const initialThemeMode = appConfig.theme || 'dark' + this.cleanupTheme = initTheme(initialThemeMode) // 设置默认选中的菜单项 this.updateSelectedKeys(this.$route.fullPath) @@ -139,6 +136,10 @@ export default { beforeUnmount() { ipcRenderer.removeListener('config.changed', this.onConfigChanged) + // 清理主题监听器 + if (this.cleanupTheme) { + this.cleanupTheme() + } }, methods: { @@ -258,10 +259,10 @@ export default { - diff --git a/packages/gui/src/view/composables/theme.js b/packages/gui/src/view/composables/theme.js index c5962619cd..26769c2156 100644 --- a/packages/gui/src/view/composables/theme.js +++ b/packages/gui/src/view/composables/theme.js @@ -1,3 +1,139 @@ -import { ref } from 'vue' +import { ref, computed } from 'vue' +import { theme } from 'ant-design-vue' -export const colorTheme = ref('dark') +// 主题类型:light-亮色, dark-暗色, system-跟随系统 +export const themeMode = ref('dark') + +// 实际应用的主题(system模式下会根据系统主题计算) +export const appliedTheme = ref('dark') + +// 系统主题媒体查询 +const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + +/** + * 获取系统主题 + * @returns {'dark' | 'light'} + */ +function getSystemTheme() { + return mediaQuery.matches ? 'dark' : 'light' +} + +/** + * 应用主题到 DOM + * @param {'dark' | 'light'} theme + */ +function applyThemeToDOM(theme) { + appliedTheme.value = theme + document.documentElement.setAttribute('data-theme', theme) +} + +/** + * 更新主题 + * 根据 themeMode 的值计算并应用实际主题 + */ +export function updateTheme() { + const actualTheme = themeMode.value === 'system' + ? getSystemTheme() + : themeMode.value + + applyThemeToDOM(actualTheme) +} + +/** + * 设置主题模式 + * @param {'light' | 'dark' | 'system'} mode + */ +export function setThemeMode(mode) { + themeMode.value = mode + updateTheme() +} + +/** + * 初始化主题系统 + * 应在应用挂载时调用 + * @param {'light' | 'dark' | 'system'} initialMode - 初始主题模式 + */ +export function initTheme(initialMode = 'dark') { + themeMode.value = initialMode + updateTheme() + + // 监听系统主题变化 + const handleSystemThemeChange = () => { + if (themeMode.value === 'system') { + updateTheme() + } + } + + // 添加监听器 + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', handleSystemThemeChange) + } else { + // 兼容旧版浏览器 + mediaQuery.addListener(handleSystemThemeChange) + } + + // 返回清理函数 + return () => { + if (mediaQuery.removeEventListener) { + mediaQuery.removeEventListener('change', handleSystemThemeChange) + } else { + mediaQuery.removeListener(handleSystemThemeChange) + } + } +} + +/** + * 获取 Ant Design 主题配置 + * @param {boolean} isDarkTheme - 是否为暗色主题 + * @returns {Object} Ant Design 主题配置对象 + */ +export function getAntThemeConfig(isDarkTheme) { + return { + algorithm: isDarkTheme + ? theme.darkAlgorithm + : theme.defaultAlgorithm, + token: isDarkTheme + ? { + colorBgBase: '#1e1f22', + colorTextBase: '#dddddd', + colorBgContainer: '#1e1f22', + } + : { + colorBgBase: '#ffffff', + colorTextBase: '#333333', + colorBgContainer: '#ffffff', + }, + } +} + +/** + * 在组件中使用主题的组合式函数 + * 提供响应式的主题状态和切换方法 + */ +export function useTheme() { + // 计算属性:是否为暗色主题 + const isDark = computed(() => appliedTheme.value === 'dark') + + // 计算属性:是否为亮色主题 + const isLight = computed(() => appliedTheme.value === 'light') + + // 计算属性:当前是否为跟随系统模式 + const isSystem = computed(() => themeMode.value === 'system') + + // Ant Design 主题配置 + const antThemeConfig = computed(() => getAntThemeConfig(isDark.value)) + + return { + themeMode, + appliedTheme, + isDark, + isLight, + isSystem, + setThemeMode, + updateTheme, + antThemeConfig, + } +} + +// 为了兼容旧代码,保留 colorTheme 导出 +export const colorTheme = appliedTheme diff --git a/packages/gui/src/view/pages/index.vue b/packages/gui/src/view/pages/index.vue index 9c93a37b43..1a825211ed 100644 --- a/packages/gui/src/view/pages/index.vue +++ b/packages/gui/src/view/pages/index.vue @@ -422,23 +422,23 @@ export default { height: 100px; border-radius: 100px; transition: all 0.3s ease; - border: 2px solid #d9d9d9; - background-color: #fff; + border: 2px solid var(--btn-border); + background-color: var(--btn-bg); &:hover { - border-color: #40a9ff; - box-shadow: 0 0 8px rgba(24, 144, 255, 0.2); + border-color: var(--accent-hover); + box-shadow: 0 0 8px var(--accent-shadow); } /* 激活状态 */ &.is-active { - background-color: #1890ff; - border-color: #1890ff; - box-shadow: 0 0 12px rgba(24, 144, 255, 0.4); + background-color: var(--accent-color); + border-color: var(--accent-color); + box-shadow: 0 0 12px var(--accent-shadow); &:hover { - background-color: #40a9ff; - border-color: #40a9ff; + background-color: var(--accent-hover); + border-color: var(--accent-hover); } } } @@ -452,11 +452,11 @@ export default { margin-top: 10px; font-size: 14px; font-weight: 500; - color: #666; + color: var(--text-secondary); transition: color 0.3s ease; &.is-active { - color: #1890ff; + color: var(--accent-color); font-weight: 600; } } diff --git a/packages/gui/src/view/pages/plugin/free-eye.vue b/packages/gui/src/view/pages/plugin/free-eye.vue index 5b68fe71df..bf36bbca42 100644 --- a/packages/gui/src/view/pages/plugin/free-eye.vue +++ b/packages/gui/src/view/pages/plugin/free-eye.vue @@ -1,10 +1,11 @@