diff --git a/packages/frontend/src/components/LayoutConfigurator.vue b/packages/frontend/src/components/LayoutConfigurator.vue index 44977f4..85b41ac 100644 --- a/packages/frontend/src/components/LayoutConfigurator.vue +++ b/packages/frontend/src/components/LayoutConfigurator.vue @@ -143,14 +143,12 @@ const saveLayout = () => { const resetToDefault = () => { if (confirm(t('layoutConfigurator.confirmReset', '确定要恢复默认布局吗?当前更改将丢失。'))) { - // 重新调用 store 的初始化方法来获取默认布局 - layoutStore.initializeLayout(); - // 重新加载到本地副本 - if (layoutStore.layoutTree) { - localLayoutTree.value = JSON.parse(JSON.stringify(layoutStore.layoutTree)); - hasChanges.value = true; // 标记为有更改,因为是重置操作 - console.log('[LayoutConfigurator] 已重置为默认布局。'); - } + // 调用 store 中获取系统默认布局的方法 + const defaultLayout = layoutStore.getSystemDefaultLayout(); + // 直接将获取到的默认布局(深拷贝)赋值给本地副本 + localLayoutTree.value = JSON.parse(JSON.stringify(defaultLayout)); + hasChanges.value = true; // 标记为有更改,因为是重置操作 + console.log('[LayoutConfigurator] 已重置为系统默认布局。'); } }; diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index a4b7006..b329a1f 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -41,17 +41,24 @@ const i18n = createI18n<[MessageSchema], 'en' | 'zh'>({ * @param lang 要设置的语言代码 ('en', 'zh', etc.) */ export const setLocale = (lang: 'en' | 'zh') => { + console.log(`[i18n] Attempting to set locale to: ${lang}`); // <-- 添加日志 const globalComposer = i18n.global as unknown as Composer; // 强制类型断言 if (globalComposer.availableLocales.includes(lang)) { - globalComposer.locale.value = lang; // 访问 .value 属性 - try { - localStorage.setItem(localStorageKey, lang); // 持久化到 localStorage - console.log(`[i18n] Locale set to "${lang}" and saved to localStorage.`); // 添加日志 - } catch (e) { - console.error('[i18n] Failed to save locale to localStorage:', e); + const currentLocale = globalComposer.locale.value; // <-- 获取当前 locale + if (currentLocale !== lang) { // <-- 仅在 locale 实际改变时更新 + globalComposer.locale.value = lang; // 访问 .value 属性 + console.log(`[i18n] Successfully updated global locale from "${currentLocale}" to "${lang}".`); // <-- 修改日志 + try { + localStorage.setItem(localStorageKey, lang); // 持久化到 localStorage + console.log(`[i18n] Locale "${lang}" saved to localStorage.`); // <-- 修改日志 + } catch (e) { + console.error('[i18n] Failed to save locale to localStorage:', e); + } + } else { + console.log(`[i18n] Locale is already "${lang}". No update needed.`); // <-- 添加日志 } } else { - console.warn(`[i18n] Locale "${lang}" is not available.`); + console.warn(`[i18n] Locale "${lang}" is not available. Available locales: ${globalComposer.availableLocales.join(', ')}`); // <-- 修改日志 } }; diff --git a/packages/frontend/src/stores/layout.store.ts b/packages/frontend/src/stores/layout.store.ts index 890c154..1140f1c 100644 --- a/packages/frontend/src/stores/layout.store.ts +++ b/packages/frontend/src/stores/layout.store.ts @@ -251,6 +251,12 @@ export const useLayoutStore = defineStore('layout', () => { } } + // 新增 Action: 获取系统内置的默认布局 + function getSystemDefaultLayout(): LayoutNode { + console.log('[Layout Store] Getting system default layout.'); + return getDefaultLayout(); // 直接调用内部函数 + } + // 新增 Action: 将当前布局树持久化到后端和 localStorage async function persistLayoutTree() { if (!layoutTree.value) { @@ -328,5 +334,7 @@ export const useLayoutStore = defineStore('layout', () => { isHeaderVisible, loadHeaderVisibility, toggleHeaderVisibility, + // 新增:暴露获取默认布局的方法 + getSystemDefaultLayout, }; }); diff --git a/packages/frontend/src/stores/settings.store.ts b/packages/frontend/src/stores/settings.store.ts index be709c7..5ff9c2a 100644 --- a/packages/frontend/src/stores/settings.store.ts +++ b/packages/frontend/src/stores/settings.store.ts @@ -43,7 +43,8 @@ export const useSettingsStore = defineStore('settings', () => { console.log('[SettingsStore] 加载通用设置...'); const response = await apiClient.get>('/settings'); // 使用 apiClient settings.value = response.data; // Store fetched general settings - console.log('[SettingsStore] 通用设置已加载:', settings.value); + // --- 更详细的日志 --- + console.log('[SettingsStore] Fetched settings from backend:', JSON.stringify(settings.value)); // --- 设置默认值 (如果后端未返回) --- if (settings.value.showPopupFileEditor === undefined) { @@ -77,30 +78,34 @@ export const useSettingsStore = defineStore('settings', () => { // --- 语言设置 --- const langFromSettings = settings.value.language; + console.log(`[SettingsStore] Language from fetched settings: ${langFromSettings}`); // <-- 添加日志 if (langFromSettings === 'en' || langFromSettings === 'zh') { fetchedLang = langFromSettings; } else { const navigatorLang = navigator.language?.split('-')[0]; fetchedLang = navigatorLang === 'zh' ? 'zh' : defaultLng; - console.warn(`[SettingsStore] 语言设置无效 ('${langFromSettings}'), 回退到 '${fetchedLang}'.`); + console.warn(`[SettingsStore] Invalid language setting ('${langFromSettings}') received from backend or missing. Falling back to '${fetchedLang}'.`); // <-- 修改日志 // Optionally save the fallback language back // await updateSetting('language', fetchedLang); } if (fetchedLang) { - console.log(`[SettingsStore] 设置语言: ${fetchedLang}`); + console.log(`[SettingsStore] Determined language: ${fetchedLang}. Calling setLocale...`); // <-- 添加日志 setLocale(fetchedLang); } else { - console.error('[SettingsStore] 无法确定有效语言。'); + // This case should theoretically not happen with the fallback logic above + console.error('[SettingsStore] Could not determine a valid language. This should not happen.'); + console.log(`[SettingsStore] Falling back to default: ${defaultLng}. Calling setLocale...`); // <-- 添加日志 setLocale(defaultLng); } } catch (err: any) { - console.error('加载通用设置失败:', err); - error.value = err.response?.data?.message || err.message || '加载设置失败'; + console.error('Error loading general settings:', err); // <-- 修改日志 + error.value = err.response?.data?.message || err.message || 'Failed to load settings'; // 出错时(例如未登录),根据浏览器语言设置回退语言 const navigatorLang = navigator.language?.split('-')[0]; const fallbackLang = navigatorLang === 'zh' ? 'zh' : defaultLng; + console.log(`[SettingsStore] Error loading settings. Falling back to language: ${fallbackLang}. Calling setLocale...`); // <-- 添加日志 setLocale(fallbackLang); } finally { isLoading.value = false; @@ -134,6 +139,7 @@ export const useSettingsStore = defineStore('settings', () => { // If updating language, also update i18n if (key === 'language' && (value === 'en' || value === 'zh')) { + console.log(`[SettingsStore] updateSetting: Language updated to ${value}. Calling setLocale...`); // <-- 添加日志 setLocale(value); } } catch (err: any) { @@ -180,6 +186,7 @@ export const useSettingsStore = defineStore('settings', () => { // If language is updated, apply it if (languageUpdate) { + console.log(`[SettingsStore] updateMultipleSettings: Language updated to ${languageUpdate}. Calling setLocale...`); // <-- 添加日志 setLocale(languageUpdate); } } catch (err: any) { diff --git a/packages/frontend/src/views/SettingsView.vue b/packages/frontend/src/views/SettingsView.vue index 2956da2..f12f266 100644 --- a/packages/frontend/src/views/SettingsView.vue +++ b/packages/frontend/src/views/SettingsView.vue @@ -251,15 +251,16 @@ const appearanceStore = useAppearanceStore(); // 实例化外观 store const { t } = useI18n(); // --- Reactive state from store --- -// 使用 storeToRefs 获取响应式 getter -const { settings, isLoading: settingsLoading, error: settingsError, showPopupFileEditorBoolean, shareFileEditorTabsBoolean, autoCopyOnSelectBoolean, dockerDefaultExpandBoolean } = storeToRefs(settingsStore); // +++ 添加 dockerDefaultExpandBoolean +++ +// 使用 storeToRefs 获取响应式 getter,包括 language +const { settings, isLoading: settingsLoading, error: settingsError, showPopupFileEditorBoolean, shareFileEditorTabsBoolean, autoCopyOnSelectBoolean, dockerDefaultExpandBoolean, language: storeLanguage } = storeToRefs(settingsStore); // +++ 添加 dockerDefaultExpandBoolean 和 language getter +++ // --- Local state for forms --- const ipWhitelistInput = ref(''); -const selectedLanguage = ref<'en' | 'zh'>('en'); // Default to 'en', will be updated by watcher +// 使用 store 的 language getter 初始化 selectedLanguage +const selectedLanguage = ref<'en' | 'zh'>(storeLanguage.value); const blacklistSettingsForm = reactive({ // Renamed to avoid conflict with store state name - maxLoginAttempts: '5', - loginBanDuration: '300', + maxLoginAttempts: '5', // 初始值将在 watcher 中被 store 值覆盖 + loginBanDuration: '300', // 初始值将在 watcher 中被 store 值覆盖 }); const popupEditorEnabled = ref(true); // 本地状态,用于 v-model @@ -297,11 +298,11 @@ watch(settings, (newSettings, oldSettings) => { const isInitialLoad = !oldSettings; ipWhitelistInput.value = newSettings.ipWhitelist || ''; - selectedLanguage.value = newSettings.language || 'en'; + // selectedLanguage.value = newSettings.language || 'en'; // <-- 移除这一行,selectedLanguage 现在由 v-model 更新 blacklistSettingsForm.maxLoginAttempts = newSettings.maxLoginAttempts || '5'; blacklistSettingsForm.loginBanDuration = newSettings.loginBanDuration || '300'; - // 始终将本地布尔状态与 store 的布尔 getter 同步 + // 始终将本地布尔状态与 store 的布尔 getter 同步 (除了 language) popupEditorEnabled.value = showPopupFileEditorBoolean.value; shareTabsEnabled.value = shareFileEditorTabsBoolean.value; autoCopyEnabled.value = autoCopyOnSelectBoolean.value; // 同步选中即复制状态