feat(theme): implement comprehensive theme management system
Add full theme support with light, dark, and system modes, including: - Theme store using Pinia for state management - useTheme composable for reactive theme handling - Theme setting UI in settings page - Enhanced CSS variable system with Tailwind integration - IPC communication for theme persistence - Internationalization support for theme texts - System theme detection and auto-switching The implementation follows ClawX's architecture while adapting to Vue 3 and zn-ai's existing infrastructure.
This commit is contained in:
182
src/stores/theme.ts
Normal file
182
src/stores/theme.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 主题状态管理 store
|
||||
* 采用 Vue 推荐的 Pinia 实现,功能和架构与 ClawX 的 Zustand 实现保持一致
|
||||
* 集成 zn-ai 现有 IPC 通信
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
interface ThemeState {
|
||||
// 主题状态
|
||||
theme: Theme;
|
||||
isDark: boolean;
|
||||
systemTheme: 'light' | 'dark';
|
||||
|
||||
// 初始化状态
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
// 检测系统主题
|
||||
const detectSystemTheme = (): 'light' | 'dark' => {
|
||||
if (typeof window === 'undefined') return 'light';
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
|
||||
// 计算实际应用的主题(考虑 system 模式)
|
||||
const getAppliedTheme = (theme: Theme, systemTheme: 'light' | 'dark'): 'light' | 'dark' => {
|
||||
if (theme === 'system') return systemTheme;
|
||||
return theme;
|
||||
};
|
||||
|
||||
// 应用主题到 DOM(通过 CSS 类)
|
||||
const applyThemeToDom = (theme: 'light' | 'dark') => {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
const root = document.documentElement;
|
||||
if (theme === 'dark') {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
export const useThemeStore = defineStore('zn-ai-theme', {
|
||||
state: (): ThemeState => {
|
||||
// 从 localStorage 恢复缓存的主题,确保在初始化前 UI 不会闪烁
|
||||
const cachedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('zn-ai-theme-cache') as Theme : null;
|
||||
const initialTheme = (cachedTheme === 'light' || cachedTheme === 'dark' || cachedTheme === 'system') ? cachedTheme : 'system';
|
||||
|
||||
return {
|
||||
theme: initialTheme,
|
||||
isDark: false,
|
||||
systemTheme: 'light',
|
||||
initialized: false,
|
||||
};
|
||||
},
|
||||
|
||||
actions: {
|
||||
async init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// 1. 检测系统主题
|
||||
const systemTheme = detectSystemTheme();
|
||||
|
||||
// 2. 从主进程获取持久化的主题设置
|
||||
let savedTheme: Theme = 'system';
|
||||
try {
|
||||
savedTheme = await window.api.invoke<Theme>('get-theme-mode');
|
||||
} catch (error) {
|
||||
console.warn('Failed to get theme from main process, using default:', error);
|
||||
}
|
||||
|
||||
// 3. 计算实际主题和 isDark 状态
|
||||
const appliedTheme = getAppliedTheme(savedTheme, systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
|
||||
// 4. 应用主题到 DOM
|
||||
applyThemeToDom(appliedTheme);
|
||||
|
||||
// 5. 更新 store 状态
|
||||
this.theme = savedTheme;
|
||||
this.isDark = isDark;
|
||||
this.systemTheme = systemTheme;
|
||||
this.initialized = true;
|
||||
|
||||
// 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', savedTheme);
|
||||
}
|
||||
|
||||
// 6. 监听系统主题变化
|
||||
if (typeof window !== 'undefined') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
const newSystemTheme = e.matches ? 'dark' : 'light';
|
||||
|
||||
if (this.theme === 'system') {
|
||||
const appliedTheme = getAppliedTheme('system', newSystemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
applyThemeToDom(appliedTheme);
|
||||
this.systemTheme = newSystemTheme;
|
||||
this.isDark = isDark;
|
||||
} else {
|
||||
this.systemTheme = newSystemTheme;
|
||||
}
|
||||
};
|
||||
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
} else {
|
||||
mediaQuery.addListener(handleChange);
|
||||
}
|
||||
|
||||
// 7. 监听主进程主题更新事件
|
||||
window.api.on('theme-mode-updated', (isDarkUpdate: boolean) => {
|
||||
const newIsDark = Boolean(isDarkUpdate);
|
||||
const newTheme = newIsDark ? 'dark' : 'light';
|
||||
if (this.theme === 'system') {
|
||||
this.isDark = newIsDark;
|
||||
} else {
|
||||
const actualTheme = newIsDark ? 'dark' : 'light';
|
||||
if (this.theme !== actualTheme) {
|
||||
this.theme = actualTheme;
|
||||
this.isDark = newIsDark;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Theme store initialized:', { theme: savedTheme, isDark, systemTheme });
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize theme store:', error);
|
||||
const systemTheme = detectSystemTheme();
|
||||
const appliedTheme = getAppliedTheme('system', systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
applyThemeToDom(appliedTheme);
|
||||
this.theme = 'system';
|
||||
this.isDark = isDark;
|
||||
this.systemTheme = systemTheme;
|
||||
this.initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
async setTheme(theme: Theme) {
|
||||
if (theme === this.theme) return;
|
||||
|
||||
try {
|
||||
// 1. 保存到主进程
|
||||
await window.api.invoke('set-theme-mode', theme);
|
||||
|
||||
// 2. 计算实际应用的主题
|
||||
const appliedTheme = getAppliedTheme(theme, this.systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
|
||||
// 3. 应用主题到 DOM
|
||||
applyThemeToDom(appliedTheme);
|
||||
|
||||
// 4. 更新 store 状态
|
||||
this.theme = theme;
|
||||
this.isDark = isDark;
|
||||
|
||||
// 5. 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', theme);
|
||||
}
|
||||
|
||||
console.log('Theme changed:', { theme, appliedTheme, isDark });
|
||||
} catch (error) {
|
||||
console.error('Failed to set theme:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
detectSystemTheme,
|
||||
|
||||
updateIsDark() {
|
||||
const appliedTheme = getAppliedTheme(this.theme, this.systemTheme);
|
||||
this.isDark = appliedTheme === 'dark';
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user