♻️ Refactor configuration service
This commit is contained in:
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
// 同时更新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);
|
||||
});
|
||||
try {
|
||||
// 转换为后端语言类型
|
||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(locale);
|
||||
|
||||
// 使用新的配置服务方法设置语言
|
||||
await ConfigService.Set('editor.language', backendLanguage);
|
||||
|
||||
// 更新前端语言
|
||||
i18n.global.locale = locale;
|
||||
} catch (error) {
|
||||
console.error('Failed to set language:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -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)'
|
||||
}
|
||||
};
|
@@ -26,7 +26,9 @@ export default {
|
||||
fontSizeFixed: '字体大小值({value})已被修正为{fixed}',
|
||||
tabSizeFixed: 'Tab大小值({value})已被修正为{fixed}',
|
||||
tabTypeFixed: 'Tab类型({value})不合法,已修正为空格',
|
||||
alwaysOnTopFailed: '无法设置窗口置顶状态'
|
||||
alwaysOnTopFailed: '无法设置窗口置顶状态',
|
||||
languageChanged: '语言设置已更新',
|
||||
languageChangeFailed: '语言设置更新失败'
|
||||
},
|
||||
languages: {
|
||||
'zh-CN': '简体中文',
|
||||
|
@@ -16,6 +16,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
redirect: '/settings/general',
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
// 主题选择(未实际实现,仅界面展示)
|
||||
|
@@ -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,170 +32,130 @@ 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 configLoaded = ref(false);
|
||||
|
||||
// 配置是否正在从后端加载
|
||||
// 加载状态
|
||||
const isLoading = ref(false);
|
||||
const configLoaded = 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 {
|
||||
// 状态
|
||||
@@ -203,31 +163,32 @@ export const useConfigStore = defineStore('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
|
||||
};
|
||||
});
|
42
frontend/src/utils/configUtils.ts
Normal file
42
frontend/src/utils/configUtils.ts
Normal 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
14
go.mod
@@ -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
24
go.sum
@@ -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=
|
||||
|
@@ -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{
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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")
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user