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:
duanshuwen
2026-04-08 23:46:41 +08:00
parent 3ef3392808
commit a8bfbff0e9
12 changed files with 1450 additions and 326 deletions

139
src/composables/useTheme.ts Normal file
View File

@@ -0,0 +1,139 @@
/**
* 主题管理 composable
* 提供响应式的主题状态和操作方法
*/
import { computed, onMounted, onUnmounted } from 'vue';
import { useThemeStore, type Theme } from '@src/stores/theme';
/**
* 主题管理 composable
* 封装了主题状态管理、切换和系统主题检测
*/
export function useTheme() {
const themeStore = useThemeStore();
// 响应式状态
const theme = computed(() => themeStore.theme);
const isDark = computed(() => themeStore.isDark);
const systemTheme = computed(() => themeStore.systemTheme);
const initialized = computed(() => themeStore.initialized);
// 计算实际应用的主题(考虑 system 模式)
const appliedTheme = computed(() => {
if (theme.value === 'system') return systemTheme.value;
return theme.value;
});
// 主题选项
const themeOptions = computed(() => [
{ value: 'light' as Theme, label: '浅色' },
{ value: 'dark' as Theme, label: '深色' },
{ value: 'system' as Theme, label: '跟随系统' },
]);
// 获取主题对应的标签
const getThemeLabel = (themeValue: Theme): string => {
const option = themeOptions.value.find(opt => opt.value === themeValue);
return option?.label || themeValue;
};
// 当前主题标签
const currentThemeLabel = computed(() => getThemeLabel(theme.value));
/**
* 初始化主题设置
* 应该在应用启动时调用一次
*/
const initTheme = async () => {
await themeStore.init();
};
/**
* 切换主题
* @param newTheme 新的主题模式
*/
const setTheme = async (newTheme: Theme) => {
await themeStore.setTheme(newTheme);
};
/**
* 切换主题(轮换)
*/
const toggleTheme = async () => {
const themes: Theme[] = ['light', 'dark', 'system'];
const currentIndex = themes.indexOf(theme.value);
const nextIndex = (currentIndex + 1) % themes.length;
await setTheme(themes[nextIndex]);
};
/**
* 重置为系统主题
*/
const resetToSystemTheme = async () => {
await setTheme('system');
};
/**
* 检测当前系统主题
*/
const detectSystemTheme = () => {
return themeStore.detectSystemTheme();
};
// 监听系统主题变化store 内部已处理,这里提供额外的事件监听)
const onSystemThemeChange = (callback: (theme: 'light' | 'dark') => void) => {
if (typeof window === 'undefined') return () => {};
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e: MediaQueryListEvent) => {
callback(e.matches ? 'dark' : 'light');
};
// 现代浏览器支持 addEventListener
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
} else {
// 旧浏览器支持 addListener
mediaQuery.addListener(handleChange);
return () => mediaQuery.removeListener(handleChange);
}
};
// 自动初始化(可选)
onMounted(() => {
if (!themeStore.initialized) {
initTheme().catch(console.error);
}
});
return {
// 响应式状态
theme,
isDark,
systemTheme,
appliedTheme,
initialized,
// 主题选项
themeOptions,
currentThemeLabel,
// 操作方法
initTheme,
setTheme,
toggleTheme,
resetToSystemTheme,
detectSystemTheme,
getThemeLabel,
// 事件监听
onSystemThemeChange,
// store 引用(谨慎使用)
themeStore,
};
}
// 导出类型
export type { Theme };