♻️ 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);
// 同时更新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);
}
}
};

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,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
};
});

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;
}
}