diff --git a/packages/backend/src/repositories/appearance.repository.ts b/packages/backend/src/repositories/appearance.repository.ts index b8fcb0c..26eb1d7 100644 --- a/packages/backend/src/repositories/appearance.repository.ts +++ b/packages/backend/src/repositories/appearance.repository.ts @@ -18,7 +18,8 @@ interface DbAppearanceSettingsRow { const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): AppearanceSettings => { const settings: Partial = {}; let latestUpdatedAt = 0; - + let terminalBackgroundEnabledFound = false; // 新增:标记是否在数据库中找到该设置 + for (const row of rows) { // 更新 latestUpdatedAt if (row.updated_at > latestUpdatedAt) { @@ -48,9 +49,13 @@ const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): Appearanc case 'pageBackgroundImage': settings.pageBackgroundImage = row.value || undefined; break; + case 'terminalBackgroundEnabled': + settings.terminalBackgroundEnabled = row.value === 'true'; // 将 'true'/'false' 字符串转为 boolean + terminalBackgroundEnabledFound = true; // 标记已找到 + break; } } - + const defaults = getDefaultAppearanceSettings(); return { _id: 'global_appearance', // 全局外观设置的固定 ID @@ -61,11 +66,15 @@ const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): Appearanc editorFontSize: settings.editorFontSize ?? defaults.editorFontSize, terminalBackgroundImage: settings.terminalBackgroundImage ?? defaults.terminalBackgroundImage, pageBackgroundImage: settings.pageBackgroundImage ?? defaults.pageBackgroundImage, + // 修改:只有当数据库中未找到记录时才使用默认值 + terminalBackgroundEnabled: terminalBackgroundEnabledFound + ? settings.terminalBackgroundEnabled // 使用数据库找到的值 (true 或 false) + : defaults.terminalBackgroundEnabled, // 否则使用默认值 (true) updatedAt: latestUpdatedAt || defaults.updatedAt, // 使用最新的更新时间,否则使用默认时间戳 }; }; - - + + // 获取默认外观设置 (已简化, _id 在此不再相关) const getDefaultAppearanceSettings = (): Omit => { return { @@ -76,11 +85,12 @@ const getDefaultAppearanceSettings = (): Omit => { editorFontSize: 14, terminalBackgroundImage: undefined, pageBackgroundImage: undefined, + terminalBackgroundEnabled: true, // 新增:默认启用 updatedAt: Date.now(), // 提供默认时间戳 }; }; - - + + /** * 确保默认设置存在于键值表中。 * 此函数在数据库初始化期间调用。 @@ -100,8 +110,9 @@ export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise< { key: 'editorFontSize', value: defaults.editorFontSize }, { key: 'terminalBackgroundImage', value: defaults.terminalBackgroundImage ?? '' }, // 数据库中使用空字符串 { key: 'pageBackgroundImage', value: defaults.pageBackgroundImage ?? '' }, // 数据库中使用空字符串 + { key: 'terminalBackgroundEnabled', value: defaults.terminalBackgroundEnabled }, // 新增 ]; - + try { for (const entry of defaultEntries) { // 将值转换为字符串以存储到数据库,处理 null/undefined @@ -177,7 +188,10 @@ export const getAppearanceSettings = async (): Promise => { const db = await getDbInstance(); // 从键值表中获取所有行 const rows = await allDb(db, `SELECT key, value, updated_at FROM ${TABLE_NAME}`); - return mapRowsToAppearanceSettings(rows); // 将键值对映射到设置对象 + console.log('[AppearanceRepo LOG] 从数据库读取的原始行:', JSON.stringify(rows)); // 添加原始行日志 + const mappedSettings = mapRowsToAppearanceSettings(rows); // 将键值对映射到设置对象 + console.log(`[AppearanceRepo LOG] 映射后的 terminalBackgroundEnabled 值: ${mappedSettings.terminalBackgroundEnabled}`); // 添加映射后值的日志 + return mappedSettings; } catch (err: any) { console.error('[AppearanceRepo] 获取外观设置失败:', err.message); throw new Error('获取外观设置失败'); @@ -235,6 +249,8 @@ const updateAppearanceSettingsInternal = async (db: sqlite3.Database, settingsDt dbValue = key === 'activeTerminalThemeId' ? 'null' : ''; // 主题 ID 特殊存储为 'null' } else if (typeof value === 'object') { dbValue = JSON.stringify(value); + } else if (typeof value === 'boolean') { // 新增:处理布尔值 + dbValue = value ? 'true' : 'false'; } else { dbValue = String(value); } @@ -252,8 +268,10 @@ const updateAppearanceSettingsInternal = async (db: sqlite3.Database, settingsDt } // 对每个键值对执行 INSERT OR REPLACE + console.log(`[AppearanceRepo LOG] 准备更新/插入键: '${key}', 值: '${dbValue}'`); // 添加保存日志 const result = await runDb(db, sqlReplace, [key, dbValue, nowSeconds]); if (result.changes > 0) { + console.log(`[AppearanceRepo LOG] 键 '${key}' 更新成功。`); // 添加成功日志 changesMade = true; } } diff --git a/packages/backend/src/types/appearance.types.ts b/packages/backend/src/types/appearance.types.ts index 7b866f3..021709b 100644 --- a/packages/backend/src/types/appearance.types.ts +++ b/packages/backend/src/types/appearance.types.ts @@ -16,9 +16,10 @@ export interface AppearanceSettings { terminalBackgroundImage?: string; // 终端背景图片 URL 或路径 pageBackgroundImage?: string; // 页面背景图片 URL 或路径 editorFontSize?: number; // 编辑器字体大小 (px) + terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用 updatedAt?: number; } - + /** * 用于更新外观设置的数据结构 (所有字段可选) */ diff --git a/packages/frontend/src/components/StyleCustomizer.vue b/packages/frontend/src/components/StyleCustomizer.vue index 0ac4c13..2d2007f 100644 --- a/packages/frontend/src/components/StyleCustomizer.vue +++ b/packages/frontend/src/components/StyleCustomizer.vue @@ -22,8 +22,9 @@ const { // pageBackgroundOpacity, // Removed terminalBackgroundImage, // terminalBackgroundOpacity, // Removed + isTerminalBackgroundEnabled, // <-- 新增:终端背景启用状态 } = storeToRefs(appearanceStore); - + // --- 本地状态用于编辑 --- const editableUiTheme = ref>({}); const editableTerminalFontFamily = ref(''); @@ -33,7 +34,8 @@ const editableEditorFontSize = ref(14); // <-- 新增,编辑器字体大小 // const editableTerminalBackgroundOpacity = ref(1.0); // Removed const editableUiThemeString = ref(''); // 用于 textarea 绑定 const themeParseError = ref(null); // 用于显示 JSON 解析错误 - +const localTerminalBackgroundEnabled = ref(true); // <-- 新增:本地终端背景开关状态 + // 终端主题管理相关状态 const isEditingTheme = ref(false); // 是否正在编辑某个主题 const themeSearchTerm = ref(''); // 主题搜索词 @@ -92,6 +94,8 @@ const initializeEditableState = () => { editableTerminalFontFamily.value = currentTerminalFontFamily.value; editableTerminalFontSize.value = currentTerminalFontSize.value; editableEditorFontSize.value = currentEditorFontSize.value; // <-- 新增 + localTerminalBackgroundEnabled.value = isTerminalBackgroundEnabled.value; // <-- 重新添加:在此处初始化 + console.log(`[StyleCustomizer initializeEditableState] Initializing localTerminalBackgroundEnabled to: ${localTerminalBackgroundEnabled.value} (from store: ${isTerminalBackgroundEnabled.value})`); // 添加日志 uploadError.value = null; importError.value = null; saveThemeError.value = null; @@ -130,12 +134,13 @@ watch([ const newActiveThemeId = newVals[1]; const oldActiveThemeId = oldVals ? oldVals[1] : null; - // 仅当非编辑状态时,或活动终端主题ID变化时,或 UI 主题设置本身发生变化时 (例如重置),才重新初始化 - if (!isEditingTheme.value || newActiveThemeId !== oldActiveThemeId || newSettings?.customUiTheme !== oldSettings?.customUiTheme) { - console.log('[StyleCustomizer] Watch triggered re-initialization.'); + // 仅当非编辑状态时,或活动终端主题ID变化时,或 UI 主题/终端背景启用状态设置本身发生变化时 (例如重置或外部更改),才重新初始化 + const settingsChanged = newSettings?.customUiTheme !== oldSettings?.customUiTheme || newSettings?.terminalBackgroundEnabled !== oldSettings?.terminalBackgroundEnabled; // 检查相关设置是否变化 + if (!isEditingTheme.value || newActiveThemeId !== oldActiveThemeId || settingsChanged) { + console.log(`[StyleCustomizer Watch] Triggering re-initialization. isEditing: ${isEditingTheme.value}, themeIdChanged: ${newActiveThemeId !== oldActiveThemeId}, settingsChanged: ${settingsChanged}`); // 添加日志 initializeEditableState(); // 调用修改后的初始化函数 } else { - // 如果正在编辑,只更新非编辑相关的部分 (不包括 UI 主题,因为它由 initializeEditableState 处理) + // 如果正在编辑,只更新非编辑相关的部分 (不包括 UI 主题和终端背景开关,因为它们由 initializeEditableState 处理) console.log('[StyleCustomizer] Watch triggered partial update (editing).'); // editableUiTheme.value = JSON.parse(JSON.stringify(newVals[0] || {})); // 移除或注释掉,避免覆盖编辑状态 // 确保从正确的 newVals 索引获取值,现在 watch 的依赖项变了 @@ -144,12 +149,26 @@ watch([ editableTerminalFontFamily.value = newSettings?.terminalFontFamily || ''; editableTerminalFontSize.value = newSettings?.terminalFontSize || 14; editableEditorFontSize.value = newSettings?.editorFontSize || 14; // <-- 新增同步 + // localTerminalBackgroundEnabled.value = newSettings?.terminalBackgroundEnabled ?? true; // <-- 移除此行,避免冲突 } }, { deep: true }); - - + +// 监听 store 中 isTerminalBackgroundEnabled 的变化,以同步本地状态 +watch(isTerminalBackgroundEnabled, (newValue) => { + // 只有当本地状态与 store 状态不一致时才更新本地状态 + // 这避免了 handleToggleTerminalBackground 触发的不必要更新 + if (localTerminalBackgroundEnabled.value !== newValue) { + console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, updating local state.`); // 添加日志 + localTerminalBackgroundEnabled.value = newValue; + } else { + console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, but local state already matches. No update needed.`); // 添加日志 + } +}); +// 移除单独监听 isTerminalBackgroundEnabled 的 watcher + const emit = defineEmits(['close']); - + + const closeCustomizer = () => { // 如果正在编辑主题,直接关闭并重置状态 if (isEditingTheme.value) { @@ -756,9 +775,24 @@ const handleRemoveTerminalBg = async () => { alert(t('styleCustomizer.removeBgFailed', { message: error.message })); } }; - + +// 处理终端背景启用/禁用切换 +const handleToggleTerminalBackground = async () => { + const newValue = !localTerminalBackgroundEnabled.value; // 先计算新值 + localTerminalBackgroundEnabled.value = newValue; // 立即更新本地 UI + try { + await appearanceStore.setTerminalBackgroundEnabled(newValue); + // 成功后不需要提示,UI 已更新 + } catch (error: any) { + console.error("更新终端背景启用状态失败:", error); + // 失败时回滚本地状态 + localTerminalBackgroundEnabled.value = !newValue; + alert(t('styleCustomizer.errorToggleTerminalBg', { message: error.message })); // 需要添加翻译 + } +}; + // Removed handlePageOpacityChange and handleTerminalOpacityChange functions - + // --- 辅助函数 --- // 格式化 UI 主题标签 const formatLabel = (key: string): string => { @@ -1140,17 +1174,45 @@ const handleFocusAndSelect = (event: FocusEvent) => {
-

{{ t('styleCustomizer.terminalBackground') }}

-
- {{ t('styleCustomizer.noBackground') }} -
+
+

{{ t('styleCustomizer.terminalBackground') }}

+ + +
+ + +
+
+ {{ t('styleCustomizer.noBackground') }} +
-
+
- + +
+ {{ t('styleCustomizer.terminalBgDisabled', '终端背景功能已禁用。') }} +
+

{{ t('styleCustomizer.otherSettings') }}

diff --git a/packages/frontend/src/components/Terminal.vue b/packages/frontend/src/components/Terminal.vue index a505781..5cad06c 100644 --- a/packages/frontend/src/components/Terminal.vue +++ b/packages/frontend/src/components/Terminal.vue @@ -37,8 +37,14 @@ let selectionListenerDisposable: IDisposable | null = null; // +++ 提升声明 // --- Appearance Store --- const appearanceStore = useAppearanceStore(); -const { currentTerminalTheme, currentTerminalFontFamily, terminalBackgroundImage, currentTerminalFontSize } = storeToRefs(appearanceStore); // <-- 添加 currentTerminalFontSize - +const { + currentTerminalTheme, + currentTerminalFontFamily, + terminalBackgroundImage, + currentTerminalFontSize, + isTerminalBackgroundEnabled // <-- 新增:导入背景启用状态 +} = storeToRefs(appearanceStore); + // --- Settings Store --- const settingsStore = useSettingsStore(); // +++ 实例化设置 store +++ const { autoCopyOnSelectBoolean } = storeToRefs(settingsStore); // +++ 获取选中即复制状态 +++ @@ -289,11 +295,11 @@ onMounted(() => { } }); - // 监听背景图片变化 - watch(terminalBackgroundImage, () => { // 只监听图片 - console.log(`[Terminal Watcher] terminalBackgroundImage changed. New image: ${terminalBackgroundImage.value}`); // 添加日志确认 watcher 触发 + // 监听背景图片和启用状态的变化 + watch([terminalBackgroundImage, isTerminalBackgroundEnabled], () => { + console.log(`[Terminal Watcher] Background image or enabled status changed. Image: ${terminalBackgroundImage.value}, Enabled: ${isTerminalBackgroundEnabled.value}`); applyTerminalBackground(); - }, { immediate: true }); // 添加 immediate: true,强制立即执行一次 + }, { immediate: true }); // 强制立即执行一次 // 移除 onMounted 中的 applyTerminalBackground 调用,完全依赖 watch // applyTerminalBackground(); // 初始应用一次 @@ -400,6 +406,20 @@ defineExpose({ write, findNext, findPrevious, clearSearch }); // --- 应用终端背景 --- const applyTerminalBackground = () => { if (terminalRef.value) { + // 首先检查背景功能是否启用 + if (!isTerminalBackgroundEnabled.value) { + // 如果禁用,则移除背景并返回 + nextTick(() => { + if (terminalRef.value) { + terminalRef.value.style.backgroundImage = 'none'; + terminalRef.value.classList.remove('has-terminal-background'); + } + }); + console.log(`[Terminal ${props.sessionId}] 终端背景已禁用,移除背景。`); + return; // 提前退出 + } + + // 如果启用,再检查是否有背景图片 if (terminalBackgroundImage.value) { // --- 修改开始 --- // 使用环境变量获取后端基础 URL diff --git a/packages/frontend/src/stores/appearance.store.ts b/packages/frontend/src/stores/appearance.store.ts index 2a3396e..24d083e 100644 --- a/packages/frontend/src/stores/appearance.store.ts +++ b/packages/frontend/src/stores/appearance.store.ts @@ -77,7 +77,14 @@ export const useAppearanceStore = defineStore('appearance', () => { const size = appearanceSettings.value.editorFontSize; return typeof size === 'number' && size > 0 ? size : 14; }); - + + // 终端背景是否启用 + const isTerminalBackgroundEnabled = computed(() => { + // 提供默认值 true,如果后端没有设置或设置无效 + const enabled = appearanceSettings.value.terminalBackgroundEnabled; + return typeof enabled === 'boolean' ? enabled : true; // 默认启用 + }); + // --- Actions --- /** @@ -94,9 +101,10 @@ export const useAppearanceStore = defineStore('appearance', () => { ]); appearanceSettings.value = settingsResponse.data; allTerminalThemes.value = themesResponse.data; // 更新 allTerminalThemes - console.log('[AppearanceStore] 外观设置已加载:', appearanceSettings.value); + console.log('[AppearanceStore LOG] 外观设置已加载 (原始数据):', JSON.stringify(settingsResponse.data)); // 添加原始数据日志 + console.log(`[AppearanceStore LOG] 从后端加载的 terminalBackgroundEnabled 原始值: ${settingsResponse.data.terminalBackgroundEnabled}`); // 专门记录该值 console.log('[AppearanceStore] 所有终端主题列表已加载:', allTerminalThemes.value); - + // --- 后端返回的 activeTerminalThemeId 已经是 number | null --- // 前端不再需要设置默认主题 ID 的逻辑,后端初始化时会保证它不为 NULL // 如果后端返回 null (理论上不应发生,除非初始化失败),则 currentTerminalTheme 计算属性会回退到 defaultXtermTheme @@ -146,7 +154,8 @@ export const useAppearanceStore = defineStore('appearance', () => { if (updates.customUiTheme !== undefined) applyUiTheme(currentUiTheme.value); if (updates.pageBackgroundImage !== undefined) applyPageBackground(); // 移除 pageBackgroundOpacity 检查 // 终端相关设置由 Terminal 组件监听应用 - + // 注意:terminalBackgroundEnabled 的应用逻辑在 Terminal 组件中处理 + } catch (err: any) { console.error('更新外观设置失败:', err); throw new Error(err.response?.data?.message || err.message || '更新外观设置失败'); @@ -221,7 +230,17 @@ export const useAppearanceStore = defineStore('appearance', () => { async function setEditorFontSize(size: number) { await updateAppearanceSettings({ editorFontSize: size }); } - + + /** + * 设置终端背景是否启用 + * @param enabled 是否启用 + */ + async function setTerminalBackgroundEnabled(enabled: boolean) { + console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 调用,准备发送给后端的值: ${enabled}`); // 添加日志 + await updateAppearanceSettings({ terminalBackgroundEnabled: enabled }); + console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 更新后端调用完成。`); // 添加日志 + } + // --- 终端主题列表管理 Actions --- /** @@ -565,6 +584,7 @@ export const useAppearanceStore = defineStore('appearance', () => { setTerminalFontFamily, setTerminalFontSize, setEditorFontSize, // <-- 新增 + setTerminalBackgroundEnabled, // <-- 新增 createTerminalTheme, // 保留 updateTerminalTheme, // 保留 deleteTerminalTheme, // 保留 @@ -577,6 +597,7 @@ export const useAppearanceStore = defineStore('appearance', () => { removePageBackground, removeTerminalBackground, loadTerminalThemeData, // <-- 新增导出 + isTerminalBackgroundEnabled, // <-- 新增导出 // Visibility control isStyleCustomizerVisible, toggleStyleCustomizer, diff --git a/packages/frontend/src/types/appearance.types.ts b/packages/frontend/src/types/appearance.types.ts index 9ea2f58..26d6abc 100644 --- a/packages/frontend/src/types/appearance.types.ts +++ b/packages/frontend/src/types/appearance.types.ts @@ -11,8 +11,9 @@ export interface AppearanceSettings { terminalBackgroundImage?: string; pageBackgroundImage?: string; editorFontSize?: number; + terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用 } - + // 前端用于更新外观设置的数据结构 (对应 API 请求体) // 使用 Partial 也可以,但明确定义更清晰 export type UpdateAppearanceDto = Partial; \ No newline at end of file