♻️ Refactor configuration service

This commit is contained in:
2025-05-28 00:37:18 +08:00
parent 72a222f932
commit 5f102edcf7
18 changed files with 721 additions and 657 deletions

View File

@@ -155,14 +155,30 @@ export class Document {
*/
export class DocumentConfig {
/**
* 详细保存选项
* 自动保存延迟(毫秒)- 内容变更后多久自动保存
*/
"saveOptions": SaveOptions;
"autoSaveDelay": number;
/**
* 变更字符阈值,超过此阈值立即触发保存
*/
"changeThreshold": number;
/**
* 最小保存间隔(毫秒)- 两次保存之间的最小时间间隔避免频繁IO
*/
"minSaveInterval": number;
/** Creates a new DocumentConfig instance. */
constructor($$source: Partial<DocumentConfig> = {}) {
if (!("saveOptions" in $$source)) {
this["saveOptions"] = (new SaveOptions());
if (!("autoSaveDelay" in $$source)) {
this["autoSaveDelay"] = 0;
}
if (!("changeThreshold" in $$source)) {
this["changeThreshold"] = 0;
}
if (!("minSaveInterval" in $$source)) {
this["minSaveInterval"] = 0;
}
Object.assign(this, $$source);
@@ -172,11 +188,7 @@ export class DocumentConfig {
* Creates a new DocumentConfig instance from a string or object.
*/
static createFrom($$source: any = {}): DocumentConfig {
const $$createField0_0 = $$createType5;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("saveOptions" in $$parsedSource) {
$$parsedSource["saveOptions"] = $$createField0_0($$parsedSource["saveOptions"]);
}
return new DocumentConfig($$parsedSource as Partial<DocumentConfig>);
}
}
@@ -323,11 +335,6 @@ export enum LanguageType {
* PathsConfig 路径配置集合
*/
export class PathsConfig {
/**
* 日志文件路径
*/
"logPath": string;
/**
* 数据存储路径
*/
@@ -335,9 +342,6 @@ export class PathsConfig {
/** Creates a new PathsConfig instance. */
constructor($$source: Partial<PathsConfig> = {}) {
if (!("logPath" in $$source)) {
this["logPath"] = "";
}
if (!("dataPath" in $$source)) {
this["dataPath"] = "";
}
@@ -354,49 +358,6 @@ export class PathsConfig {
}
}
/**
* SaveOptions 保存选项
*/
export class SaveOptions {
/**
* 自动保存延迟(毫秒)- 内容变更后多久自动保存
*/
"autoSaveDelay": number;
/**
* 变更字符阈值,超过此阈值立即触发保存
*/
"changeThreshold": number;
/**
* 最小保存间隔(毫秒)- 两次保存之间的最小时间间隔避免频繁IO
*/
"minSaveInterval": number;
/** Creates a new SaveOptions instance. */
constructor($$source: Partial<SaveOptions> = {}) {
if (!("autoSaveDelay" in $$source)) {
this["autoSaveDelay"] = 0;
}
if (!("changeThreshold" in $$source)) {
this["changeThreshold"] = 0;
}
if (!("minSaveInterval" in $$source)) {
this["minSaveInterval"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new SaveOptions instance from a string or object.
*/
static createFrom($$source: any = {}): SaveOptions {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SaveOptions($$parsedSource as Partial<SaveOptions>);
}
}
/**
* TabType 定义了制表符类型
*/
@@ -423,4 +384,3 @@ const $$createType1 = DocumentConfig.createFrom;
const $$createType2 = PathsConfig.createFrom;
const $$createType3 = ConfigMetadata.createFrom;
const $$createType4 = DocumentMeta.createFrom;
const $$createType5 = SaveOptions.createFrom;

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* ConfigService 提供配置管理功能
* ConfigService 提供基于 Viper 的配置管理功能
* @module
*/
@@ -14,6 +14,14 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* Get 获取配置项
*/
export function Get(key: string): Promise<any> & { cancel(): void } {
let $resultPromise = $Call.ByID(807201772, key) as any;
return $resultPromise;
}
/**
* GetConfig 获取完整应用配置
*/
@@ -26,50 +34,6 @@ export function GetConfig(): Promise<models$0.AppConfig | null> & { cancel(): vo
return $typingPromise;
}
/**
* GetEditorConfig 获取编辑器配置
*/
export function GetEditorConfig(): Promise<models$0.EditorConfig> & { cancel(): void } {
let $resultPromise = $Call.ByID(3648153351) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType2($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetLanguage 获取当前语言设置
*/
export function GetLanguage(): Promise<models$0.LanguageType> & { cancel(): void } {
let $resultPromise = $Call.ByID(3409375894) as any;
return $resultPromise;
}
/**
* GetMetadata 获取配置元数据
*/
export function GetMetadata(): Promise<models$0.ConfigMetadata> & { cancel(): void } {
let $resultPromise = $Call.ByID(3276720617) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType3($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetPaths 获取路径配置
*/
export function GetPaths(): Promise<models$0.PathsConfig> & { cancel(): void } {
let $resultPromise = $Call.ByID(1096257096) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType4($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* ResetConfig 重置为默认配置
*/
@@ -79,48 +43,13 @@ export function ResetConfig(): Promise<void> & { cancel(): void } {
}
/**
* SaveConfig 保存完整应用配置
* Set 设置配置
*/
export function SaveConfig(config: models$0.AppConfig | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(616684383, config) as any;
return $resultPromise;
}
/**
* SetLanguage 设置语言
*/
export function SetLanguage(language: models$0.LanguageType): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(814725002, language) as any;
return $resultPromise;
}
/**
* UpdateEditorConfig 更新编辑器配置
*/
export function UpdateEditorConfig(editorConfig: models$0.EditorConfig): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1237949666, editorConfig) as any;
return $resultPromise;
}
/**
* UpdateMetadata 更新配置元数据
*/
export function UpdateMetadata(metadata: models$0.ConfigMetadata): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3353893284, metadata) as any;
return $resultPromise;
}
/**
* UpdatePaths 更新路径配置
*/
export function UpdatePaths(paths: models$0.PathsConfig): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3650847675, paths) as any;
export function Set(key: string, value: any): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2921955968, key, value) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.AppConfig.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = models$0.EditorConfig.createFrom;
const $$createType3 = models$0.ConfigMetadata.createFrom;
const $$createType4 = models$0.PathsConfig.createFrom;

View File

@@ -1,12 +1,5 @@
<script setup lang="ts">
import {onMounted} from 'vue';
import {useConfigStore} from "@/stores/configStore";
const configStore = useConfigStore();
onMounted(async () => {
await configStore.loadConfigFromBackend();
})
</script>
<template>

View File

@@ -5,7 +5,10 @@ import {useLogStore} from '@/stores/logStore';
import { useI18n } from 'vue-i18n';
import { ref, onMounted, watch } from 'vue';
import {SUPPORTED_LOCALES, setLocale, SupportedLocaleType} from '@/i18n';
import {LanguageType} from '@/../bindings/voidraft/internal/models/models';
import { ConfigUtils } from '@/utils/configUtils';
import * as runtime from '@wailsio/runtime';
const editorStore = useEditorStore();
const configStore = useConfigStore();
const logStore = useLogStore();
@@ -15,8 +18,19 @@ const { t, locale } = useI18n();
const showLanguageMenu = ref(false);
// 切换语言
const changeLanguage = (localeCode: SupportedLocaleType) => {
setLocale(localeCode);
const changeLanguage = async (localeCode: SupportedLocaleType) => {
// 使用工具类转换语言类型
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
try {
// 设置后端语言配置
await configStore.setLanguage(backendLanguage);
// 设置前端语言
setLocale(localeCode);
} catch (error) {
console.error('Failed to change language:', error);
}
showLanguageMenu.value = false;
};
@@ -26,10 +40,10 @@ const toggleLanguageMenu = () => {
};
// 窗口置顶控制
const toggleAlwaysOnTop = () => {
configStore.toggleAlwaysOnTop();
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
const toggleAlwaysOnTop = async () => {
try {
await configStore.toggleAlwaysOnTop();
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
} catch (error) {
console.error('Failed to set window always on top:', error);
@@ -51,18 +65,30 @@ const openSettingsWindow = () => {
}
};
// 在组件挂载时根据配置设置窗口置顶状态
onMounted(() => {
if (configStore.configLoaded && configStore.config.alwaysOnTop) {
// 初始化配置
onMounted(async () => {
// 加载配置
if (!configStore.configLoaded) {
await configStore.loadConfig();
}
// 设置窗口置顶状态
if (configStore.config.alwaysOnTop) {
try {
runtime.Window.SetAlwaysOnTop(true);
} catch (error) {
console.error('Failed to set window always on top:', error);
}
}
// 同步前端语言设置
const frontendLocale = ConfigUtils.backendLanguageToFrontend(configStore.config.language);
if (locale.value !== frontendLocale) {
setLocale(frontendLocale);
}
});
// 监听配置加载完成,加载后检查并设置窗口置顶状态
// 监听配置加载完成
watch(() => configStore.configLoaded, (isLoaded) => {
if (isLoaded && configStore.config.alwaysOnTop) {
try {
@@ -92,21 +118,21 @@ watch(() => configStore.configLoaded, (isLoaded) => {
</span>
</div>
<div class="actions">
<span class="font-size" :title="t('toolbar.fontSizeTooltip')" @click="configStore.resetFontSize">
<span class="font-size" :title="t('toolbar.fontSizeTooltip')" @click="() => configStore.resetFontSize()">
{{ configStore.config.fontSize }}px
</span>
<span class="tab-settings">
<label :title="t('toolbar.tabLabel')" class="tab-toggle">
<input type="checkbox" :checked="configStore.config.enableTabIndent" @change="configStore.toggleTabIndent"/>
<input type="checkbox" :checked="configStore.config.enableTabIndent" @change="() => configStore.toggleTabIndent()"/>
<span>{{ t('toolbar.tabLabel') }}</span>
</label>
<span class="tab-type" :title="t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab'))" @click="configStore.toggleTabType">
<span class="tab-type" :title="t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab'))" @click="() => configStore.toggleTabType()">
{{ t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab')) }}
</span>
<span class="tab-size" title="Tab大小" v-if="configStore.config.tabType === 'spaces'">
<button class="tab-btn" @click="configStore.decreaseTabSize" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
<span>{{ configStore.config.tabSize }}</span>
<button class="tab-btn" @click="configStore.increaseTabSize" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
</span>
</span>

View File

@@ -2,7 +2,7 @@ import {createI18n} from 'vue-i18n';
import messages from './locales';
import { ConfigService } from '@/../bindings/voidraft/internal/services';
import { LanguageType } from '@/../bindings/voidraft/internal/models';
import { useConfigStore } from '@/stores/configStore';
import { ConfigUtils } from '@/utils/configUtils';
// 定义支持的语言类型
export type SupportedLocaleType = 'zh-CN' | 'en-US';
@@ -40,40 +40,41 @@ const i18n = createI18n({
messages
});
// 立即从后端获取语言设置
ConfigService.GetLanguage().then(lang => {
if (lang) {
i18n.global.locale = lang as any;
// 从后端获取语言设置
const initializeLanguage = async () => {
try {
// 使用新的配置服务方法获取语言设置
const language = await ConfigService.Get('editor.language') as LanguageType;
if (language) {
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
i18n.global.locale = frontendLocale as any;
}
} catch (error) {
console.error('Failed to get language from backend:', error);
// 如果获取失败,使用浏览器语言作为后备
const browserLang = getBrowserLanguage();
i18n.global.locale = browserLang as any;
}
}).catch(error => {
console.error('Failed to get language from backend:', error);
// 如果获取失败,使用浏览器语言作为后备
const browserLang = getBrowserLanguage();
i18n.global.locale = browserLang as any;
});
};
// 立即初始化语言
initializeLanguage();
// 切换语言的方法
export const setLocale = (locale: SupportedLocaleType) => {
export const setLocale = async (locale: SupportedLocaleType) => {
if (SUPPORTED_LOCALES.some(l => l.code === locale)) {
// 更新后端配置
ConfigService.SetLanguage(locale as LanguageType)
.then(() => {
i18n.global.locale = locale;
document.documentElement.setAttribute('lang', locale);
try {
// 转换为后端语言类型
const backendLanguage = ConfigUtils.frontendLanguageToBackend(locale);
// 同时更新configStore中的语言设置
try {
const configStore = useConfigStore();
if (configStore.configLoaded) {
configStore.config.language = locale as LanguageType;
}
} catch (error) {
console.error('Failed to update configStore language:', error);
}
})
.catch(error => {
console.error('Failed to set language:', error);
});
// 使用新的配置服务方法设置语言
await ConfigService.Set('editor.language', backendLanguage);
// 更新前端语言
i18n.global.locale = locale;
} catch (error) {
console.error('Failed to set language:', error);
}
}
};

View File

@@ -26,7 +26,9 @@ export default {
fontSizeFixed: 'Font size ({value}) has been corrected to {fixed}',
tabSizeFixed: 'Tab size ({value}) has been corrected to {fixed}',
tabTypeFixed: 'Tab type ({value}) is invalid, corrected to spaces',
alwaysOnTopFailed: 'Failed to set window always on top'
alwaysOnTopFailed: 'Failed to set window always on top',
languageChanged: 'Language setting updated',
languageChangeFailed: 'Failed to update language setting'
},
languages: {
'zh-CN': '简体中文',
@@ -44,4 +46,37 @@ export default {
saveFailed: 'Failed to update save settings'
}
},
settings: {
title: 'Settings',
general: 'General',
editing: 'Editor',
appearance: 'Appearance',
keyBindings: 'Key Bindings',
updates: 'Updates',
comingSoon: 'Coming Soon...',
save: 'Save',
reset: 'Reset',
globalHotkey: 'Global Keyboard Shortcuts',
enableGlobalHotkey: 'Enable Global Hotkeys',
window: 'Window/Application',
showInSystemTray: 'Show in System Tray',
alwaysOnTop: 'Always on Top',
bufferFiles: 'Buffer Files Path',
useCustomLocation: 'Use custom location for buffer files',
selectDirectory: 'Select Directory',
fontSize: 'Font Size',
fontSizeDescription: 'Editor font size',
tabSettings: 'Tab Settings',
tabSize: 'Tab Size',
tabType: 'Tab Type',
spaces: 'Spaces',
tabs: 'Tabs',
enableTabIndent: 'Enable Tab Indent',
language: 'Interface Language',
restartRequired: '(Restart required)',
saveOptions: 'Save Options',
autoSaveDelay: 'Auto Save Delay (ms)',
changeThreshold: 'Change Threshold',
minSaveInterval: 'Min Save Interval (ms)'
}
};

View File

@@ -26,7 +26,9 @@ export default {
fontSizeFixed: '字体大小值({value})已被修正为{fixed}',
tabSizeFixed: 'Tab大小值({value})已被修正为{fixed}',
tabTypeFixed: 'Tab类型({value})不合法,已修正为空格',
alwaysOnTopFailed: '无法设置窗口置顶状态'
alwaysOnTopFailed: '无法设置窗口置顶状态',
languageChanged: '语言设置已更新',
languageChangeFailed: '语言设置更新失败'
},
languages: {
'zh-CN': '简体中文',

View File

@@ -16,6 +16,7 @@ const routes: RouteRecordRaw[] = [
{
path: '/settings',
name: 'Settings',
redirect: '/settings/general',
component: Settings,
children: [
{

View File

@@ -18,11 +18,6 @@ const navItems = [
{ id: 'updates', icon: '🔄', route: '/settings/updates' }
];
// 默认导航到常规设置
if (route.path === '/settings') {
router.replace('/settings/general');
}
const activeNavItem = ref(route.path.split('/').pop() || 'general');
// 处理导航点击
@@ -33,7 +28,7 @@ const handleNavClick = (item: typeof navItems[0]) => {
// 重置设置
const resetSettings = async () => {
await configStore.resetToDefaults();
await configStore.resetConfig();
};
</script>

View File

@@ -5,20 +5,33 @@ import { ref } from 'vue';
import SettingSection from '../components/SettingSection.vue';
import SettingItem from '../components/SettingItem.vue';
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
import { setLocale, SupportedLocaleType } from '@/i18n';
import { ConfigUtils } from '@/utils/configUtils';
const { t } = useI18n();
const configStore = useConfigStore();
// 语言选项
const languageOptions = [
{ value: 'zh-CN', label: t('languages.zh-CN') },
{ value: 'en-US', label: t('languages.en-US') },
{ value: LanguageType.LangZhCN, label: t('languages.zh-CN') },
{ value: LanguageType.LangEnUS, label: t('languages.en-US') },
];
// 更新语言设置
const updateLanguage = (event: Event) => {
const updateLanguage = async (event: Event) => {
const select = event.target as HTMLSelectElement;
configStore.updateConfig('language', select.value as LanguageType);
const selectedLanguage = select.value as LanguageType;
try {
// 设置后端语言配置
await configStore.setLanguage(selectedLanguage);
// 同步前端语言设置
const frontendLocale = ConfigUtils.backendLanguageToFrontend(selectedLanguage);
setLocale(frontendLocale);
} catch (error) {
console.error('Failed to update language:', error);
}
};
// 主题选择(未实际实现,仅界面展示)

View File

@@ -1,29 +1,29 @@
import {defineStore} from 'pinia';
import {ref, watch} from 'vue';
import {useDebounceFn} from '@vueuse/core';
import {ref, computed} from 'vue';
import {
ConfigService
} from '@/../bindings/voidraft/internal/services';
import {EditorConfig, TabType} 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 { ConfigUtils } from '@/utils/configUtils';
// 字体大小范围
const MIN_FONT_SIZE = 12;
const MAX_FONT_SIZE = 28;
const DEFAULT_FONT_SIZE = 13;
// 配置键映射 - 前端字段到后端配置键的映射
const CONFIG_KEY_MAP = {
fontSize: 'editor.font_size',
enableTabIndent: 'editor.enable_tab_indent',
tabSize: 'editor.tab_size',
tabType: 'editor.tab_type',
language: 'editor.language',
alwaysOnTop: 'editor.always_on_top'
} as const;
// Tab设置
const DEFAULT_TAB_SIZE = 4;
const MIN_TAB_SIZE = 2;
const MAX_TAB_SIZE = 8;
// 配置项限制定义
// 配置限制
const CONFIG_LIMITS = {
fontSize: { min: MIN_FONT_SIZE, max: MAX_FONT_SIZE, default: DEFAULT_FONT_SIZE },
tabSize: { min: MIN_TAB_SIZE, max: MAX_TAB_SIZE, default: DEFAULT_TAB_SIZE },
tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces },
};
fontSize: { min: 12, max: 28, default: 13 },
tabSize: { min: 2, max: 8, default: 4 },
tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces }
} as const;
export const useConfigStore = defineStore('config', () => {
// 获取日志store
@@ -32,202 +32,163 @@ export const useConfigStore = defineStore('config', () => {
// 配置状态
const config = ref<EditorConfig>(new EditorConfig({
fontSize: DEFAULT_FONT_SIZE,
fontSize: CONFIG_LIMITS.fontSize.default,
enableTabIndent: true,
tabSize: DEFAULT_TAB_SIZE,
tabType: TabType.TabTypeSpaces
tabSize: CONFIG_LIMITS.tabSize.default,
tabType: CONFIG_LIMITS.tabType.default,
language: LanguageType.LangZhCN,
alwaysOnTop: false
}));
// 配置是否已从后端加载
// 加载状态
const isLoading = ref(false);
const configLoaded = ref(false);
// 配置是否正在从后端加载
const isLoading = ref(false);
// 计算属性
const MIN_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.min);
const MAX_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.max);
const MIN_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.min);
const MAX_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.max);
// 从后端加载配置
async function loadConfigFromBackend() {
async function loadConfig(): Promise<void> {
if (isLoading.value) return;
isLoading.value = true;
try {
const loadedConfig = await ConfigService.GetEditorConfig();
const appConfig = await ConfigService.GetConfig();
// 深拷贝配置以避免直接引用
config.value = new EditorConfig(JSON.parse(JSON.stringify(loadedConfig)));
if (appConfig?.editor) {
Object.assign(config.value, appConfig.editor);
}
// 验证并纠正配置,不自动保存
validateAndFixConfig(false);
// 等待下一个事件循环确保watch不会在初始加载时触发
setTimeout(() => {
configLoaded.value = true;
isLoading.value = false;
logStore.info(t('config.loadSuccess'));
}, 0);
configLoaded.value = true;
logStore.info(t('config.loadSuccess'));
} catch (error) {
console.error('Failed to load configuration:', error);
logStore.error(t('config.loadFailed'));
} finally {
isLoading.value = false;
}
}
// 验证配置是否在合理范围内,并修正无效值
function validateAndFixConfig(autoSave = true) {
let hasChanges = false;
// 验证字体大小
if (config.value.fontSize < CONFIG_LIMITS.fontSize.min || config.value.fontSize > CONFIG_LIMITS.fontSize.max) {
config.value.fontSize = config.value.fontSize < CONFIG_LIMITS.fontSize.min
? CONFIG_LIMITS.fontSize.min
: CONFIG_LIMITS.fontSize.max;
logStore.warning(t('config.fontSizeFixed'));
hasChanges = true;
}
// 验证Tab大小
if (config.value.tabSize < CONFIG_LIMITS.tabSize.min || config.value.tabSize > CONFIG_LIMITS.tabSize.max) {
config.value.tabSize = config.value.tabSize < CONFIG_LIMITS.tabSize.min
? CONFIG_LIMITS.tabSize.min
: CONFIG_LIMITS.tabSize.max;
logStore.warning(t('config.tabSizeFixed'));
hasChanges = true;
}
// 验证TabType是否合法
if (!CONFIG_LIMITS.tabType.values.includes(config.value.tabType)) {
config.value.tabType = CONFIG_LIMITS.tabType.default;
logStore.warning(t('config.tabTypeFixed'));
hasChanges = true;
}
// 如果配置被修正且需要自动保存,保存回后端
if (hasChanges && autoSave && configLoaded.value) {
saveConfigToBackend();
}
}
// 使用防抖保存配置到后端
const saveConfigToBackend = useDebounceFn(async () => {
if (!configLoaded.value || isLoading.value) return;
// 更新配置项的通用方法 - 直接调用后端Set方法
async function updateConfig<K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> {
if (!configLoaded.value) return;
try {
// 首先获取后端当前的语言设置
const currentLanguage = await ConfigService.GetLanguage();
const backendKey = CONFIG_KEY_MAP[key];
await ConfigService.Set(backendKey, value);
// 确保我们使用当前的语言设置,避免被默认值覆盖
if (currentLanguage && currentLanguage !== config.value.language) {
config.value.language = currentLanguage;
}
// 更新本地状态
config.value[key] = value;
await ConfigService.UpdateEditorConfig(config.value);
logStore.info(t('config.saveSuccess'));
} catch (error) {
console.error('Failed to save configuration:', error);
console.error(`Failed to update config ${key}:`, error);
logStore.error(t('config.saveFailed'));
}
}, 500);
// 监听配置变化,自动保存到后端
watch(() => config.value, async () => {
if (configLoaded.value && !isLoading.value) {
await saveConfigToBackend();
}
}, {deep: true});
// 更新特定配置项的类型安全方法
function updateConfig<K extends keyof EditorConfig>(
key: K,
value: EditorConfig[K] | ((currentValue: EditorConfig[K]) => EditorConfig[K])
) {
if (typeof value === 'function') {
const currentValue = config.value[key];
const fn = value as (val: EditorConfig[K]) => EditorConfig[K];
config.value[key] = fn(currentValue);
} else {
config.value[key] = value;
throw error;
}
}
// 用于数字类型配置的增减方法
function adjustFontSize(amount: number) {
let newValue = config.value.fontSize + amount;
if (newValue < MIN_FONT_SIZE) newValue = MIN_FONT_SIZE;
if (newValue > MAX_FONT_SIZE) newValue = MAX_FONT_SIZE;
config.value.fontSize = newValue;
// 字体大小操作
async function adjustFontSize(delta: number): Promise<void> {
const newSize = ConfigUtils.clamp(config.value.fontSize + delta, CONFIG_LIMITS.fontSize.min, CONFIG_LIMITS.fontSize.max);
await updateConfig('fontSize', newSize);
}
function adjustTabSize(amount: number) {
let newValue = config.value.tabSize + amount;
if (newValue < MIN_TAB_SIZE) newValue = MIN_TAB_SIZE;
if (newValue > MAX_TAB_SIZE) newValue = MAX_TAB_SIZE;
config.value.tabSize = newValue;
// Tab大小操作
async function adjustTabSize(delta: number): Promise<void> {
const newSize = ConfigUtils.clamp(config.value.tabSize + delta, CONFIG_LIMITS.tabSize.min, CONFIG_LIMITS.tabSize.max);
await updateConfig('tabSize', newSize);
}
// Tab相关类型安全的配置切换
function toggleTabType() {
config.value.tabType = config.value.tabType === TabType.TabTypeSpaces
? TabType.TabTypeTab
: TabType.TabTypeSpaces;
// 切换操作
async function toggleTabIndent(): Promise<void> {
await updateConfig('enableTabIndent', !config.value.enableTabIndent);
}
// 切换窗口置顶状态
function toggleAlwaysOnTop() {
updateConfig('alwaysOnTop', val => !val);
async function toggleTabType(): Promise<void> {
const newTabType = config.value.tabType === TabType.TabTypeSpaces ? TabType.TabTypeTab : TabType.TabTypeSpaces;
await updateConfig('tabType', newTabType);
}
// 重置为默认配置
async function resetToDefaults() {
async function toggleAlwaysOnTop(): Promise<void> {
await updateConfig('alwaysOnTop', !config.value.alwaysOnTop);
}
// 语言设置
async function setLanguage(language: LanguageType): Promise<void> {
try {
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
config.value.language = language;
logStore.info(t('config.languageChanged'));
} catch (error) {
console.error('Failed to set language:', error);
logStore.error(t('config.languageChangeFailed'));
throw error;
}
}
// 重置配置
async function resetConfig(): Promise<void> {
if (isLoading.value) return;
try {
isLoading.value = true;
await ConfigService.ResetConfig();
await loadConfigFromBackend();
await loadConfig();
logStore.info(t('config.resetSuccess'));
} catch (error) {
console.error('Failed to reset configuration:', error);
logStore.error(t('config.resetFailed'));
} finally {
isLoading.value = false;
}
}
// 设置字体大小
async function setFontSize(size: number): Promise<void> {
await updateConfig('fontSize', size);
}
// 设置Tab大小
async function setTabSize(size: number): Promise<void> {
await updateConfig('tabSize', size);
}
return {
// 状态
config,
configLoaded,
isLoading,
// 常量
// 计算属性
MIN_FONT_SIZE,
MAX_FONT_SIZE,
DEFAULT_FONT_SIZE,
MIN_TAB_SIZE,
MAX_TAB_SIZE,
// 核心方法
loadConfigFromBackend,
saveConfigToBackend,
loadConfig,
resetConfig,
setLanguage,
updateConfig,
resetToDefaults,
// 字体大小方法
// 字体大小操作
increaseFontSize: () => adjustFontSize(1),
decreaseFontSize: () => adjustFontSize(-1),
resetFontSize: () => updateConfig('fontSize', DEFAULT_FONT_SIZE),
resetFontSize: () => setFontSize(CONFIG_LIMITS.fontSize.default),
setFontSize,
// Tab操作
toggleTabIndent: () => updateConfig('enableTabIndent', val => !val),
toggleTabIndent,
increaseTabSize: () => adjustTabSize(1),
decreaseTabSize: () => adjustTabSize(-1),
toggleTabType,
setTabSize,
// 窗口置顶操作
// 窗口操作
toggleAlwaysOnTop
};
});

View File

@@ -0,0 +1,42 @@
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
import { SupportedLocaleType } from '@/i18n';
/**
* 配置工具类
*/
export class ConfigUtils {
/**
* 将后端语言类型转换为前端语言代码
*/
static backendLanguageToFrontend(language: LanguageType): SupportedLocaleType {
return language === LanguageType.LangZhCN ? 'zh-CN' : 'en-US';
}
/**
* 将前端语言代码转换为后端语言类型
*/
static frontendLanguageToBackend(locale: SupportedLocaleType): LanguageType {
return locale === 'zh-CN' ? LanguageType.LangZhCN : LanguageType.LangEnUS;
}
/**
* 验证数值是否在指定范围内
*/
static clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
/**
* 验证配置值是否有效
*/
static isValidConfigValue<T>(value: T, validValues: readonly T[]): boolean {
return validValues.includes(value);
}
/**
* 获取配置的默认值
*/
static getDefaultValue<T>(key: string, defaults: Record<string, { default: T }>): T {
return defaults[key]?.default;
}
}

14
go.mod
View File

@@ -5,11 +5,13 @@ go 1.23.0
toolchain go1.24.2
require (
dario.cat/mergo v1.0.2
github.com/fsnotify/fsnotify v1.9.0
github.com/spf13/viper v1.20.1
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
@@ -22,6 +24,7 @@ require (
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -33,15 +36,23 @@ require (
github.com/lmittmann/tint v1.0.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/samber/lo v1.50.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/wailsapp/go-webview2 v1.0.21 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/net v0.40.0 // indirect
@@ -49,4 +60,5 @@ require (
golang.org/x/text v0.25.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

24
go.sum
View File

@@ -26,6 +26,10 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -38,6 +42,8 @@ github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
@@ -73,6 +79,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -86,6 +94,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -93,11 +103,23 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
@@ -106,6 +128,8 @@ github.com/wailsapp/wails/v3 v3.0.0-alpha.9 h1:b8CfRrhPno8Fra0xFp4Ifyj+ogmXBc35r
github.com/wailsapp/wails/v3 v3.0.0-alpha.9/go.mod h1:dSv6s722nSWaUyUiapAM1DHc5HKggNGY1a79shO85/g=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=

View File

@@ -16,29 +16,24 @@ const (
TabTypeTab TabType = "tab"
)
// SaveOptions 保存选项
type SaveOptions struct {
// 自动保存延迟(毫秒)- 内容变更后多久自动保存
AutoSaveDelay int `json:"autoSaveDelay"`
// 变更字符阈值,超过此阈值立即触发保存
ChangeThreshold int `json:"changeThreshold"`
// 最小保存间隔(毫秒)- 两次保存之间的最小时间间隔避免频繁IO
MinSaveInterval int `json:"minSaveInterval"`
}
// DocumentConfig 定义文档配置
type DocumentConfig struct {
SaveOptions SaveOptions `json:"saveOptions"` // 详细保存选项
// 自动保存延迟(毫秒)- 内容变更后多久自动保存
AutoSaveDelay int `json:"autoSaveDelay" yaml:"auto_save_delay" mapstructure:"auto_save_delay"`
// 变更字符阈值,超过此阈值立即触发保存
ChangeThreshold int `json:"changeThreshold" yaml:"change_threshold" mapstructure:"change_threshold"`
// 最小保存间隔(毫秒)- 两次保存之间的最小时间间隔避免频繁IO
MinSaveInterval int `json:"minSaveInterval" yaml:"min_save_interval" mapstructure:"min_save_interval"`
}
// EditorConfig 定义编辑器配置
type EditorConfig struct {
FontSize int `json:"fontSize"` // 字体大小
EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进
TabSize int `json:"tabSize"` // Tab大小
TabType TabType `json:"tabType"` // Tab类型空格或Tab
Language LanguageType `json:"language"` // 界面语言
AlwaysOnTop bool `json:"alwaysOnTop"` // 窗口是否置顶
FontSize int `json:"fontSize" yaml:"font_size" mapstructure:"font_size"` // 字体大小
EnableTabIndent bool `json:"enableTabIndent" yaml:"enable_tab_indent" mapstructure:"enable_tab_indent"` // 是否启用Tab缩进
TabSize int `json:"tabSize" yaml:"tab_size" mapstructure:"tab_size"` // Tab大小
TabType TabType `json:"tabType" yaml:"tab_type" mapstructure:"tab_type"` // Tab类型空格或Tab
Language LanguageType `json:"language" yaml:"language" mapstructure:"language"` // 界面语言
AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶
}
// LanguageType 语言类型定义
@@ -53,35 +48,33 @@ const (
// PathsConfig 路径配置集合
type PathsConfig struct {
LogPath string `json:"logPath"` // 日志文件路径
DataPath string `json:"dataPath"` // 数据存储路径
DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径
}
// AppConfig 应用配置 - 包含业务配置和路径配置
type AppConfig struct {
Editor EditorConfig `json:"editor"` // 编辑器配置
Document DocumentConfig `json:"document"` // 文档配置
Paths PathsConfig `json:"paths"` // 路径配置
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
Editor EditorConfig `json:"editor" yaml:"editor" mapstructure:"editor"` // 编辑器配置
Document DocumentConfig `json:"document" yaml:"document" mapstructure:"document"` // 文档配置
Paths PathsConfig `json:"paths" yaml:"paths" mapstructure:"paths"` // 路径配置
Metadata ConfigMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` // 配置元数据
}
// ConfigMetadata 配置元数据
type ConfigMetadata struct {
Version string `json:"version"` // 配置版本
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
Version string `json:"version" yaml:"version" mapstructure:"version"` // 配置版本
LastUpdated time.Time `json:"lastUpdated" yaml:"last_updated" mapstructure:"last_updated"` // 最后更新时间
}
// NewDefaultAppConfig 创建默认应用配置
func NewDefaultAppConfig() *AppConfig {
// 获取用户主目录
homePath, err := os.UserHomeDir()
// 获取当前工作目录
currentDir, err := os.Getwd()
if err != nil {
homePath = "."
currentDir = "."
}
// 默认路径配置
rootDir := filepath.Join(homePath, ".voidraft")
dataDir := filepath.Join(rootDir, "data")
// 默认路径配置 - 使用当前目录
dataDir := filepath.Join(currentDir, "data")
return &AppConfig{
Editor: EditorConfig{
@@ -93,14 +86,11 @@ func NewDefaultAppConfig() *AppConfig {
AlwaysOnTop: false,
},
Document: DocumentConfig{
SaveOptions: SaveOptions{
AutoSaveDelay: 5000, // 5秒后自动保存
ChangeThreshold: 500, // 500个字符变更触发保存
MinSaveInterval: 1000, // 最小间隔1000毫秒
},
AutoSaveDelay: 5000, // 5秒后自动保存
ChangeThreshold: 500, // 500个字符变更触发保存
MinSaveInterval: 1000, // 最小间隔1000毫秒
},
Paths: PathsConfig{
LogPath: filepath.Join(rootDir, "logs"),
DataPath: dataDir,
},
Metadata: ConfigMetadata{

View File

@@ -3,48 +3,22 @@ package services
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/pkg/services/log"
"os"
"path/filepath"
"sync"
"time"
"voidraft/internal/models"
"dario.cat/mergo"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// ConfigPath 配置路径提供接口
type ConfigPath interface {
// GetConfigPath 获取配置文件路径
GetConfigPath() string
}
// DefaultConfigPathProvider 默认配置路径提供者
type DefaultConfigPathProvider struct{}
// GetConfigPath 获取默认配置路径
func (p *DefaultConfigPathProvider) GetConfigPath() string {
// 获取用户主目录
homePath, err := os.UserHomeDir()
if err != nil {
homePath = "."
}
// 返回固定的配置路径
return filepath.Join(homePath, ".voidraft", "config", "config.json")
}
// ConfigOption 配置服务选项
type ConfigOption struct {
Logger *log.LoggerService // 日志服务
PathProvider ConfigPath // 路径提供者
}
// ConfigService 提供配置管理功能
// ConfigService 提供基于 Viper 的配置管理功能
type ConfigService struct {
store *Store[models.AppConfig] // 配置存储
logger *log.LoggerService // 日志服务
cache *models.AppConfig // 配置缓存
cacheMu sync.RWMutex // 缓存锁
viper *viper.Viper // Viper 实例
logger *log.LoggerService // 日志服务
mu sync.RWMutex // 读写锁
}
// ConfigError 配置错误
@@ -65,280 +39,391 @@ func (e *ConfigError) Unwrap() error {
// Is 实现错误匹配
func (e *ConfigError) Is(target error) bool {
_, ok := target.(*ConfigError)
var configError *ConfigError
ok := errors.As(target, &configError)
return ok
}
// NewConfigService 创建新的配置服务实例
func NewConfigService(opt ...ConfigOption) *ConfigService {
var option ConfigOption
// ConfigLimits 配置限制定义
type ConfigLimits struct {
FontSizeMin int
FontSizeMax int
TabSizeMin int
TabSizeMax int
}
// 使用第一个选项
if len(opt) > 0 {
option = opt[0]
// getConfigLimits 获取配置限制
func getConfigLimits() ConfigLimits {
return ConfigLimits{
FontSizeMin: 12,
FontSizeMax: 28,
TabSizeMin: 2,
TabSizeMax: 8,
}
}
// validateAndFixValue 验证并修正配置值
func (cs *ConfigService) validateAndFixValue(key string, value interface{}) (interface{}, bool) {
limits := getConfigLimits()
fixed := false
switch key {
case "editor.font_size":
if intVal, ok := value.(int); ok {
if intVal < limits.FontSizeMin {
cs.logger.Warning("Config: Font size too small, fixing", "original", intVal, "fixed", limits.FontSizeMin)
return limits.FontSizeMin, true
}
if intVal > limits.FontSizeMax {
cs.logger.Warning("Config: Font size too large, fixing", "original", intVal, "fixed", limits.FontSizeMax)
return limits.FontSizeMax, true
}
}
case "editor.tab_size":
if intVal, ok := value.(int); ok {
if intVal < limits.TabSizeMin {
cs.logger.Warning("Config: Tab size too small, fixing", "original", intVal, "fixed", limits.TabSizeMin)
return limits.TabSizeMin, true
}
if intVal > limits.TabSizeMax {
cs.logger.Warning("Config: Tab size too large, fixing", "original", intVal, "fixed", limits.TabSizeMax)
return limits.TabSizeMax, true
}
}
case "editor.tab_type":
if strVal, ok := value.(string); ok {
validTypes := []string{string(models.TabTypeSpaces), string(models.TabTypeTab)}
isValid := false
for _, validType := range validTypes {
if strVal == validType {
isValid = true
break
}
}
if !isValid {
cs.logger.Warning("Config: Invalid tab type, fixing", "original", strVal, "fixed", string(models.TabTypeSpaces))
return string(models.TabTypeSpaces), true
}
}
case "editor.language":
if strVal, ok := value.(string); ok {
validLanguages := []string{string(models.LangZhCN), string(models.LangEnUS)}
isValid := false
for _, validLang := range validLanguages {
if strVal == validLang {
isValid = true
break
}
}
if !isValid {
cs.logger.Warning("Config: Invalid language, fixing", "original", strVal, "fixed", string(models.LangZhCN))
return string(models.LangZhCN), true
}
}
case "document.auto_save_delay":
if intVal, ok := value.(int); ok {
if intVal < 1000 {
cs.logger.Warning("Config: Auto save delay too small, fixing", "original", intVal, "fixed", 1000)
return 1000, true
}
if intVal > 30000 {
cs.logger.Warning("Config: Auto save delay too large, fixing", "original", intVal, "fixed", 30000)
return 30000, true
}
}
case "document.change_threshold":
if intVal, ok := value.(int); ok {
if intVal < 10 {
cs.logger.Warning("Config: Change threshold too small, fixing", "original", intVal, "fixed", 10)
return 10, true
}
if intVal > 10000 {
cs.logger.Warning("Config: Change threshold too large, fixing", "original", intVal, "fixed", 10000)
return 10000, true
}
}
case "document.min_save_interval":
if intVal, ok := value.(int); ok {
if intVal < 100 {
cs.logger.Warning("Config: Min save interval too small, fixing", "original", intVal, "fixed", 100)
return 100, true
}
if intVal > 10000 {
cs.logger.Warning("Config: Min save interval too large, fixing", "original", intVal, "fixed", 10000)
return 10000, true
}
}
}
return value, fixed
}
// validateAllConfig 验证并修正所有配置
func (cs *ConfigService) validateAllConfig() error {
hasChanges := false
// 获取当前配置
var config models.AppConfig
if err := cs.viper.Unmarshal(&config); err != nil {
return &ConfigError{Operation: "unmarshal_for_validation", Err: err}
}
// 验证编辑器配置
if fixedValue, fixed := cs.validateAndFixValue("editor.font_size", config.Editor.FontSize); fixed {
cs.viper.Set("editor.font_size", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.tab_size", config.Editor.TabSize); fixed {
cs.viper.Set("editor.tab_size", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.tab_type", string(config.Editor.TabType)); fixed {
cs.viper.Set("editor.tab_type", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("editor.language", string(config.Editor.Language)); fixed {
cs.viper.Set("editor.language", fixedValue)
hasChanges = true
}
// 验证文档配置
if fixedValue, fixed := cs.validateAndFixValue("document.auto_save_delay", config.Document.AutoSaveDelay); fixed {
cs.viper.Set("document.auto_save_delay", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("document.change_threshold", config.Document.ChangeThreshold); fixed {
cs.viper.Set("document.change_threshold", fixedValue)
hasChanges = true
}
if fixedValue, fixed := cs.validateAndFixValue("document.min_save_interval", config.Document.MinSaveInterval); fixed {
cs.viper.Set("document.min_save_interval", fixedValue)
hasChanges = true
}
// 如果有修正,保存配置
if hasChanges {
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "save_validated_config", Err: err}
}
cs.logger.Info("Config: Configuration validated and fixed")
}
return nil
}
// NewConfigService 创建新的配置服务实例
func NewConfigService(logger *log.LoggerService) *ConfigService {
// 设置日志服务
logger := option.Logger
if logger == nil {
logger = log.New()
}
// 设置路径提供者
pathProvider := option.PathProvider
if pathProvider == nil {
pathProvider = &DefaultConfigPathProvider{}
// 获取当前工作目录
currentDir, err := os.Getwd()
if err != nil {
currentDir = "."
}
// 获取配置路径
configPath := pathProvider.GetConfigPath()
logger.Info("Config: Using config path", "path", configPath)
// 固定配置路径和文件名
configPath := filepath.Join(currentDir, "config")
configName := "config"
// 创建存储
store := NewStore[models.AppConfig](StoreOption{
FilePath: configPath,
Logger: logger,
})
// 创建 Viper 实例
v := viper.New()
// 配置 Viper
v.SetConfigName(configName)
v.SetConfigType("yaml")
v.AddConfigPath(configPath)
// 设置环境变量前缀
v.SetEnvPrefix("VOIDRAFT")
v.AutomaticEnv()
// 设置默认值
setDefaults(v)
// 构造配置服务实例
service := &ConfigService{
store: store,
viper: v,
logger: logger,
}
// 初始化加载配置
// 初始化配置
if err := service.initConfig(); err != nil {
service.logger.Error("Config: Failed to initialize config", "error", err)
}
// 验证并修正配置
if err := service.validateAllConfig(); err != nil {
service.logger.Error("Config: Failed to validate config", "error", err)
}
// 启动配置文件监听
service.startWatching()
return service
}
// setDefaults 设置默认配置值
func setDefaults(v *viper.Viper) {
defaultConfig := models.NewDefaultAppConfig()
// 编辑器配置默认值
v.SetDefault("editor.font_size", defaultConfig.Editor.FontSize)
v.SetDefault("editor.enable_tab_indent", defaultConfig.Editor.EnableTabIndent)
v.SetDefault("editor.tab_size", defaultConfig.Editor.TabSize)
v.SetDefault("editor.tab_type", defaultConfig.Editor.TabType)
v.SetDefault("editor.language", defaultConfig.Editor.Language)
v.SetDefault("editor.always_on_top", defaultConfig.Editor.AlwaysOnTop)
// 文档配置默认值
v.SetDefault("document.auto_save_delay", defaultConfig.Document.AutoSaveDelay)
v.SetDefault("document.change_threshold", defaultConfig.Document.ChangeThreshold)
v.SetDefault("document.min_save_interval", defaultConfig.Document.MinSaveInterval)
// 路径配置默认值
v.SetDefault("paths.data_path", defaultConfig.Paths.DataPath)
// 元数据默认值
v.SetDefault("metadata.version", defaultConfig.Metadata.Version)
v.SetDefault("metadata.last_updated", defaultConfig.Metadata.LastUpdated)
}
// initConfig 初始化配置
func (cs *ConfigService) initConfig() error {
config, err := cs.loadConfig()
cs.mu.Lock()
defer cs.mu.Unlock()
if err != nil {
// 如果加载失败,使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.logger.Info("Config: Using default config")
// 保存默认配置并更新缓存
if err := cs.saveConfigWithCache(*defaultConfig); err != nil {
return &ConfigError{Operation: "init_save_default", Err: err}
// 尝试读取配置文件
if err := cs.viper.ReadInConfig(); err != nil {
var configFileNotFoundError viper.ConfigFileNotFoundError
if errors.As(err, &configFileNotFoundError) {
// 配置文件不存在,创建默认配置文件
cs.logger.Info("Config: Config file not found, creating default config")
return cs.createDefaultConfig()
}
return nil
}
// 合并默认配置
if err := cs.mergeWithDefaults(&config); err != nil {
return &ConfigError{Operation: "init_merge_defaults", Err: err}
// 配置文件存在但读取失败
return &ConfigError{Operation: "read_config", Err: err}
}
cs.logger.Info("Config: Successfully loaded config file", "file", cs.viper.ConfigFileUsed())
return nil
}
// mergeWithDefaults 将默认配置合并到现有配置中
func (cs *ConfigService) mergeWithDefaults(config *models.AppConfig) error {
defaultConfig := models.NewDefaultAppConfig()
// createDefaultConfig 创建默认配置文件
func (cs *ConfigService) createDefaultConfig() error {
// 获取配置目录路径
currentDir, err := os.Getwd()
if err != nil {
currentDir = "."
}
configDir := filepath.Join(currentDir, "config")
configPath := filepath.Join(configDir, "config.yaml")
// 使用mergo库合并配置
if err := mergo.Merge(config, defaultConfig, mergo.WithOverrideEmptySlice); err != nil {
return err
// 确保配置目录存在
if err := os.MkdirAll(configDir, 0755); err != nil {
return &ConfigError{Operation: "create_config_dir", Err: err}
}
// 更新最后修改时间
config.Metadata.LastUpdated = time.Now()
// 设置当前时间为最后更新时间
cs.viper.Set("metadata.last_updated", time.Now())
// 保存合并后的配置
return cs.saveConfigWithCache(*config)
}
// loadConfig 加载配置
func (cs *ConfigService) loadConfig() (models.AppConfig, error) {
// 尝试从缓存获取
cs.cacheMu.RLock()
cachedConfig := cs.cache
cs.cacheMu.RUnlock()
if cachedConfig != nil {
return *cachedConfig, nil
}
// 从存储加载
config := cs.store.Get()
// 检查配置是否有效
if !isValidConfig(config) {
return models.AppConfig{}, errors.New("invalid or empty configuration")
}
return config, nil
}
// isValidConfig 检查配置是否有效
func isValidConfig(config models.AppConfig) bool {
// 检查关键字段
if config.Metadata.Version == "" {
return false
}
return true
}
// saveConfigWithCache 保存配置并更新缓存
func (cs *ConfigService) saveConfigWithCache(config models.AppConfig) error {
// 更新缓存
cs.cacheMu.Lock()
cs.cache = &config
cs.cacheMu.Unlock()
// 保存到存储
if err := cs.store.Set(config); err != nil {
return err
// 使用 SafeWriteConfigAs 写入配置文件(如果文件不存在则创建)
if err := cs.viper.SafeWriteConfigAs(configPath); err != nil {
return &ConfigError{Operation: "write_default_config", Err: err}
}
cs.logger.Info("Config: Created default config file", "path", configPath)
return nil
}
// startWatching 启动配置文件监听
func (cs *ConfigService) startWatching() {
// 设置配置变化回调
cs.viper.OnConfigChange(func(e fsnotify.Event) {
cs.logger.Info("Config: Config file changed", "file", e.Name, "operation", e.Op.String())
// 注释掉自动更新时间戳,避免无限循环
// err := cs.Set("metadata.last_updated", time.Now())
// if err != nil {
// cs.logger.Error("Config: Failed to update last_updated field", "error", err)
// }
})
// 启动配置文件监听
cs.viper.WatchConfig()
cs.logger.Info("Config: Started watching config file for changes")
}
// GetConfig 获取完整应用配置
func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
// 优先使用缓存
cs.cacheMu.RLock()
if cs.cache != nil {
config := *cs.cache
cs.cacheMu.RUnlock()
return &config, nil
cs.mu.RLock()
defer cs.mu.RUnlock()
var config models.AppConfig
if err := cs.viper.Unmarshal(&config); err != nil {
return nil, &ConfigError{Operation: "unmarshal_config", Err: err}
}
cs.cacheMu.RUnlock()
// 加载配置
config, err := cs.loadConfig()
if err != nil {
// 加载失败,使用默认配置
defaultConfig := models.NewDefaultAppConfig()
// 保存默认配置
if saveErr := cs.saveConfigWithCache(*defaultConfig); saveErr != nil {
cs.logger.Error("Config: Failed to save default config", "error", saveErr)
return nil, &ConfigError{Operation: "get_save_default", Err: saveErr}
}
return defaultConfig, nil
}
return &config, nil
}
// SaveConfig 保存完整应用配置
func (cs *ConfigService) SaveConfig(config *models.AppConfig) error {
if config == nil {
return errors.New("cannot save nil config")
// Set 设置配置
func (cs *ConfigService) Set(key string, value interface{}) error {
cs.mu.Lock()
defer cs.mu.Unlock()
// 验证并修正配置值
validatedValue, wasFixed := cs.validateAndFixValue(key, value)
// 设置验证后的值
cs.viper.Set(key, validatedValue)
// 使用 WriteConfig 写入配置文件(会触发文件监听)
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "set_config", Err: err}
}
// 更新配置元数据
config.Metadata.LastUpdated = time.Now()
if wasFixed {
cs.logger.Debug("Config: Successfully set config with validation", "key", key, "original", value, "fixed", validatedValue)
} else {
cs.logger.Debug("Config: Successfully set config", "key", key, "value", value)
}
// 保存配置
return cs.saveConfigWithCache(*config)
return nil
}
// UpdateEditorConfig 更新编辑器配置
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
// 保存当前的语言设置
currentLanguage := config.Editor.Language
// 更新编辑器配置
config.Editor = editorConfig
// 如果更新后的语言设置为空,则使用之前的语言设置
if editorConfig.Language == "" {
config.Editor.Language = currentLanguage
}
return cs.SaveConfig(config)
}
// GetEditorConfig 获取编辑器配置
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
config, err := cs.GetConfig()
if err != nil {
return models.EditorConfig{}, err
}
return config.Editor, nil
}
// SetLanguage 设置语言
func (cs *ConfigService) SetLanguage(language models.LanguageType) error {
// 验证语言类型有效
if language != models.LangZhCN && language != models.LangEnUS {
return errors.New("invalid language type")
}
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Editor.Language = language
return cs.SaveConfig(config)
}
// GetLanguage 获取当前语言设置
func (cs *ConfigService) GetLanguage() (models.LanguageType, error) {
editorConfig, err := cs.GetEditorConfig()
if err != nil {
return "", err
}
return editorConfig.Language, nil
}
// UpdatePaths 更新路径配置
func (cs *ConfigService) UpdatePaths(paths models.PathsConfig) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Paths = paths
return cs.SaveConfig(config)
}
// GetPaths 获取路径配置
func (cs *ConfigService) GetPaths() (models.PathsConfig, error) {
config, err := cs.GetConfig()
if err != nil {
return models.PathsConfig{}, err
}
return config.Paths, nil
}
// UpdateMetadata 更新配置元数据
func (cs *ConfigService) UpdateMetadata(metadata models.ConfigMetadata) error {
config, err := cs.GetConfig()
if err != nil {
return err
}
config.Metadata = metadata
return cs.SaveConfig(config)
}
// GetMetadata 获取配置元数据
func (cs *ConfigService) GetMetadata() (models.ConfigMetadata, error) {
config, err := cs.GetConfig()
if err != nil {
return models.ConfigMetadata{}, err
}
return config.Metadata, nil
// Get 获取配置
func (cs *ConfigService) Get(key string) interface{} {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.viper.Get(key)
}
// ResetConfig 重置为默认配置
func (cs *ConfigService) ResetConfig() error {
defaultConfig := models.NewDefaultAppConfig()
return cs.SaveConfig(defaultConfig)
cs.mu.Lock()
defer cs.mu.Unlock()
// 重新设置默认值
setDefaults(cs.viper)
// 使用 WriteConfig 写入配置文件(会触发文件监听)
if err := cs.viper.WriteConfig(); err != nil {
return &ConfigError{Operation: "reset_config", Err: err}
}
cs.logger.Info("Config: Successfully reset to default configuration")
return nil
}

View File

@@ -147,9 +147,9 @@ func (ds *DocumentService) scheduleAutoSave() {
// 打印保存设置,便于调试
ds.logger.Debug("Document: Auto save settings",
"autoSaveDelay", config.Document.SaveOptions.AutoSaveDelay,
"changeThreshold", config.Document.SaveOptions.ChangeThreshold,
"minSaveInterval", config.Document.SaveOptions.MinSaveInterval)
"autoSaveDelay", config.Document.AutoSaveDelay,
"changeThreshold", config.Document.ChangeThreshold,
"minSaveInterval", config.Document.MinSaveInterval)
ds.lock.Lock()
defer ds.lock.Unlock()
@@ -160,7 +160,7 @@ func (ds *DocumentService) scheduleAutoSave() {
}
// 创建新的自动保存定时器
autoSaveDelay := config.Document.SaveOptions.AutoSaveDelay
autoSaveDelay := config.Document.AutoSaveDelay
ds.logger.Debug("Document: Scheduling auto save", "delay", autoSaveDelay)
ds.scheduleTimerWithDelay(autoSaveDelay)
}
@@ -197,7 +197,7 @@ func (ds *DocumentService) saveToStore(trigger SaveTrigger) {
// 如果成功获取了配置,使用配置值
if err == nil && config != nil {
minInterval = config.Document.SaveOptions.MinSaveInterval
minInterval = config.Document.MinSaveInterval
}
// 如果是自动保存,检查最小保存间隔
@@ -405,7 +405,7 @@ func (ds *DocumentService) UpdateActiveDocumentContent(content string) error {
// 如果成功获取了配置,使用配置值
if err == nil && config != nil {
threshold = config.Document.SaveOptions.ChangeThreshold
threshold = config.Document.ChangeThreshold
}
ds.lock.Lock()
@@ -530,22 +530,20 @@ func (ds *DocumentService) GetSaveSettings() (*models.DocumentConfig, error) {
// UpdateSaveSettings 更新文档保存设置
func (ds *DocumentService) UpdateSaveSettings(docConfig models.DocumentConfig) error {
// 获取当前配置
config, err := ds.configService.GetConfig()
if err != nil {
return &DocumentError{Operation: "update_save_settings", Err: err}
// 使用配置服务的 Set 方法更新文档配置
if err := ds.configService.Set("document.auto_save_delay", docConfig.AutoSaveDelay); err != nil {
return &DocumentError{Operation: "update_save_settings_auto_save_delay", Err: err}
}
// 更新保存设置
config.Document = docConfig
// 保存配置
err = ds.configService.SaveConfig(config)
if err != nil {
return &DocumentError{Operation: "update_save_settings_save", Err: err}
if err := ds.configService.Set("document.change_threshold", docConfig.ChangeThreshold); err != nil {
return &DocumentError{Operation: "update_save_settings_change_threshold", Err: err}
}
// 安排自动保存(不再需要检查保存模式)
if err := ds.configService.Set("document.min_save_interval", docConfig.MinSaveInterval); err != nil {
return &DocumentError{Operation: "update_save_settings_min_save_interval", Err: err}
}
// 安排自动保存
ds.scheduleAutoSave()
ds.logger.Info("Document: Updated save settings")

View File

@@ -17,11 +17,8 @@ func NewServiceManager() *ServiceManager {
// 初始化日志服务
logger := log.New()
// 初始化配置服务
configService := NewConfigService(ConfigOption{
Logger: logger,
PathProvider: nil,
})
// 初始化配置服务 - 使用固定配置(当前目录下的 config/config.yaml
configService := NewConfigService(logger)
// 初始化文档服务
documentService := NewDocumentService(configService, logger)