diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 284ee74..ea0c8ed 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,8 +7,8 @@ const configStore = useConfigStore(); // 应用启动时加载配置 onMounted(async () => { - await configStore.initializeLanguage(); await configStore.initConfig(); + await configStore.initializeLanguage(); }); diff --git a/frontend/src/components/monitor/MemoryMonitor.vue b/frontend/src/components/monitor/MemoryMonitor.vue index c15f62b..0bb223f 100644 --- a/frontend/src/components/monitor/MemoryMonitor.vue +++ b/frontend/src/components/monitor/MemoryMonitor.vue @@ -13,10 +13,28 @@ let intervalId: ReturnType | null = null; const historyData = ref([]); const maxDataPoints = 60; +// 静默错误处理包装器 +const withSilentErrorHandling = async ( + operation: () => Promise, + fallback?: T +): Promise => { + try { + return await operation(); + } catch (error) { + // 静默处理错误,不输出到控制台 + return fallback; + } +}; + // 获取内存统计信息 const fetchMemoryStats = async () => { - try { - const stats = await SystemService.GetMemoryStats(); + const stats = await withSilentErrorHandling(() => SystemService.GetMemoryStats()); + + if (!stats) { + isLoading.value = false; + return; + } + memoryStats.value = stats; // 格式化内存显示 - 主要显示堆内存使用量 @@ -42,10 +60,6 @@ const fetchMemoryStats = async () => { drawChart(); isLoading.value = false; - } catch (error) { - console.error('Failed to fetch memory stats:', error); - isLoading.value = false; - } }; // 绘制实时曲线图 @@ -199,12 +213,11 @@ const drawChart = () => { // 手动触发GC const triggerGC = async () => { - try { - await SystemService.TriggerGC(); + const success = await withSilentErrorHandling(() => SystemService.TriggerGC()); + + if (success) { // 延迟一下再获取新的统计信息 setTimeout(fetchMemoryStats, 100); - } catch (error) { - console.error('Failed to trigger GC:', error); } }; diff --git a/frontend/src/components/toolbar/Toolbar.vue b/frontend/src/components/toolbar/Toolbar.vue index 5b42280..186ba62 100644 --- a/frontend/src/components/toolbar/Toolbar.vue +++ b/frontend/src/components/toolbar/Toolbar.vue @@ -2,15 +2,16 @@ import {useEditorStore} from '@/stores/editorStore'; import {useConfigStore, SUPPORTED_LOCALES, type SupportedLocaleType} from '@/stores/configStore'; import {useLogStore} from '@/stores/logStore'; +import { useErrorHandler } from '@/utils/errorHandler'; import { useI18n } from 'vue-i18n'; import { ref, onMounted, watch } from 'vue'; -import { ConfigUtils } from '@/utils/configUtils'; import * as runtime from '@wailsio/runtime'; import { useRouter } from 'vue-router'; const editorStore = useEditorStore(); const configStore = useConfigStore(); const logStore = useLogStore(); +const { safeCall } = useErrorHandler(); const { t, locale } = useI18n(); const router = useRouter(); // 语言下拉菜单 @@ -18,13 +19,10 @@ const showLanguageMenu = ref(false); // 切换语言 const changeLanguage = async (localeCode: SupportedLocaleType) => { - try { - // 使用 configStore 的语言设置方法 - await configStore.setLocale(localeCode); - } catch (error) { - console.error('Failed to change language:', error); - } - + await safeCall( + () => configStore.setLocale(localeCode), + 'config.languageChangeFailed' + ); showLanguageMenu.value = false; }; @@ -35,14 +33,11 @@ const toggleLanguageMenu = () => { // 窗口置顶控制 const toggleAlwaysOnTop = async () => { - try { + await safeCall(async () => { await configStore.toggleAlwaysOnTop(); // 使用Window.SetAlwaysOnTop方法设置窗口置顶状态 await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop); - } catch (error) { - console.error('Failed to set window always on top:', error); - logStore.error(t('config.alwaysOnTopFailed')); - } + }, 'config.alwaysOnTopFailed'); }; // 打开设置页面 @@ -50,31 +45,31 @@ const openSettings = () => { router.push('/settings'); }; +// 设置窗口置顶状态的通用函数 +const setWindowAlwaysOnTop = async (alwaysOnTop: boolean) => { + await safeCall( + () => runtime.Window.SetAlwaysOnTop(alwaysOnTop), + 'config.alwaysOnTopFailed' + ); +}; + // 初始化配置 onMounted(async () => { // 加载配置 if (!configStore.configLoaded) { - await configStore.loadConfig(); + await configStore.initConfig(); } // 设置窗口置顶状态 if (configStore.config.alwaysOnTop) { - try { - await runtime.Window.SetAlwaysOnTop(true); - } catch (error) { - console.error('Failed to set window always on top:', error); - } + await setWindowAlwaysOnTop(true); } }); // 监听配置加载完成 watch(() => configStore.configLoaded, (isLoaded) => { if (isLoaded && configStore.config.alwaysOnTop) { - try { - runtime.Window.SetAlwaysOnTop(true); - } catch (error) { - console.error('Failed to set window always on top:', error); - } + setWindowAlwaysOnTop(true); } }); diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index ac8d8ec..6676376 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -4,8 +4,8 @@ import { ConfigService } from '@/../bindings/voidraft/internal/services'; import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/internal/models/models'; -import {useLogStore} from './logStore'; import { useI18n } from 'vue-i18n'; +import { useErrorHandler } from '@/utils/errorHandler'; import { ConfigUtils } from '@/utils/configUtils'; // 国际化相关导入 @@ -21,7 +21,7 @@ export const SUPPORTED_LOCALES = [ code: 'en-US' as SupportedLocaleType, name: 'English' } -]; +] as const; // 配置键映射和限制的类型定义 type ConfigKeyMap = { @@ -93,8 +93,8 @@ const DEFAULT_CONFIG: EditorConfig = { }; export const useConfigStore = defineStore('config', () => { - const logStore = useLogStore(); - const { t, locale } = useI18n(); + const { locale } = useI18n(); + const { safeCall } = useErrorHandler(); // 响应式状态 const state = reactive({ @@ -105,57 +105,32 @@ export const useConfigStore = defineStore('config', () => { // 计算属性 - 使用工厂函数简化 const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]); - const limits = { - fontSize: createLimitComputed('fontSize'), - tabSize: createLimitComputed('tabSize'), - lineHeight: createLimitComputed('lineHeight') - }; - - // 错误处理装饰器 - const withErrorHandling = ( - fn: (...args: T) => Promise, - errorMsg: string, - successMsg?: string - ) => async (...args: T): Promise => { - const result = await fn(...args).catch(error => { - console.error(errorMsg, error); - logStore.error(t(errorMsg)); - return null; - }); - - if (result !== null && successMsg) { - logStore.info(t(successMsg)); - } - - return result; - }; + const limits = Object.fromEntries( + (['fontSize', 'tabSize', 'lineHeight'] as const).map(key => [key, createLimitComputed(key)]) + ) as Record>; // 通用配置更新方法 - const updateConfig = withErrorHandling( - async (key: K, value: EditorConfig[K]): Promise => { - // 确保配置已加载 - if (!state.configLoaded && !state.isLoading) { - await initConfig(); - } - - const backendKey = CONFIG_KEY_MAP[key]; - if (!backendKey) { - throw new Error(`No backend key mapping found for ${key.toString()}`); - } - - await ConfigService.Set(backendKey, value); - state.config[key] = value; - }, - 'config.saveFailed', - 'config.saveSuccess' - ); + const updateConfig = async (key: K, value: EditorConfig[K]): Promise => { + // 确保配置已加载 + if (!state.configLoaded && !state.isLoading) { + await initConfig(); + } + + const backendKey = CONFIG_KEY_MAP[key]; + if (!backendKey) { + throw new Error(`No backend key mapping found for ${key.toString()}`); + } + + await ConfigService.Set(backendKey, value); + state.config[key] = value; + }; // 加载配置 - const initConfig = withErrorHandling( - async (): Promise => { - if (state.isLoading) return; - - state.isLoading = true; + const initConfig = async (): Promise => { + if (state.isLoading) return; + + state.isLoading = true; + try { const appConfig = await ConfigService.GetConfig(); if (appConfig?.editor) { @@ -163,62 +138,60 @@ export const useConfigStore = defineStore('config', () => { } state.configLoaded = true; + } finally { state.isLoading = false; - }, - 'config.loadFailed', - 'config.loadSuccess' - ); + } + }; - // 数值调整工厂函数 - const createNumberAdjuster = (key: NumberConfigKey) => { + // 通用数值调整器工厂 + const createAdjuster = (key: T) => { const limit = CONFIG_LIMITS[key]; + const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max); + return { - increase: () => updateConfig(key, ConfigUtils.clamp(state.config[key] + 1, limit.min, limit.max)), - decrease: () => updateConfig(key, ConfigUtils.clamp(state.config[key] - 1, limit.min, limit.max)), - set: (value: number) => updateConfig(key, ConfigUtils.clamp(value, limit.min, limit.max)), - reset: () => updateConfig(key, limit.default) + increase: () => safeCall(() => updateConfig(key, clamp(state.config[key] + 1)), 'config.saveFailed', 'config.saveSuccess'), + decrease: () => safeCall(() => updateConfig(key, clamp(state.config[key] - 1)), 'config.saveFailed', 'config.saveSuccess'), + set: (value: number) => safeCall(() => updateConfig(key, clamp(value)), 'config.saveFailed', 'config.saveSuccess'), + reset: () => safeCall(() => updateConfig(key, limit.default), 'config.saveFailed', 'config.saveSuccess') }; }; - // 布尔值切换工厂函数 - const createToggler = (key: BooleanConfigKey) => - () => updateConfig(key, !state.config[key]); + // 通用布尔值切换器 + const createToggler = (key: T) => + () => safeCall(() => updateConfig(key, !state.config[key]), 'config.saveFailed', 'config.saveSuccess'); - // 枚举值切换工厂函数 + // 枚举值切换器 const createEnumToggler = (key: 'tabType', values: readonly T[]) => () => { const currentIndex = values.indexOf(state.config[key] as T); const nextIndex = (currentIndex + 1) % values.length; - return updateConfig(key, values[nextIndex]); + return safeCall(() => updateConfig(key, values[nextIndex]), 'config.saveFailed', 'config.saveSuccess'); }; // 重置配置 - const resetConfig = withErrorHandling( - async (): Promise => { - if (state.isLoading) return; - - state.isLoading = true; - await ConfigService.ResetConfig(); - await initConfig(); + const resetConfig = async (): Promise => { + if (state.isLoading) return; + + state.isLoading = true; + try { + await safeCall(() => ConfigService.ResetConfig(), 'config.resetFailed', 'config.resetSuccess'); + await safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess'); + } finally { state.isLoading = false; - }, - 'config.resetFailed', - 'config.resetSuccess' - ); + } + }; // 语言设置方法 - const setLanguage = withErrorHandling( - async (language: LanguageType): Promise => { + const setLanguage = async (language: LanguageType): Promise => { + await safeCall(async () => { await ConfigService.Set(CONFIG_KEY_MAP.language, language); state.config.language = language; // 同步更新前端语言 const frontendLocale = ConfigUtils.backendLanguageToFrontend(language); locale.value = frontendLocale as any; - }, - 'config.languageChangeFailed', - 'config.languageChanged' - ); + }, 'config.languageChangeFailed', 'config.languageChanged'); + }; // 通过前端语言代码设置语言 const setLocale = async (localeCode: SupportedLocaleType): Promise => { @@ -243,19 +216,25 @@ export const useConfigStore = defineStore('config', () => { } }; - // 创建数值调整器 - const fontSize = createNumberAdjuster('fontSize'); - const tabSize = createNumberAdjuster('tabSize'); - const lineHeight = createNumberAdjuster('lineHeight'); + // 创建数值调整器实例 + const adjusters = { + fontSize: createAdjuster('fontSize'), + tabSize: createAdjuster('tabSize'), + lineHeight: createAdjuster('lineHeight') + }; - // 创建切换器 - const toggleTabIndent = createToggler('enableTabIndent'); - const toggleAlwaysOnTop = createToggler('alwaysOnTop'); - const toggleTabType = createEnumToggler('tabType', CONFIG_LIMITS.tabType.values); + // 创建切换器实例 + const togglers = { + tabIndent: createToggler('enableTabIndent'), + alwaysOnTop: createToggler('alwaysOnTop'), + tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values) + }; // 字符串配置设置器 - const setFontFamily = (value: string) => updateConfig('fontFamily', value); - const setFontWeight = (value: string) => updateConfig('fontWeight', value); + const setters = { + fontFamily: (value: string) => safeCall(() => updateConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'), + fontWeight: (value: string) => safeCall(() => updateConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess') + }; return { // 状态 @@ -267,9 +246,9 @@ export const useConfigStore = defineStore('config', () => { ...limits, // 核心方法 - initConfig, + initConfig: () => safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess'), resetConfig, - updateConfig, + updateConfig: (key: keyof EditorConfig, value: any) => safeCall(() => updateConfig(key, value), 'config.saveFailed', 'config.saveSuccess'), // 语言相关方法 setLanguage, @@ -277,29 +256,29 @@ export const useConfigStore = defineStore('config', () => { initializeLanguage, // 字体大小操作 - ...fontSize, - increaseFontSize: fontSize.increase, - decreaseFontSize: fontSize.decrease, - resetFontSize: fontSize.reset, - setFontSize: fontSize.set, + ...adjusters.fontSize, + increaseFontSize: adjusters.fontSize.increase, + decreaseFontSize: adjusters.fontSize.decrease, + resetFontSize: adjusters.fontSize.reset, + setFontSize: adjusters.fontSize.set, // Tab操作 - toggleTabIndent, - ...tabSize, - increaseTabSize: tabSize.increase, - decreaseTabSize: tabSize.decrease, - setTabSize: tabSize.set, - toggleTabType, + toggleTabIndent: togglers.tabIndent, + ...adjusters.tabSize, + increaseTabSize: adjusters.tabSize.increase, + decreaseTabSize: adjusters.tabSize.decrease, + setTabSize: adjusters.tabSize.set, + toggleTabType: togglers.tabType, // 行高操作 - setLineHeight: lineHeight.set, + setLineHeight: adjusters.lineHeight.set, // 窗口操作 - toggleAlwaysOnTop, + toggleAlwaysOnTop: togglers.alwaysOnTop, // 字体操作 - setFontFamily, - setFontWeight, + setFontFamily: setters.fontFamily, + setFontWeight: setters.fontWeight, }; },{ persist: true, diff --git a/frontend/src/stores/documentStore.ts b/frontend/src/stores/documentStore.ts index 17aa3a6..85abef8 100644 --- a/frontend/src/stores/documentStore.ts +++ b/frontend/src/stores/documentStore.ts @@ -2,12 +2,10 @@ import {defineStore} from 'pinia'; import {computed, ref} from 'vue'; import {DocumentService} from '@/../bindings/voidraft/internal/services'; import {Document} from '@/../bindings/voidraft/internal/models/models'; -import {useLogStore} from './logStore'; -import {useI18n} from 'vue-i18n'; +import {useErrorHandler} from '@/utils/errorHandler'; export const useDocumentStore = defineStore('document', () => { - const logStore = useLogStore(); - const { t } = useI18n(); + const {safeCall} = useErrorHandler(); // 状态 const activeDocument = ref(null); @@ -16,86 +14,92 @@ export const useDocumentStore = defineStore('document', () => { const lastSaved = ref(null); // 计算属性 - const documentContent = computed(() => activeDocument.value?.content || ''); - const documentTitle = computed(() => activeDocument.value?.meta?.title || ''); + const documentContent = computed(() => activeDocument.value?.content ?? ''); + const documentTitle = computed(() => activeDocument.value?.meta?.title ?? ''); const hasActiveDocument = computed(() => !!activeDocument.value); const isSaveInProgress = computed(() => isSaving.value); const lastSavedTime = computed(() => lastSaved.value); - // 加载文档 - async function loadDocument() { - if (isLoading.value) return; + // 状态管理包装器 + const withStateGuard = async ( + operation: () => Promise, + stateRef: typeof isLoading | typeof isSaving, + errorMessageKey: string, + successMessageKey?: string + ): Promise => { + if (stateRef.value) return null; - isLoading.value = true; + stateRef.value = true; try { - activeDocument.value = await DocumentService.GetActiveDocument(); - logStore.info(t('document.loadSuccess')); - } catch (err) { - console.error('Failed to load document:', err); - logStore.error(t('document.loadFailed')); - activeDocument.value = null; + return await safeCall(operation, errorMessageKey, successMessageKey); } finally { - isLoading.value = false; + stateRef.value = false; } - } + }; + + // 加载文档 + const loadDocument = () => withStateGuard( + async () => { + const doc = await DocumentService.GetActiveDocument(); + activeDocument.value = doc; + return doc; + }, + isLoading, + 'document.loadFailed', + 'document.loadSuccess' + ); // 保存文档 - async function saveDocument(content: string): Promise { - if (isSaving.value) return false; + const saveDocument = async (content: string): Promise => { + const result = await withStateGuard( + async () => { + await DocumentService.UpdateActiveDocumentContent(content); + lastSaved.value = new Date(); + + // 使用可选链更新本地副本 + if (activeDocument.value) { + activeDocument.value.content = content; + activeDocument.value.meta.lastUpdated = lastSaved.value; + } + + return true; + }, + isSaving, + 'document.saveFailed', + 'document.saveSuccess' + ); - isSaving.value = true; - try { - await DocumentService.UpdateActiveDocumentContent(content); - lastSaved.value = new Date(); - - // 更新本地副本 - if (activeDocument.value) { - activeDocument.value.content = content; - activeDocument.value.meta.lastUpdated = lastSaved.value; - } - - logStore.info(t('document.saveSuccess')); - return true; - } catch (err) { - console.error('Failed to save document:', err); - logStore.error(t('document.saveFailed')); - return false; - } finally { - isSaving.value = false; - } - } + return result ?? false; + }; // 强制保存文档到磁盘 - async function forceSaveDocument(): Promise { - if (isSaving.value) return false; + const forceSaveDocument = async (): Promise => { + const result = await withStateGuard( + async () => { + // 直接调用强制保存API + await DocumentService.ForceSave(); + + lastSaved.value = new Date(); + + // 使用可选链更新时间戳 + if (activeDocument.value) { + activeDocument.value.meta.lastUpdated = lastSaved.value; + } + + return true; + }, + isSaving, + 'document.saveFailed', + 'document.manualSaveSuccess' + ); - isSaving.value = true; - try { - // 直接调用强制保存API - await DocumentService.ForceSave(); - - lastSaved.value = new Date(); - - // 更新时间戳 - if (activeDocument.value) { - activeDocument.value.meta.lastUpdated = lastSaved.value; - } - - logStore.info(t('document.manualSaveSuccess')); - return true; - } catch (err) { - console.error('Failed to force save document:', err); - logStore.error(t('document.saveFailed')); - return false; - } finally { - isSaving.value = false; - } - } + return result ?? false; + }; // 初始化 - async function initialize() { + const initialize = async () => { await loadDocument(); - } + }; return { // 状态 diff --git a/frontend/src/utils/errorHandler.ts b/frontend/src/utils/errorHandler.ts new file mode 100644 index 0000000..c5e4660 --- /dev/null +++ b/frontend/src/utils/errorHandler.ts @@ -0,0 +1,97 @@ +import { useLogStore } from '@/stores/logStore'; +import { useI18n } from 'vue-i18n'; + +/** + * 创建组合式函数,用于在组件中使用错误处理 + */ +export function useErrorHandler() { + const logStore = useLogStore(); + const { t } = useI18n(); + + const handleError = (error: unknown, messageKey: string) => { + logStore.error(t(messageKey)); + }; + + const safeCall = async ( + operation: () => Promise, + errorMessageKey: string, + successMessageKey?: string + ): Promise => { + try { + const result = await operation(); + if (successMessageKey) { + logStore.info(t(successMessageKey)); + } + return result; + } catch (error) { + logStore.error(t(errorMessageKey)); + return null; + } + }; + + /** + * 静默处理错误,不显示用户提示 + */ + const silentCall = async ( + operation: () => Promise + ): Promise => { + try { + return await operation(); + } catch (error) { + // 静默忽略错误 + return null; + } + }; + + /** + * 创建带错误处理的数值调整器 + */ + const createSafeAdjuster = ( + adjustFn: () => Promise, + errorMessageKey: string + ) => () => safeCall(adjustFn, errorMessageKey); + + return { + handleError, + safeCall, + silentCall, + createSafeAdjuster + }; +} + +/** + * 错误处理工具函数集合(不依赖Vue上下文) + */ +export const ErrorUtils = { + /** + * 静默处理错误 + */ + silent: async (operation: () => Promise): Promise => { + try { + return await operation(); + } catch (error) { + return null; + } + }, + + /** + * 带重试的错误处理 + */ + withRetry: async ( + operation: () => Promise, + maxRetries: number = 3, + delay: number = 1000 + ): Promise => { + for (let i = 0; i < maxRetries; i++) { + try { + return await operation(); + } catch (error) { + if (i === maxRetries - 1) { + return null; + } + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + return null; + } +}; \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/autoSaveExtension.ts b/frontend/src/views/editor/extensions/autoSaveExtension.ts index 0c1090e..c40acba 100644 --- a/frontend/src/views/editor/extensions/autoSaveExtension.ts +++ b/frontend/src/views/editor/extensions/autoSaveExtension.ts @@ -49,7 +49,7 @@ export function createAutoSavePlugin(options: AutoSaveOptions = {}) { await DocumentService.UpdateActiveDocumentContent(content); onSave(true); } catch (err) { - console.error('Failed to update document content:', err); + // 静默处理错误,不在控制台打印 onSave(false); } finally { this.isSaving = false; @@ -69,11 +69,11 @@ export function createAutoSavePlugin(options: AutoSaveOptions = {}) { // 标记插件不再活跃 this.isActive = false; - // 直接发送最终内容 + // 静默发送最终内容,忽略错误 const content = this.view.state.doc.toString(); - DocumentService.UpdateActiveDocumentContent(content) - .then(() => console.log('Successfully sent final content on destroy')) - .catch(err => console.error('Failed to send content on destroy:', err)); + DocumentService.UpdateActiveDocumentContent(content).catch(() => { + // 静默忽略销毁时的错误 + }); } } ); diff --git a/frontend/src/views/settings/pages/AppearancePage.vue b/frontend/src/views/settings/pages/AppearancePage.vue index 1909ed8..5b058f4 100644 --- a/frontend/src/views/settings/pages/AppearancePage.vue +++ b/frontend/src/views/settings/pages/AppearancePage.vue @@ -1,5 +1,6 @@