diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index 7e792ea..b4f0099 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -109,11 +109,19 @@ export class AppearanceConfig { */ "language": LanguageType; + /** + * 编辑器主题 + */ + "theme": ThemeType; + /** Creates a new AppearanceConfig instance. */ constructor($$source: Partial = {}) { if (!("language" in $$source)) { this["language"] = ("" as LanguageType); } + if (!("theme" in $$source)) { + this["theme"] = ("" as ThemeType); + } Object.assign(this, $$source); } @@ -513,6 +521,76 @@ export enum TabType { TabTypeTab = "tab", }; +/** + * ThemeType 主题类型定义 + */ +export enum ThemeType { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * ThemeDefaultDark 默认深色主题 + */ + ThemeDefaultDark = "default-dark", + + /** + * ThemeDracula Dracula主题 + */ + ThemeDracula = "dracula", + + /** + * ThemeAura Aura主题 + */ + ThemeAura = "aura", + + /** + * ThemeGithubDark GitHub深色主题 + */ + ThemeGithubDark = "github-dark", + + /** + * ThemeGithubLight GitHub浅色主题 + */ + ThemeGithubLight = "github-light", + + /** + * ThemeMaterialDark Material深色主题 + */ + ThemeMaterialDark = "material-dark", + + /** + * ThemeMaterialLight Material浅色主题 + */ + ThemeMaterialLight = "material-light", + + /** + * ThemeSolarizedDark Solarized深色主题 + */ + ThemeSolarizedDark = "solarized-dark", + + /** + * ThemeSolarizedLight Solarized浅色主题 + */ + ThemeSolarizedLight = "solarized-light", + + /** + * ThemeTokyoNight Tokyo Night主题 + */ + ThemeTokyoNight = "tokyo-night", + + /** + * ThemeTokyoNightStorm Tokyo Night Storm主题 + */ + ThemeTokyoNightStorm = "tokyo-night-storm", + + /** + * ThemeTokyoNightDay Tokyo Night Day主题 + */ + ThemeTokyoNightDay = "tokyo-night-day", +}; + /** * UpdatesConfig 更新设置配置 */ diff --git a/frontend/src/composables/useEditorTheme.ts b/frontend/src/composables/useEditorTheme.ts new file mode 100644 index 0000000..954f7b2 --- /dev/null +++ b/frontend/src/composables/useEditorTheme.ts @@ -0,0 +1,189 @@ +import { ref, computed, shallowRef } from 'vue'; +import { Extension, Compartment } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import type { ThemeType } from '@/types'; + +// 主题加载器类型 +type ThemeLoader = () => Promise; + +// 默认主题常量 +const DEFAULT_THEME = 'default-dark' as ThemeType; + +// 主题加载映射 +const themeLoaderMap = new Map(); + +// 初始化主题加载器 +const initThemeLoaders = () => { + themeLoaderMap.set('default-dark', () => import('@/views/editor/theme/default-dark').then(m => m.defaultDark)); + themeLoaderMap.set('dracula', () => import('@/views/editor/theme/dracula').then(m => m.dracula)); + themeLoaderMap.set('aura', () => import('@/views/editor/theme/aura').then(m => m.aura)); + themeLoaderMap.set('github-dark', () => import('@/views/editor/theme/github-dark').then(m => m.githubDark)); + themeLoaderMap.set('github-light', () => import('@/views/editor/theme/github-light').then(m => m.githubLight)); + themeLoaderMap.set('material-dark', () => import('@/views/editor/theme/material-dark').then(m => m.materialDark)); + themeLoaderMap.set('material-light', () => import('@/views/editor/theme/material-light').then(m => m.materialLight)); + themeLoaderMap.set('solarized-dark', () => import('@/views/editor/theme/solarized-dark').then(m => m.solarizedDark)); + themeLoaderMap.set('solarized-light', () => import('@/views/editor/theme/solarized-light').then(m => m.solarizedLight)); + themeLoaderMap.set('tokyo-night', () => import('@/views/editor/theme/tokyo-night').then(m => m.tokyoNight)); + themeLoaderMap.set('tokyo-night-storm', () => import('@/views/editor/theme/tokyo-night-storm').then(m => m.tokyoNightStorm)); + themeLoaderMap.set('tokyo-night-day', () => import('@/views/editor/theme/tokyo-night-day').then(m => m.tokyoNightDay)); +}; + +// 延迟初始化 +initThemeLoaders(); + +// 全局状态 +const currentTheme = ref(DEFAULT_THEME); +const themeCompartment = new Compartment(); +const themeCache = new Map(); +const failedThemes = new Set(); // 记录加载失败的主题 + +/** + * 编辑器主题管理 + */ +export function useEditorTheme() { + + /** + * 安全加载主题扩展 + */ + const loadTheme = async (targetTheme: ThemeType): Promise => { + // 1. 从缓存快速返回 + const cached = themeCache.get(targetTheme); + if (cached) return cached; + + // 2. 检查是否已知失败的主题,避免重复尝试 + if (failedThemes.has(targetTheme) && targetTheme !== DEFAULT_THEME) { + console.info(`Theme ${targetTheme} is known to fail, attempting default theme directly`); + return attemptLoadTheme(DEFAULT_THEME).catch(() => [] as Extension); + } + + // 3. 使用 try-catch 链和 nullish coalescing,替代递归 + const result = await attemptLoadTheme(targetTheme) + .catch(async (error) => { + // 仅当目标主题不是默认主题时,才尝试默认主题 + if (targetTheme !== DEFAULT_THEME) { + console.warn(`Theme ${targetTheme} failed, fallback to ${DEFAULT_THEME}:`, error); + return attemptLoadTheme(DEFAULT_THEME).catch((fallbackError) => { + console.error(`Fallback theme ${DEFAULT_THEME} also failed:`, fallbackError); + return [] as Extension; // 最终回退到空扩展 + }); + } + // 如果默认主题也失败了,返回空扩展 + console.error(`Default theme ${DEFAULT_THEME} failed:`, error); + return [] as Extension; + }); + + return result; + }; + + /** + * 单纯的主题加载尝试 - 不处理回退逻辑 + */ + const attemptLoadTheme = async (themeType: ThemeType): Promise => { + // 获取加载器,使用 optional chaining 和 nullish coalescing + const loader = themeLoaderMap.get(themeType); + + if (!loader) { + const error = new Error(`Theme loader not found: ${themeType}`); + failedThemes.add(themeType); + throw error; + } + + try { + const extension = await loader(); + + // 缓存成功加载的主题 + themeCache.set(themeType, extension); + // 从失败列表中移除(如果存在) + failedThemes.delete(themeType); + + return extension; + } catch (error) { + // 记录失败的主题 + failedThemes.add(themeType); + console.error(`Failed to load theme: ${themeType}`, error); + throw error; + } + }; + + /** + * 创建可配置的主题扩展 + */ + const createThemeExtension = async (themeType: ThemeType): Promise => { + const extension = await loadTheme(themeType); + currentTheme.value = themeType; + return themeCompartment.of(extension); + }; + + /** + * 更新编辑器主题 - 使用防抖和错误处理 + */ + const updateTheme = async (view: EditorView, themeType: ThemeType): Promise => { + // 使用可选链操作符检查 view + if (!view?.dispatch || themeType === currentTheme.value) { + return; + } + + const extension = await loadTheme(themeType); + + // 使用 try-catch 包装 dispatch,避免编辑器状态异常 + try { + view.dispatch({ + effects: themeCompartment.reconfigure(extension) + }); + currentTheme.value = themeType; + } catch (error) { + console.error('Failed to dispatch theme update:', error); + throw error; // 重新抛出,让调用者处理 + } + }; + + /** + * 批量预加载主题 - 使用 Promise.allSettled 确保部分失败不影响其他 + */ + const preloadThemes = async (themes: ThemeType[]): Promise[]> => { + const uniqueThemes = [...new Set(themes)]; // 去重 + return Promise.allSettled( + uniqueThemes.map(theme => loadTheme(theme)) + ); + }; + + /** + * 重置主题系统状态 + */ + const resetThemeSystem = (): void => { + themeCache.clear(); + failedThemes.clear(); + currentTheme.value = DEFAULT_THEME; + }; + + /** + * 获取主题系统状态信息 + */ + const getThemeSystemInfo = () => ({ + currentTheme: currentTheme.value, + cachedThemes: Array.from(themeCache.keys()), + failedThemes: Array.from(failedThemes), + availableThemes: Array.from(themeLoaderMap.keys()), + }); + + return { + // 状态 + currentTheme: computed(() => currentTheme.value), + + // 核心方法 + createThemeExtension, + updateTheme, + loadTheme, + + // 批量操作 + preloadThemes, + + // 工具方法 + resetThemeSystem, + getThemeSystemInfo, + + // 缓存管理 + clearCache: () => themeCache.clear(), + clearFailedThemes: () => failedThemes.clear(), + }; +} \ No newline at end of file diff --git a/frontend/src/composables/useTheme.ts b/frontend/src/composables/useTheme.ts new file mode 100644 index 0000000..973d711 --- /dev/null +++ b/frontend/src/composables/useTheme.ts @@ -0,0 +1,59 @@ +import { useConfigStore } from '@/stores/configStore'; +import { useEditorTheme } from './useEditorTheme'; +import type { ThemeType } from '@/types'; + +/** + * 主题管理 - 用于设置页面 + */ +export function useTheme() { + const configStore = useConfigStore(); + const { preloadThemes, getThemeSystemInfo } = useEditorTheme(); + + /** + * 设置主题 - 同时更新配置和预览 + */ + const setTheme = async (themeType: ThemeType): Promise => { + try { + // 更新配置存储(这会自动触发编辑器主题更新) + await configStore.setTheme(themeType); + + console.info(`Theme switched to: ${themeType}`); + } catch (error) { + console.error('Failed to set theme:', error); + throw error; + } + }; + + /** + * 预加载常用主题 + */ + const preloadCommonThemes = async (): Promise => { + const commonThemes: ThemeType[] = [ + 'default-dark' as ThemeType, + 'dracula' as ThemeType, + 'github-dark' as ThemeType, + 'material-dark' as ThemeType + ]; + + try { + await preloadThemes(commonThemes); + console.info('Common themes preloaded successfully'); + } catch (error) { + console.warn('Some themes failed to preload:', error); + } + }; + + /** + * 获取主题状态 + */ + const getThemeStatus = () => ({ + current: configStore.config.appearance.theme, + ...getThemeSystemInfo() + }); + + return { + setTheme, + preloadCommonThemes, + getThemeStatus, + }; +} \ No newline at end of file diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 1aa2e56..915d0ac 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -29,7 +29,9 @@ export default { alwaysOnTopFailed: 'Failed to set window always on top', alwaysOnTopSuccess: 'Window always on top status updated', languageChanged: 'Language setting updated', - languageChangeFailed: 'Failed to update language setting' + languageChangeFailed: 'Failed to update language setting', + themeChanged: 'Theme setting updated', + themeChangeFailed: 'Failed to update theme setting' }, languages: { 'zh-CN': '简体中文', @@ -97,6 +99,8 @@ export default { tabs: 'Tabs', enableTabIndent: 'Enable Tab Indent', language: 'Interface Language', + theme: 'Editor Theme', + themeDescription: 'Choose editor theme', restartRequired: '(Restart required)', saveOptions: 'Save Options', autoSaveDelay: 'Auto Save Delay (ms)', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 573682c..d9c7ccb 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -29,7 +29,9 @@ export default { alwaysOnTopFailed: '无法设置窗口置顶状态', alwaysOnTopSuccess: '窗口置顶状态已更新', languageChanged: '语言设置已更新', - languageChangeFailed: '语言设置更新失败' + languageChangeFailed: '语言设置更新失败', + themeChanged: '主题设置已更新', + themeChangeFailed: '主题设置更新失败' }, languages: { 'zh-CN': '简体中文', @@ -97,6 +99,8 @@ export default { tabs: '制表符', enableTabIndent: '启用 Tab 缩进', language: '界面语言', + theme: '编辑器主题', + themeDescription: '选择编辑器主题', restartRequired: '(需要重启)', saveOptions: '保存选项', autoSaveDelay: '自动保存延迟(毫秒)', diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index 7b9d0ce..fb147d5 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -7,7 +7,8 @@ import { EditingConfig, GeneralConfig, LanguageType, - TabType + TabType, + ThemeType } from '@/../bindings/voidraft/internal/models'; import {useI18n} from 'vue-i18n'; import {useErrorHandler} from '@/utils/errorHandler'; @@ -64,7 +65,8 @@ const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = { } as const; const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = { - language: 'appearance.language' + language: 'appearance.language', + theme: 'appearance.theme' } as const; // 配置限制 @@ -113,7 +115,7 @@ const getBrowserLanguage = (): SupportedLocaleType => { const DEFAULT_CONFIG: AppConfig = { general: { alwaysOnTop: false, - dataPath: './data', + dataPath: '', enableGlobalHotkey: false, globalHotkey: { ctrl: false, @@ -134,7 +136,8 @@ const DEFAULT_CONFIG: AppConfig = { autoSaveDelay: 5000 }, appearance: { - language: LanguageType.LangZhCN + language: LanguageType.LangZhCN, + theme: 'default-dark' as ThemeType }, keyBindings: {}, updates: {}, @@ -296,6 +299,13 @@ export const useConfigStore = defineStore('config', () => { }, 'config.languageChangeFailed', 'config.languageChanged'); }; + // 主题设置方法 + const setTheme = async (theme: ThemeType): Promise => { + await safeCall(async () => { + await updateAppearanceConfig('theme', theme); + }, 'config.themeChangeFailed', 'config.themeChanged'); + }; + // 初始化语言设置 const initializeLanguage = async (): Promise => { try { @@ -358,6 +368,9 @@ export const useConfigStore = defineStore('config', () => { setLanguage, initializeLanguage, + // 主题相关方法 + setTheme, + // 字体大小操作 ...adjusters.fontSize, increaseFontSize: adjusters.fontSize.increase, diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..e7e6d96 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,4 @@ +// 统一类型导出 +export type { ThemeType, LanguageType } from '@/../bindings/voidraft/internal/models'; +export * from './theme'; +export * from './editor'; \ No newline at end of file diff --git a/frontend/src/types/theme.ts b/frontend/src/types/theme.ts new file mode 100644 index 0000000..5ddd498 --- /dev/null +++ b/frontend/src/types/theme.ts @@ -0,0 +1,204 @@ +import type { ThemeType } from '@/../bindings/voidraft/internal/models'; + +// 主题配置信息 +export interface ThemeInfo { + id: ThemeType; + name: string; + displayName: string; + isDark: boolean; + previewColors: { + background: string; + foreground: string; + keyword: string; + string: string; + function: string; + comment: string; + }; +} + +// 可用主题列表 +export const AVAILABLE_THEMES: ThemeInfo[] = [ + { + id: 'default-dark' as ThemeType, + name: 'default-dark', + displayName: '深色默认', + isDark: true, + previewColors: { + background: '#252B37', + foreground: '#9BB586', + keyword: '#FF79C6', + string: '#F1FA8C', + function: '#50FA7B', + comment: '#6272A4' + } + }, + { + id: 'dracula' as ThemeType, + name: 'dracula', + displayName: 'Dracula', + isDark: true, + previewColors: { + background: '#282A36', + foreground: '#F8F8F2', + keyword: '#FF79C6', + string: '#F1FA8C', + function: '#50FA7B', + comment: '#6272A4' + } + }, + { + id: 'aura' as ThemeType, + name: 'aura', + displayName: 'Aura', + isDark: true, + previewColors: { + background: '#21202e', + foreground: '#edecee', + keyword: '#a277ff', + string: '#61ffca', + function: '#ffca85', + comment: '#6d6d6d' + } + }, + { + id: 'github-dark' as ThemeType, + name: 'github-dark', + displayName: 'GitHub 深色', + isDark: true, + previewColors: { + background: '#24292e', + foreground: '#d1d5da', + keyword: '#f97583', + string: '#9ecbff', + function: '#79b8ff', + comment: '#6a737d' + } + }, + { + id: 'github-light' as ThemeType, + name: 'github-light', + displayName: 'GitHub 浅色', + isDark: false, + previewColors: { + background: '#fff', + foreground: '#444d56', + keyword: '#d73a49', + string: '#032f62', + function: '#005cc5', + comment: '#6a737d' + } + }, + { + id: 'material-dark' as ThemeType, + name: 'material-dark', + displayName: 'Material 深色', + isDark: true, + previewColors: { + background: '#263238', + foreground: '#EEFFFF', + keyword: '#C792EA', + string: '#C3E88D', + function: '#82AAFF', + comment: '#546E7A' + } + }, + { + id: 'material-light' as ThemeType, + name: 'material-light', + displayName: 'Material 浅色', + isDark: false, + previewColors: { + background: '#FAFAFA', + foreground: '#90A4AE', + keyword: '#7C4DFF', + string: '#91B859', + function: '#6182B8', + comment: '#90A4AE' + } + }, + { + id: 'solarized-dark' as ThemeType, + name: 'solarized-dark', + displayName: 'Solarized 深色', + isDark: true, + previewColors: { + background: '#002B36', + foreground: '#93A1A1', + keyword: '#859900', + string: '#2AA198', + function: '#268BD2', + comment: '#586E75' + } + }, + { + id: 'solarized-light' as ThemeType, + name: 'solarized-light', + displayName: 'Solarized 浅色', + isDark: false, + previewColors: { + background: '#FDF6E3', + foreground: '#586E75', + keyword: '#859900', + string: '#2AA198', + function: '#268BD2', + comment: '#93A1A1' + } + }, + { + id: 'tokyo-night' as ThemeType, + name: 'tokyo-night', + displayName: 'Tokyo Night', + isDark: true, + previewColors: { + background: '#1a1b26', + foreground: '#787c99', + keyword: '#bb9af7', + string: '#9ece6a', + function: '#7aa2f7', + comment: '#444b6a' + } + }, + { + id: 'tokyo-night-storm' as ThemeType, + name: 'tokyo-night-storm', + displayName: 'Tokyo Night Storm', + isDark: true, + previewColors: { + background: '#24283b', + foreground: '#7982a9', + keyword: '#bb9af7', + string: '#9ece6a', + function: '#7aa2f7', + comment: '#565f89' + } + }, + { + id: 'tokyo-night-day' as ThemeType, + name: 'tokyo-night-day', + displayName: 'Tokyo Night Day', + isDark: false, + previewColors: { + background: '#e1e2e7', + foreground: '#6a6f8e', + keyword: '#9854f1', + string: '#587539', + function: '#2e7de9', + comment: '#9da3c2' + } + } +]; + +// 根据主题ID获取主题信息 +export function getThemeInfo(themeId: ThemeType): ThemeInfo | undefined { + return AVAILABLE_THEMES.find(theme => theme.id === themeId); +} + +// 获取所有深色主题 +export function getDarkThemes(): ThemeInfo[] { + return AVAILABLE_THEMES.filter(theme => theme.isDark); +} + +// 获取所有浅色主题 +export function getLightThemes(): ThemeInfo[] { + return AVAILABLE_THEMES.filter(theme => !theme.isDark); +} \ No newline at end of file diff --git a/frontend/src/views/editor/Editor.vue b/frontend/src/views/editor/Editor.vue index 3f99df4..3216e82 100644 --- a/frontend/src/views/editor/Editor.vue +++ b/frontend/src/views/editor/Editor.vue @@ -18,7 +18,9 @@ import { createFontExtensionFromBackend, updateFontConfig, } from './extensions'; +import { useEditorTheme } from '@/composables/useEditorTheme'; import { useI18n } from 'vue-i18n'; +import type { ThemeType } from '@/types'; import { DocumentService } from '../../../bindings/voidraft/internal/services'; import Toolbar from '@/components/toolbar/Toolbar.vue'; @@ -27,6 +29,7 @@ const configStore = useConfigStore(); const documentStore = useDocumentStore(); const logStore = useLogStore(); const { t } = useI18n(); +const { createThemeExtension, updateTheme } = useEditorTheme(); const props = defineProps({ initialDoc: { @@ -51,6 +54,11 @@ const createEditor = async () => { // 获取基本扩展 const basicExtensions = createBasicSetup(); + // 获取主题扩展 + const themeExtension = await createThemeExtension( + configStore.config.appearance.theme || 'default-dark' as ThemeType + ); + // 获取Tab相关扩展 const tabExtensions = getTabExtensions( configStore.config.editing.tabSize, @@ -90,6 +98,7 @@ const createEditor = async () => { // 组合所有扩展 const extensions: Extension[] = [ + themeExtension, ...basicExtensions, ...tabExtensions, fontExtension, @@ -185,6 +194,13 @@ watch([ editorStore.applyFontSize(); }); +// 监听主题变化 +watch(() => configStore.config.appearance.theme, async (newTheme) => { + if (newTheme && editorStore.editorView) { + await updateTheme(editorStore.editorView as EditorView, newTheme); + } +}); + onMounted(() => { // 创建编辑器 createEditor(); diff --git a/frontend/src/views/editor/extensions/basicSetup.ts b/frontend/src/views/editor/extensions/basicSetup.ts index c8638db..dca727d 100644 --- a/frontend/src/views/editor/extensions/basicSetup.ts +++ b/frontend/src/views/editor/extensions/basicSetup.ts @@ -23,13 +23,10 @@ import {defaultKeymap, history, historyKeymap,} from '@codemirror/commands'; import {highlightSelectionMatches, searchKeymap} from '@codemirror/search'; import {autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete'; import {lintKeymap} from '@codemirror/lint'; -import {customHighlightActiveLine, defaultDark} from '@/views/editor/theme/default-dark'; // 基本编辑器设置,包含常用扩展 export const createBasicSetup = (): Extension[] => { return [ - // 主题相关 - defaultDark, // 基础UI lineNumbers(), @@ -46,7 +43,6 @@ export const createBasicSetup = (): Extension[] => { // 选择与高亮 drawSelection(), - customHighlightActiveLine, highlightActiveLine(), highlightSelectionMatches(), rectangularSelection(), diff --git a/frontend/src/views/settings/pages/AppearancePage.vue b/frontend/src/views/settings/pages/AppearancePage.vue index 2140374..0a80234 100644 --- a/frontend/src/views/settings/pages/AppearancePage.vue +++ b/frontend/src/views/settings/pages/AppearancePage.vue @@ -2,14 +2,18 @@ import { useConfigStore } from '@/stores/configStore'; import { useErrorHandler } from '@/utils/errorHandler'; import { useI18n } from 'vue-i18n'; -import { ref } from 'vue'; +import { ref, computed, watch } from 'vue'; import SettingSection from '../components/SettingSection.vue'; import SettingItem from '../components/SettingItem.vue'; -import { LanguageType } from '../../../../bindings/voidraft/internal/models/models'; +import type { ThemeType } from '@/types'; +import { LanguageType } from '@/../bindings/voidraft/internal/models'; +import { AVAILABLE_THEMES } from '@/types/theme'; +import { useTheme } from '@/composables/useTheme'; const { t } = useI18n(); const configStore = useConfigStore(); const { safeCall } = useErrorHandler(); +const { setTheme: setThemeComposable } = useTheme(); // 语言选项 const languageOptions = [ @@ -28,19 +32,37 @@ const updateLanguage = async (event: Event) => { ); }; -// 主题选择(未实际实现,仅界面展示) -const themeOptions = [ - { id: 'dark', name: '深色', color: '#2a2a2a' }, - { id: 'darker', name: '暗黑', color: '#1a1a1a' }, - { id: 'light', name: '浅色', color: '#f5f5f5' }, - { id: 'blue', name: '蓝调', color: '#1e3a5f' }, -]; +// 主题选择 +const themeOptions = computed(() => AVAILABLE_THEMES); +const selectedTheme = ref(configStore.config.appearance.theme || 'default-dark' as ThemeType); -const selectedTheme = ref('dark'); +// 当前主题预览信息 +const currentPreviewTheme = computed(() => { + const theme = themeOptions.value.find(t => t.id === selectedTheme.value); + return theme || themeOptions.value[0]; +}); -const selectTheme = (themeId: string) => { +// 选择主题 +const selectTheme = async (themeId: ThemeType) => { selectedTheme.value = themeId; + + // 更新配置(这会自动触发编辑器主题更新) + await safeCall( + () => configStore.setTheme(themeId), + 'config.themeChangeFailed' + ); + + // 同步更新预览(用于设置页面的预览区域) + await setThemeComposable(themeId); }; + +// 监听配置变化,同步主题选择 +watch(() => configStore.config.appearance.theme, (newTheme) => { + if (newTheme && newTheme !== selectedTheme.value) { + selectedTheme.value = newTheme; + setThemeComposable(newTheme); + } +}, { immediate: true });