🎨 Optimized error handling
This commit is contained in:
@@ -7,8 +7,8 @@ const configStore = useConfigStore();
|
|||||||
|
|
||||||
// 应用启动时加载配置
|
// 应用启动时加载配置
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await configStore.initializeLanguage();
|
|
||||||
await configStore.initConfig();
|
await configStore.initConfig();
|
||||||
|
await configStore.initializeLanguage();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -13,10 +13,28 @@ let intervalId: ReturnType<typeof setInterval> | null = null;
|
|||||||
const historyData = ref<number[]>([]);
|
const historyData = ref<number[]>([]);
|
||||||
const maxDataPoints = 60;
|
const maxDataPoints = 60;
|
||||||
|
|
||||||
|
// 静默错误处理包装器
|
||||||
|
const withSilentErrorHandling = async <T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
fallback?: T
|
||||||
|
): Promise<T | undefined> => {
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} catch (error) {
|
||||||
|
// 静默处理错误,不输出到控制台
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取内存统计信息
|
// 获取内存统计信息
|
||||||
const fetchMemoryStats = async () => {
|
const fetchMemoryStats = async () => {
|
||||||
try {
|
const stats = await withSilentErrorHandling(() => SystemService.GetMemoryStats());
|
||||||
const stats = await SystemService.GetMemoryStats();
|
|
||||||
|
if (!stats) {
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
memoryStats.value = stats;
|
memoryStats.value = stats;
|
||||||
|
|
||||||
// 格式化内存显示 - 主要显示堆内存使用量
|
// 格式化内存显示 - 主要显示堆内存使用量
|
||||||
@@ -42,10 +60,6 @@ const fetchMemoryStats = async () => {
|
|||||||
drawChart();
|
drawChart();
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch memory stats:', error);
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绘制实时曲线图
|
// 绘制实时曲线图
|
||||||
@@ -199,12 +213,11 @@ const drawChart = () => {
|
|||||||
|
|
||||||
// 手动触发GC
|
// 手动触发GC
|
||||||
const triggerGC = async () => {
|
const triggerGC = async () => {
|
||||||
try {
|
const success = await withSilentErrorHandling(() => SystemService.TriggerGC());
|
||||||
await SystemService.TriggerGC();
|
|
||||||
|
if (success) {
|
||||||
// 延迟一下再获取新的统计信息
|
// 延迟一下再获取新的统计信息
|
||||||
setTimeout(fetchMemoryStats, 100);
|
setTimeout(fetchMemoryStats, 100);
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to trigger GC:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -2,15 +2,16 @@
|
|||||||
import {useEditorStore} from '@/stores/editorStore';
|
import {useEditorStore} from '@/stores/editorStore';
|
||||||
import {useConfigStore, SUPPORTED_LOCALES, type SupportedLocaleType} from '@/stores/configStore';
|
import {useConfigStore, SUPPORTED_LOCALES, type SupportedLocaleType} from '@/stores/configStore';
|
||||||
import {useLogStore} from '@/stores/logStore';
|
import {useLogStore} from '@/stores/logStore';
|
||||||
|
import { useErrorHandler } from '@/utils/errorHandler';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch } from 'vue';
|
||||||
import { ConfigUtils } from '@/utils/configUtils';
|
|
||||||
import * as runtime from '@wailsio/runtime';
|
import * as runtime from '@wailsio/runtime';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const logStore = useLogStore();
|
const logStore = useLogStore();
|
||||||
|
const { safeCall } = useErrorHandler();
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// 语言下拉菜单
|
// 语言下拉菜单
|
||||||
@@ -18,13 +19,10 @@ const showLanguageMenu = ref(false);
|
|||||||
|
|
||||||
// 切换语言
|
// 切换语言
|
||||||
const changeLanguage = async (localeCode: SupportedLocaleType) => {
|
const changeLanguage = async (localeCode: SupportedLocaleType) => {
|
||||||
try {
|
await safeCall(
|
||||||
// 使用 configStore 的语言设置方法
|
() => configStore.setLocale(localeCode),
|
||||||
await configStore.setLocale(localeCode);
|
'config.languageChangeFailed'
|
||||||
} catch (error) {
|
);
|
||||||
console.error('Failed to change language:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
showLanguageMenu.value = false;
|
showLanguageMenu.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,14 +33,11 @@ const toggleLanguageMenu = () => {
|
|||||||
|
|
||||||
// 窗口置顶控制
|
// 窗口置顶控制
|
||||||
const toggleAlwaysOnTop = async () => {
|
const toggleAlwaysOnTop = async () => {
|
||||||
try {
|
await safeCall(async () => {
|
||||||
await configStore.toggleAlwaysOnTop();
|
await configStore.toggleAlwaysOnTop();
|
||||||
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
||||||
await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
||||||
} catch (error) {
|
}, 'config.alwaysOnTopFailed');
|
||||||
console.error('Failed to set window always on top:', error);
|
|
||||||
logStore.error(t('config.alwaysOnTopFailed'));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开设置页面
|
// 打开设置页面
|
||||||
@@ -50,31 +45,31 @@ const openSettings = () => {
|
|||||||
router.push('/settings');
|
router.push('/settings');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 设置窗口置顶状态的通用函数
|
||||||
|
const setWindowAlwaysOnTop = async (alwaysOnTop: boolean) => {
|
||||||
|
await safeCall(
|
||||||
|
() => runtime.Window.SetAlwaysOnTop(alwaysOnTop),
|
||||||
|
'config.alwaysOnTopFailed'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 加载配置
|
// 加载配置
|
||||||
if (!configStore.configLoaded) {
|
if (!configStore.configLoaded) {
|
||||||
await configStore.loadConfig();
|
await configStore.initConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置窗口置顶状态
|
// 设置窗口置顶状态
|
||||||
if (configStore.config.alwaysOnTop) {
|
if (configStore.config.alwaysOnTop) {
|
||||||
try {
|
await setWindowAlwaysOnTop(true);
|
||||||
await runtime.Window.SetAlwaysOnTop(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set window always on top:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听配置加载完成
|
// 监听配置加载完成
|
||||||
watch(() => configStore.configLoaded, (isLoaded) => {
|
watch(() => configStore.configLoaded, (isLoaded) => {
|
||||||
if (isLoaded && configStore.config.alwaysOnTop) {
|
if (isLoaded && configStore.config.alwaysOnTop) {
|
||||||
try {
|
setWindowAlwaysOnTop(true);
|
||||||
runtime.Window.SetAlwaysOnTop(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set window always on top:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@@ -4,8 +4,8 @@ import {
|
|||||||
ConfigService
|
ConfigService
|
||||||
} from '@/../bindings/voidraft/internal/services';
|
} from '@/../bindings/voidraft/internal/services';
|
||||||
import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/internal/models/models';
|
import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/internal/models/models';
|
||||||
import {useLogStore} from './logStore';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useErrorHandler } from '@/utils/errorHandler';
|
||||||
import { ConfigUtils } from '@/utils/configUtils';
|
import { ConfigUtils } from '@/utils/configUtils';
|
||||||
|
|
||||||
// 国际化相关导入
|
// 国际化相关导入
|
||||||
@@ -21,7 +21,7 @@ export const SUPPORTED_LOCALES = [
|
|||||||
code: 'en-US' as SupportedLocaleType,
|
code: 'en-US' as SupportedLocaleType,
|
||||||
name: 'English'
|
name: 'English'
|
||||||
}
|
}
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
// 配置键映射和限制的类型定义
|
// 配置键映射和限制的类型定义
|
||||||
type ConfigKeyMap = {
|
type ConfigKeyMap = {
|
||||||
@@ -93,8 +93,8 @@ const DEFAULT_CONFIG: EditorConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useConfigStore = defineStore('config', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
const logStore = useLogStore();
|
const { locale } = useI18n();
|
||||||
const { t, locale } = useI18n();
|
const { safeCall } = useErrorHandler();
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -105,57 +105,32 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// 计算属性 - 使用工厂函数简化
|
// 计算属性 - 使用工厂函数简化
|
||||||
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
||||||
const limits = {
|
const limits = Object.fromEntries(
|
||||||
fontSize: createLimitComputed('fontSize'),
|
(['fontSize', 'tabSize', 'lineHeight'] as const).map(key => [key, createLimitComputed(key)])
|
||||||
tabSize: createLimitComputed('tabSize'),
|
) as Record<NumberConfigKey, ReturnType<typeof createLimitComputed>>;
|
||||||
lineHeight: createLimitComputed('lineHeight')
|
|
||||||
};
|
|
||||||
|
|
||||||
// 错误处理装饰器
|
|
||||||
const withErrorHandling = <T extends any[], R>(
|
|
||||||
fn: (...args: T) => Promise<R>,
|
|
||||||
errorMsg: string,
|
|
||||||
successMsg?: string
|
|
||||||
) => async (...args: T): Promise<R | null> => {
|
|
||||||
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 updateConfig = withErrorHandling(
|
const updateConfig = async <K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> => {
|
||||||
async <K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> => {
|
// 确保配置已加载
|
||||||
// 确保配置已加载
|
if (!state.configLoaded && !state.isLoading) {
|
||||||
if (!state.configLoaded && !state.isLoading) {
|
await initConfig();
|
||||||
await initConfig();
|
}
|
||||||
}
|
|
||||||
|
const backendKey = CONFIG_KEY_MAP[key];
|
||||||
const backendKey = CONFIG_KEY_MAP[key];
|
if (!backendKey) {
|
||||||
if (!backendKey) {
|
throw new Error(`No backend key mapping found for ${key.toString()}`);
|
||||||
throw new Error(`No backend key mapping found for ${key.toString()}`);
|
}
|
||||||
}
|
|
||||||
|
await ConfigService.Set(backendKey, value);
|
||||||
await ConfigService.Set(backendKey, value);
|
state.config[key] = value;
|
||||||
state.config[key] = value;
|
};
|
||||||
},
|
|
||||||
'config.saveFailed',
|
|
||||||
'config.saveSuccess'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 加载配置
|
// 加载配置
|
||||||
const initConfig = withErrorHandling(
|
const initConfig = async (): Promise<void> => {
|
||||||
async (): Promise<void> => {
|
if (state.isLoading) return;
|
||||||
if (state.isLoading) return;
|
|
||||||
|
state.isLoading = true;
|
||||||
state.isLoading = true;
|
try {
|
||||||
const appConfig = await ConfigService.GetConfig();
|
const appConfig = await ConfigService.GetConfig();
|
||||||
|
|
||||||
if (appConfig?.editor) {
|
if (appConfig?.editor) {
|
||||||
@@ -163,62 +138,60 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.configLoaded = true;
|
state.configLoaded = true;
|
||||||
|
} finally {
|
||||||
state.isLoading = false;
|
state.isLoading = false;
|
||||||
},
|
}
|
||||||
'config.loadFailed',
|
};
|
||||||
'config.loadSuccess'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 数值调整工厂函数
|
// 通用数值调整器工厂
|
||||||
const createNumberAdjuster = (key: NumberConfigKey) => {
|
const createAdjuster = <T extends NumberConfigKey>(key: T) => {
|
||||||
const limit = CONFIG_LIMITS[key];
|
const limit = CONFIG_LIMITS[key];
|
||||||
|
const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
increase: () => updateConfig(key, ConfigUtils.clamp(state.config[key] + 1, limit.min, limit.max)),
|
increase: () => safeCall(() => updateConfig(key, clamp(state.config[key] + 1)), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
decrease: () => updateConfig(key, ConfigUtils.clamp(state.config[key] - 1, limit.min, limit.max)),
|
decrease: () => safeCall(() => updateConfig(key, clamp(state.config[key] - 1)), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
set: (value: number) => updateConfig(key, ConfigUtils.clamp(value, limit.min, limit.max)),
|
set: (value: number) => safeCall(() => updateConfig(key, clamp(value)), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
reset: () => updateConfig(key, limit.default)
|
reset: () => safeCall(() => updateConfig(key, limit.default), 'config.saveFailed', 'config.saveSuccess')
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 布尔值切换工厂函数
|
// 通用布尔值切换器
|
||||||
const createToggler = (key: BooleanConfigKey) =>
|
const createToggler = <T extends BooleanConfigKey>(key: T) =>
|
||||||
() => updateConfig(key, !state.config[key]);
|
() => safeCall(() => updateConfig(key, !state.config[key]), 'config.saveFailed', 'config.saveSuccess');
|
||||||
|
|
||||||
// 枚举值切换工厂函数
|
// 枚举值切换器
|
||||||
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
||||||
() => {
|
() => {
|
||||||
const currentIndex = values.indexOf(state.config[key] as T);
|
const currentIndex = values.indexOf(state.config[key] as T);
|
||||||
const nextIndex = (currentIndex + 1) % values.length;
|
const nextIndex = (currentIndex + 1) % values.length;
|
||||||
return updateConfig(key, values[nextIndex]);
|
return safeCall(() => updateConfig(key, values[nextIndex]), 'config.saveFailed', 'config.saveSuccess');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置配置
|
// 重置配置
|
||||||
const resetConfig = withErrorHandling(
|
const resetConfig = async (): Promise<void> => {
|
||||||
async (): Promise<void> => {
|
if (state.isLoading) return;
|
||||||
if (state.isLoading) return;
|
|
||||||
|
state.isLoading = true;
|
||||||
state.isLoading = true;
|
try {
|
||||||
await ConfigService.ResetConfig();
|
await safeCall(() => ConfigService.ResetConfig(), 'config.resetFailed', 'config.resetSuccess');
|
||||||
await initConfig();
|
await safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess');
|
||||||
|
} finally {
|
||||||
state.isLoading = false;
|
state.isLoading = false;
|
||||||
},
|
}
|
||||||
'config.resetFailed',
|
};
|
||||||
'config.resetSuccess'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 语言设置方法
|
// 语言设置方法
|
||||||
const setLanguage = withErrorHandling(
|
const setLanguage = async (language: LanguageType): Promise<void> => {
|
||||||
async (language: LanguageType): Promise<void> => {
|
await safeCall(async () => {
|
||||||
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
||||||
state.config.language = language;
|
state.config.language = language;
|
||||||
|
|
||||||
// 同步更新前端语言
|
// 同步更新前端语言
|
||||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||||
locale.value = frontendLocale as any;
|
locale.value = frontendLocale as any;
|
||||||
},
|
}, 'config.languageChangeFailed', 'config.languageChanged');
|
||||||
'config.languageChangeFailed',
|
};
|
||||||
'config.languageChanged'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 通过前端语言代码设置语言
|
// 通过前端语言代码设置语言
|
||||||
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
|
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
|
||||||
@@ -243,19 +216,25 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建数值调整器
|
// 创建数值调整器实例
|
||||||
const fontSize = createNumberAdjuster('fontSize');
|
const adjusters = {
|
||||||
const tabSize = createNumberAdjuster('tabSize');
|
fontSize: createAdjuster('fontSize'),
|
||||||
const lineHeight = createNumberAdjuster('lineHeight');
|
tabSize: createAdjuster('tabSize'),
|
||||||
|
lineHeight: createAdjuster('lineHeight')
|
||||||
|
};
|
||||||
|
|
||||||
// 创建切换器
|
// 创建切换器实例
|
||||||
const toggleTabIndent = createToggler('enableTabIndent');
|
const togglers = {
|
||||||
const toggleAlwaysOnTop = createToggler('alwaysOnTop');
|
tabIndent: createToggler('enableTabIndent'),
|
||||||
const toggleTabType = createEnumToggler('tabType', CONFIG_LIMITS.tabType.values);
|
alwaysOnTop: createToggler('alwaysOnTop'),
|
||||||
|
tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values)
|
||||||
|
};
|
||||||
|
|
||||||
// 字符串配置设置器
|
// 字符串配置设置器
|
||||||
const setFontFamily = (value: string) => updateConfig('fontFamily', value);
|
const setters = {
|
||||||
const setFontWeight = (value: string) => updateConfig('fontWeight', value);
|
fontFamily: (value: string) => safeCall(() => updateConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
|
fontWeight: (value: string) => safeCall(() => updateConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess')
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
@@ -267,9 +246,9 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
...limits,
|
...limits,
|
||||||
|
|
||||||
// 核心方法
|
// 核心方法
|
||||||
initConfig,
|
initConfig: () => safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess'),
|
||||||
resetConfig,
|
resetConfig,
|
||||||
updateConfig,
|
updateConfig: (key: keyof EditorConfig, value: any) => safeCall(() => updateConfig(key, value), 'config.saveFailed', 'config.saveSuccess'),
|
||||||
|
|
||||||
// 语言相关方法
|
// 语言相关方法
|
||||||
setLanguage,
|
setLanguage,
|
||||||
@@ -277,29 +256,29 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
initializeLanguage,
|
initializeLanguage,
|
||||||
|
|
||||||
// 字体大小操作
|
// 字体大小操作
|
||||||
...fontSize,
|
...adjusters.fontSize,
|
||||||
increaseFontSize: fontSize.increase,
|
increaseFontSize: adjusters.fontSize.increase,
|
||||||
decreaseFontSize: fontSize.decrease,
|
decreaseFontSize: adjusters.fontSize.decrease,
|
||||||
resetFontSize: fontSize.reset,
|
resetFontSize: adjusters.fontSize.reset,
|
||||||
setFontSize: fontSize.set,
|
setFontSize: adjusters.fontSize.set,
|
||||||
|
|
||||||
// Tab操作
|
// Tab操作
|
||||||
toggleTabIndent,
|
toggleTabIndent: togglers.tabIndent,
|
||||||
...tabSize,
|
...adjusters.tabSize,
|
||||||
increaseTabSize: tabSize.increase,
|
increaseTabSize: adjusters.tabSize.increase,
|
||||||
decreaseTabSize: tabSize.decrease,
|
decreaseTabSize: adjusters.tabSize.decrease,
|
||||||
setTabSize: tabSize.set,
|
setTabSize: adjusters.tabSize.set,
|
||||||
toggleTabType,
|
toggleTabType: togglers.tabType,
|
||||||
|
|
||||||
// 行高操作
|
// 行高操作
|
||||||
setLineHeight: lineHeight.set,
|
setLineHeight: adjusters.lineHeight.set,
|
||||||
|
|
||||||
// 窗口操作
|
// 窗口操作
|
||||||
toggleAlwaysOnTop,
|
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
||||||
|
|
||||||
// 字体操作
|
// 字体操作
|
||||||
setFontFamily,
|
setFontFamily: setters.fontFamily,
|
||||||
setFontWeight,
|
setFontWeight: setters.fontWeight,
|
||||||
};
|
};
|
||||||
},{
|
},{
|
||||||
persist: true,
|
persist: true,
|
||||||
|
@@ -2,12 +2,10 @@ import {defineStore} from 'pinia';
|
|||||||
import {computed, ref} from 'vue';
|
import {computed, ref} from 'vue';
|
||||||
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
import {DocumentService} from '@/../bindings/voidraft/internal/services';
|
||||||
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
import {Document} from '@/../bindings/voidraft/internal/models/models';
|
||||||
import {useLogStore} from './logStore';
|
import {useErrorHandler} from '@/utils/errorHandler';
|
||||||
import {useI18n} from 'vue-i18n';
|
|
||||||
|
|
||||||
export const useDocumentStore = defineStore('document', () => {
|
export const useDocumentStore = defineStore('document', () => {
|
||||||
const logStore = useLogStore();
|
const {safeCall} = useErrorHandler();
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const activeDocument = ref<Document | null>(null);
|
const activeDocument = ref<Document | null>(null);
|
||||||
@@ -16,86 +14,92 @@ export const useDocumentStore = defineStore('document', () => {
|
|||||||
const lastSaved = ref<Date | null>(null);
|
const lastSaved = ref<Date | null>(null);
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const documentContent = computed(() => activeDocument.value?.content || '');
|
const documentContent = computed(() => activeDocument.value?.content ?? '');
|
||||||
const documentTitle = computed(() => activeDocument.value?.meta?.title || '');
|
const documentTitle = computed(() => activeDocument.value?.meta?.title ?? '');
|
||||||
const hasActiveDocument = computed(() => !!activeDocument.value);
|
const hasActiveDocument = computed(() => !!activeDocument.value);
|
||||||
const isSaveInProgress = computed(() => isSaving.value);
|
const isSaveInProgress = computed(() => isSaving.value);
|
||||||
const lastSavedTime = computed(() => lastSaved.value);
|
const lastSavedTime = computed(() => lastSaved.value);
|
||||||
|
|
||||||
// 加载文档
|
// 状态管理包装器
|
||||||
async function loadDocument() {
|
const withStateGuard = async <T>(
|
||||||
if (isLoading.value) return;
|
operation: () => Promise<T>,
|
||||||
|
stateRef: typeof isLoading | typeof isSaving,
|
||||||
|
errorMessageKey: string,
|
||||||
|
successMessageKey?: string
|
||||||
|
): Promise<T | null> => {
|
||||||
|
if (stateRef.value) return null;
|
||||||
|
|
||||||
isLoading.value = true;
|
stateRef.value = true;
|
||||||
try {
|
try {
|
||||||
activeDocument.value = await DocumentService.GetActiveDocument();
|
return await safeCall(operation, errorMessageKey, successMessageKey);
|
||||||
logStore.info(t('document.loadSuccess'));
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load document:', err);
|
|
||||||
logStore.error(t('document.loadFailed'));
|
|
||||||
activeDocument.value = null;
|
|
||||||
} finally {
|
} 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<boolean> {
|
const saveDocument = async (content: string): Promise<boolean> => {
|
||||||
if (isSaving.value) return false;
|
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;
|
return result ?? false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 强制保存文档到磁盘
|
// 强制保存文档到磁盘
|
||||||
async function forceSaveDocument(): Promise<boolean> {
|
const forceSaveDocument = async (): Promise<boolean> => {
|
||||||
if (isSaving.value) return false;
|
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;
|
return result ?? false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
async function initialize() {
|
const initialize = async () => {
|
||||||
await loadDocument();
|
await loadDocument();
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
|
97
frontend/src/utils/errorHandler.ts
Normal file
97
frontend/src/utils/errorHandler.ts
Normal file
@@ -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 <T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
errorMessageKey: string,
|
||||||
|
successMessageKey?: string
|
||||||
|
): Promise<T | null> => {
|
||||||
|
try {
|
||||||
|
const result = await operation();
|
||||||
|
if (successMessageKey) {
|
||||||
|
logStore.info(t(successMessageKey));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logStore.error(t(errorMessageKey));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静默处理错误,不显示用户提示
|
||||||
|
*/
|
||||||
|
const silentCall = async <T>(
|
||||||
|
operation: () => Promise<T>
|
||||||
|
): Promise<T | null> => {
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} catch (error) {
|
||||||
|
// 静默忽略错误
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建带错误处理的数值调整器
|
||||||
|
*/
|
||||||
|
const createSafeAdjuster = (
|
||||||
|
adjustFn: () => Promise<void>,
|
||||||
|
errorMessageKey: string
|
||||||
|
) => () => safeCall(adjustFn, errorMessageKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleError,
|
||||||
|
safeCall,
|
||||||
|
silentCall,
|
||||||
|
createSafeAdjuster
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误处理工具函数集合(不依赖Vue上下文)
|
||||||
|
*/
|
||||||
|
export const ErrorUtils = {
|
||||||
|
/**
|
||||||
|
* 静默处理错误
|
||||||
|
*/
|
||||||
|
silent: async <T>(operation: () => Promise<T>): Promise<T | null> => {
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带重试的错误处理
|
||||||
|
*/
|
||||||
|
withRetry: async <T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
maxRetries: number = 3,
|
||||||
|
delay: number = 1000
|
||||||
|
): Promise<T | null> => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
@@ -49,7 +49,7 @@ export function createAutoSavePlugin(options: AutoSaveOptions = {}) {
|
|||||||
await DocumentService.UpdateActiveDocumentContent(content);
|
await DocumentService.UpdateActiveDocumentContent(content);
|
||||||
onSave(true);
|
onSave(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to update document content:', err);
|
// 静默处理错误,不在控制台打印
|
||||||
onSave(false);
|
onSave(false);
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
@@ -69,11 +69,11 @@ export function createAutoSavePlugin(options: AutoSaveOptions = {}) {
|
|||||||
// 标记插件不再活跃
|
// 标记插件不再活跃
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
|
|
||||||
// 直接发送最终内容
|
// 静默发送最终内容,忽略错误
|
||||||
const content = this.view.state.doc.toString();
|
const content = this.view.state.doc.toString();
|
||||||
DocumentService.UpdateActiveDocumentContent(content)
|
DocumentService.UpdateActiveDocumentContent(content).catch(() => {
|
||||||
.then(() => console.log('Successfully sent final content on destroy'))
|
// 静默忽略销毁时的错误
|
||||||
.catch(err => console.error('Failed to send content on destroy:', err));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
|
import { useErrorHandler } from '@/utils/errorHandler';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
@@ -8,6 +9,7 @@ import { LanguageType } from '../../../../bindings/voidraft/internal/models/mode
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
const { safeCall } = useErrorHandler();
|
||||||
|
|
||||||
// 语言选项
|
// 语言选项
|
||||||
const languageOptions = [
|
const languageOptions = [
|
||||||
@@ -20,12 +22,10 @@ const updateLanguage = async (event: Event) => {
|
|||||||
const select = event.target as HTMLSelectElement;
|
const select = event.target as HTMLSelectElement;
|
||||||
const selectedLanguage = select.value as LanguageType;
|
const selectedLanguage = select.value as LanguageType;
|
||||||
|
|
||||||
try {
|
await safeCall(
|
||||||
// 使用 configStore 的语言设置方法
|
() => configStore.setLanguage(selectedLanguage),
|
||||||
await configStore.setLanguage(selectedLanguage);
|
'config.languageChangeFailed'
|
||||||
} catch (error) {
|
);
|
||||||
console.error('Failed to update language:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 主题选择(未实际实现,仅界面展示)
|
// 主题选择(未实际实现,仅界面展示)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useConfigStore } from '@/stores/configStore';
|
import { useConfigStore } from '@/stores/configStore';
|
||||||
import { FONT_OPTIONS } from '@/stores/configStore';
|
import { FONT_OPTIONS } from '@/stores/configStore';
|
||||||
|
import { useErrorHandler } from '@/utils/errorHandler';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import SettingSection from '../components/SettingSection.vue';
|
import SettingSection from '../components/SettingSection.vue';
|
||||||
@@ -10,11 +11,12 @@ import { TabType } from '../../../../bindings/voidraft/internal/models/models';
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
const { safeCall } = useErrorHandler();
|
||||||
|
|
||||||
// 确保配置已加载
|
// 确保配置已加载
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!configStore.configLoaded) {
|
if (!configStore.configLoaded) {
|
||||||
await configStore.loadConfig();
|
await configStore.initConfig();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,11 +29,10 @@ const handleFontFamilyChange = async (event: Event) => {
|
|||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
const fontFamily = target.value;
|
const fontFamily = target.value;
|
||||||
if (fontFamily) {
|
if (fontFamily) {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.setFontFamily(fontFamily);
|
() => configStore.setFontFamily(fontFamily),
|
||||||
} catch (error) {
|
'config.fontFamilyUpdateFailed'
|
||||||
console.error('Failed to set font family:', error);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,47 +52,42 @@ const fontWeightOptions = [
|
|||||||
// 字体粗细选择
|
// 字体粗细选择
|
||||||
const handleFontWeightChange = async (event: Event) => {
|
const handleFontWeightChange = async (event: Event) => {
|
||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.setFontWeight(target.value);
|
() => configStore.setFontWeight(target.value),
|
||||||
} catch (error) {
|
'config.fontWeightUpdateFailed'
|
||||||
console.error('Failed to set font weight:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 行高控制
|
// 行高控制
|
||||||
const increaseLineHeight = async () => {
|
const increaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.min(3.0, configStore.config.lineHeight + 0.1);
|
const newLineHeight = Math.min(3.0, configStore.config.lineHeight + 0.1);
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
() => configStore.setLineHeight(Math.round(newLineHeight * 10) / 10),
|
||||||
} catch (error) {
|
'config.lineHeightIncreaseFailed'
|
||||||
console.error('Failed to increase line height:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseLineHeight = async () => {
|
const decreaseLineHeight = async () => {
|
||||||
const newLineHeight = Math.max(1.0, configStore.config.lineHeight - 0.1);
|
const newLineHeight = Math.max(1.0, configStore.config.lineHeight - 0.1);
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
() => configStore.setLineHeight(Math.round(newLineHeight * 10) / 10),
|
||||||
} catch (error) {
|
'config.lineHeightDecreaseFailed'
|
||||||
console.error('Failed to decrease line height:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字体大小控制
|
// 字体大小控制
|
||||||
const increaseFontSize = async () => {
|
const increaseFontSize = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.increaseFontSize();
|
() => configStore.increaseFontSize(),
|
||||||
} catch (error) {
|
'config.fontSizeIncreaseFailed'
|
||||||
console.error('Failed to increase font size:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseFontSize = async () => {
|
const decreaseFontSize = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.decreaseFontSize();
|
() => configStore.decreaseFontSize(),
|
||||||
} catch (error) {
|
'config.fontSizeDecreaseFailed'
|
||||||
console.error('Failed to decrease font size:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab类型切换
|
// Tab类型切换
|
||||||
@@ -103,36 +99,32 @@ const tabTypeText = computed(() => {
|
|||||||
|
|
||||||
// Tab大小增减
|
// Tab大小增减
|
||||||
const increaseTabSize = async () => {
|
const increaseTabSize = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.increaseTabSize();
|
() => configStore.increaseTabSize(),
|
||||||
} catch (error) {
|
'config.tabSizeIncreaseFailed'
|
||||||
console.error('Failed to increase tab size:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decreaseTabSize = async () => {
|
const decreaseTabSize = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.decreaseTabSize();
|
() => configStore.decreaseTabSize(),
|
||||||
} catch (error) {
|
'config.tabSizeDecreaseFailed'
|
||||||
console.error('Failed to decrease tab size:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab相关操作
|
// Tab相关操作
|
||||||
const handleToggleTabIndent = async () => {
|
const handleToggleTabIndent = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.toggleTabIndent();
|
() => configStore.toggleTabIndent(),
|
||||||
} catch (error) {
|
'config.tabIndentToggleFailed'
|
||||||
console.error('Failed to toggle tab indent:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleTabType = async () => {
|
const handleToggleTabType = async () => {
|
||||||
try {
|
await safeCall(
|
||||||
await configStore.toggleTabType();
|
() => configStore.toggleTabType(),
|
||||||
} catch (error) {
|
'config.tabTypeToggleFailed'
|
||||||
console.error('Failed to toggle tab type:', error);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user