diff --git a/packages/backend/src/repositories/appearance.repository.ts b/packages/backend/src/repositories/appearance.repository.ts index ec65cb4..2aace1d 100644 --- a/packages/backend/src/repositories/appearance.repository.ts +++ b/packages/backend/src/repositories/appearance.repository.ts @@ -20,6 +20,14 @@ const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): Appearanc let latestUpdatedAt = 0; let terminalBackgroundEnabledFound = false; // 标记是否在数据库中找到该设置 let terminalBackgroundOverlayOpacityFound = false; // 标记是否找到蒙版透明度设置 +let terminalTextStrokeEnabledFound = false; + let terminalTextStrokeWidthFound = false; + let terminalTextStrokeColorFound = false; + let terminalTextShadowEnabledFound = false; + let terminalTextShadowOffsetXFound = false; + let terminalTextShadowOffsetYFound = false; + let terminalTextShadowBlurFound = false; + let terminalTextShadowColorFound = false; for (const row of rows) { // 更新 latestUpdatedAt @@ -67,6 +75,38 @@ const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): Appearanc case 'remote_html_presets_url': settings.remoteHtmlPresetsUrl = row.value || null; // 如果为空字符串,则视为 null break; +case 'terminalTextStrokeEnabled': + settings.terminalTextStrokeEnabled = row.value === 'true'; + terminalTextStrokeEnabledFound = true; + break; + case 'terminalTextStrokeWidth': + settings.terminalTextStrokeWidth = parseFloat(row.value); + terminalTextStrokeWidthFound = true; + break; + case 'terminalTextStrokeColor': + settings.terminalTextStrokeColor = row.value; + terminalTextStrokeColorFound = true; + break; + case 'terminalTextShadowEnabled': + settings.terminalTextShadowEnabled = row.value === 'true'; + terminalTextShadowEnabledFound = true; + break; + case 'terminalTextShadowOffsetX': + settings.terminalTextShadowOffsetX = parseFloat(row.value); + terminalTextShadowOffsetXFound = true; + break; + case 'terminalTextShadowOffsetY': + settings.terminalTextShadowOffsetY = parseFloat(row.value); + terminalTextShadowOffsetYFound = true; + break; + case 'terminalTextShadowBlur': + settings.terminalTextShadowBlur = parseFloat(row.value); + terminalTextShadowBlurFound = true; + break; + case 'terminalTextShadowColor': + settings.terminalTextShadowColor = row.value; + terminalTextShadowColorFound = true; + break; } } @@ -90,6 +130,30 @@ const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): Appearanc : defaults.terminalBackgroundOverlayOpacity, // 否则使用默认值 terminal_custom_html: settings.terminal_custom_html ?? defaults.terminal_custom_html, remoteHtmlPresetsUrl: settings.remoteHtmlPresetsUrl ?? defaults.remoteHtmlPresetsUrl, + terminalTextStrokeEnabled: terminalTextStrokeEnabledFound + ? settings.terminalTextStrokeEnabled + : defaults.terminalTextStrokeEnabled, + terminalTextStrokeWidth: terminalTextStrokeWidthFound + ? settings.terminalTextStrokeWidth + : defaults.terminalTextStrokeWidth, + terminalTextStrokeColor: terminalTextStrokeColorFound + ? settings.terminalTextStrokeColor + : defaults.terminalTextStrokeColor, + terminalTextShadowEnabled: terminalTextShadowEnabledFound + ? settings.terminalTextShadowEnabled + : defaults.terminalTextShadowEnabled, + terminalTextShadowOffsetX: terminalTextShadowOffsetXFound + ? settings.terminalTextShadowOffsetX + : defaults.terminalTextShadowOffsetX, + terminalTextShadowOffsetY: terminalTextShadowOffsetYFound + ? settings.terminalTextShadowOffsetY + : defaults.terminalTextShadowOffsetY, + terminalTextShadowBlur: terminalTextShadowBlurFound + ? settings.terminalTextShadowBlur + : defaults.terminalTextShadowBlur, + terminalTextShadowColor: terminalTextShadowColorFound + ? settings.terminalTextShadowColor + : defaults.terminalTextShadowColor, updatedAt: latestUpdatedAt || defaults.updatedAt, // 使用最新的更新时间,否则使用默认时间戳 }; }; @@ -110,6 +174,17 @@ const getDefaultAppearanceSettings = (): Omit => { terminalBackgroundOverlayOpacity: 0.5, // 默认蒙版透明度 terminal_custom_html: '', // 默认自定义 HTML 为空字符串 remoteHtmlPresetsUrl: null, // 默认远程 HTML 预设 URL 为 null + // 终端文本描边设置默认值 + terminalTextStrokeEnabled: false, + terminalTextStrokeWidth: 1, + terminalTextStrokeColor: '#000000', + + // 终端文本阴影设置默认值 + terminalTextShadowEnabled: false, + terminalTextShadowOffsetX: 2, + terminalTextShadowOffsetY: 2, + terminalTextShadowBlur: 0, + terminalTextShadowColor: '#000000', updatedAt: Date.now(), // 提供默认时间戳 }; }; @@ -139,6 +214,14 @@ export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise< { key: 'terminalBackgroundOverlayOpacity', value: defaults.terminalBackgroundOverlayOpacity }, { key: 'terminal_custom_html', value: defaults.terminal_custom_html }, { key: 'remoteHtmlPresetsUrl', value: defaults.remoteHtmlPresetsUrl }, + { key: 'terminalTextStrokeEnabled', value: defaults.terminalTextStrokeEnabled }, + { key: 'terminalTextStrokeWidth', value: defaults.terminalTextStrokeWidth }, + { key: 'terminalTextStrokeColor', value: defaults.terminalTextStrokeColor }, + { key: 'terminalTextShadowEnabled', value: defaults.terminalTextShadowEnabled }, + { key: 'terminalTextShadowOffsetX', value: defaults.terminalTextShadowOffsetX }, + { key: 'terminalTextShadowOffsetY', value: defaults.terminalTextShadowOffsetY }, + { key: 'terminalTextShadowBlur', value: defaults.terminalTextShadowBlur }, + { key: 'terminalTextShadowColor', value: defaults.terminalTextShadowColor }, ]; try { diff --git a/packages/backend/src/settings/settings.controller.ts b/packages/backend/src/settings/settings.controller.ts index 5fd11c9..ce5a688 100644 --- a/packages/backend/src/settings/settings.controller.ts +++ b/packages/backend/src/settings/settings.controller.ts @@ -5,12 +5,50 @@ import { NotificationService } from '../services/notification.service'; import { ipBlacklistService } from '../services/ip-blacklist.service'; import { exportConnectionsAsEncryptedZip } from '../services/import-export.service'; import { UpdateSidebarConfigDto, UpdateCaptchaSettingsDto, CaptchaSettings } from '../types/settings.types'; +import { AppearanceSettings, UpdateAppearanceDto } from '../types/appearance.types'; +import { getAppearanceSettings, updateAppearanceSettings as updateAppearanceSettingsInRepo } from '../repositories/appearance.repository'; import i18next from '../i18n'; const auditLogService = new AuditLogService(); const notificationService = new NotificationService(); export const settingsController = { +/** + * 获取外观设置 + */ + async getAppearanceSettings(req: Request, res: Response): Promise { + try { + const settings = await getAppearanceSettings(); + res.json(settings); + } catch (error: any) { + console.error('获取外观设置时出错:', error); + res.status(500).json({ message: '获取外观设置失败', error: error.message }); + } + }, +/** + * 更新外观设置 + */ + async updateAppearanceSettings(req: Request, res: Response): Promise { + try { + const settingsDto: UpdateAppearanceDto = req.body; + // 可在此处添加 DTO 验证逻辑 + if (typeof settingsDto !== 'object' || settingsDto === null) { + res.status(400).json({ message: '无效的请求体,应为 JSON 对象' }); + return; + } + + const result = await updateAppearanceSettingsInRepo(settingsDto); + if (result) { + res.status(200).json({ message: '外观设置已成功更新' }); + } else { + // 如果仓库层返回 false,可能表示没有实际更改或更新失败 + res.status(200).json({ message: '外观设置未发生更改或更新失败' }); + } + } catch (error: any) { + console.error('更新外观设置时出错:', error); + res.status(500).json({ message: '更新外观设置失败', error: error.message }); + } + }, /** * 获取所有设置项 */ diff --git a/packages/backend/src/settings/settings.routes.ts b/packages/backend/src/settings/settings.routes.ts index fb9ad0c..9934a89 100644 --- a/packages/backend/src/settings/settings.routes.ts +++ b/packages/backend/src/settings/settings.routes.ts @@ -15,6 +15,11 @@ router.use(isAuthenticated); router.get('/', settingsController.getAllSettings); // GET /api/v1/settings router.put('/', settingsController.updateSettings); // PUT /api/v1/settings +// +++ 外观设置路由 +++ +// GET /api/v1/settings/appearance - 获取外观设置 +router.get('/appearance', settingsController.getAppearanceSettings); +// PUT /api/v1/settings/appearance - 更新外观设置 +router.put('/appearance', settingsController.updateAppearanceSettings); // +++ 焦点切换顺序路由 +++ // GET /api/v1/settings/focus-switcher-sequence - 获取焦点切换顺序 router.get('/focus-switcher-sequence', settingsController.getFocusSwitcherSequence); diff --git a/packages/backend/src/types/appearance.types.ts b/packages/backend/src/types/appearance.types.ts index c894a7b..423e1e3 100644 --- a/packages/backend/src/types/appearance.types.ts +++ b/packages/backend/src/types/appearance.types.ts @@ -21,6 +21,14 @@ export interface AppearanceSettings { terminalBackgroundOverlayOpacity?: number; // 终端背景蒙版透明度 (0-1) terminal_custom_html?: string; // 用户自定义终端背景 HTML remoteHtmlPresetsUrl?: string | null; // 远程 HTML 预设仓库 URL + terminalTextStrokeEnabled?: boolean; + terminalTextStrokeWidth?: number; + terminalTextStrokeColor?: string; + terminalTextShadowEnabled?: boolean; + terminalTextShadowOffsetX?: number; + terminalTextShadowOffsetY?: number; + terminalTextShadowBlur?: number; + terminalTextShadowColor?: string; updatedAt?: number; } diff --git a/packages/frontend/src/components/FileManagerContextMenu.vue b/packages/frontend/src/components/FileManagerContextMenu.vue index 1c832d1..dd6b276 100644 --- a/packages/frontend/src/components/FileManagerContextMenu.vue +++ b/packages/frontend/src/components/FileManagerContextMenu.vue @@ -49,7 +49,6 @@ const sourceConnectionId = computed(() => { // +++ 获取并转换源服务器 I return null; }); -// +++ 新增:用于菜单位置调整的 ref +++ const contextMenuRef = ref(null); const computedRenderPosition = ref({ x: props.position.x, y: props.position.y }); diff --git a/packages/frontend/src/components/Terminal.vue b/packages/frontend/src/components/Terminal.vue index 02167a4..0caeed9 100644 --- a/packages/frontend/src/components/Terminal.vue +++ b/packages/frontend/src/components/Terminal.vue @@ -51,6 +51,15 @@ const { isTerminalBackgroundEnabled, currentTerminalBackgroundOverlayOpacity, // 获取蒙版透明度 terminalCustomHTML, // 用于自定义终端背景 HTML + // --- 文字描边和阴影状态 --- + terminalTextStrokeEnabled, + terminalTextStrokeWidth, + terminalTextStrokeColor, + terminalTextShadowEnabled, + terminalTextShadowOffsetX, + terminalTextShadowOffsetY, + terminalTextShadowBlur, + terminalTextShadowColor, } = storeToRefs(appearanceStore); // --- Settings Store --- @@ -610,8 +619,57 @@ const clear = () => { }; defineExpose({ write, findNext, findPrevious, clearSearch, clear }); // 暴露 clear 方法 - - + + +// --- 文字描边和阴影 --- +const applyTerminalTextStyles = () => { + if (terminalRef.value && terminal?.element) { + const hostElement = terminalRef.value; // .terminal-inner-container + + // 清理类名 + hostElement.classList.remove('has-text-stroke', 'has-text-shadow'); + + // 文字描边 + if (terminalTextStrokeEnabled.value) { + hostElement.classList.add('has-text-stroke'); + hostElement.style.setProperty('--terminal-stroke-width', `${terminalTextStrokeWidth.value}px`); + hostElement.style.setProperty('--terminal-stroke-color', terminalTextStrokeColor.value); + } else { + hostElement.style.removeProperty('--terminal-stroke-width'); + hostElement.style.removeProperty('--terminal-stroke-color'); + } + + // 文字阴影 + if (terminalTextShadowEnabled.value) { + hostElement.classList.add('has-text-shadow'); + const shadowValue = `${terminalTextShadowOffsetX.value}px ${terminalTextShadowOffsetY.value}px ${terminalTextShadowBlur.value}px ${terminalTextShadowColor.value}`; + hostElement.style.setProperty('--terminal-shadow', shadowValue); + } else { + hostElement.style.removeProperty('--terminal-shadow'); + } + // console.log('[Terminal] Applied text styles. Stroke enabled:', terminalTextStrokeEnabled.value, 'Shadow enabled:', terminalTextShadowEnabled.value); + } +}; + +// 监听文字描边和阴影设置的变化 +watch( + [ + terminalTextStrokeEnabled, + terminalTextStrokeWidth, + terminalTextStrokeColor, + terminalTextShadowEnabled, + terminalTextShadowOffsetX, + terminalTextShadowOffsetY, + terminalTextShadowBlur, + terminalTextShadowColor, + ], + () => { + // console.log('[Terminal] Text style settings changed, applying new styles.'); + applyTerminalTextStyles(); + }, + { deep: true, immediate: true } // immediate: true 确保挂载时应用初始设置 +); + // --- 应用终端背景 --- const applyTerminalBackground = () => { // 背景应用到 terminalOuterWrapperRef @@ -751,6 +809,25 @@ watch(terminalCustomHTML, (newHtmlContent) => { z-index: 2; /* 在蒙版之上 */ } +/* 文字描边和阴影样式 */ +.terminal-inner-container.has-text-stroke :deep(.xterm-rows span), +.terminal-inner-container.has-text-stroke :deep(.xterm-rows div > span), /* 更具体地针对嵌套 span */ +.terminal-inner-container.has-text-stroke :deep(.xterm-rows div) { /* 针对直接包含文本的 div */ + -webkit-text-stroke-width: var(--terminal-stroke-width); + -webkit-text-stroke-color: var(--terminal-stroke-color); + text-stroke-width: var(--terminal-stroke-width); + text-stroke-color: var(--terminal-stroke-color); + /* 确保描边在填充之下,这样填充色仍然可见 */ + paint-order: stroke fill; + -webkit-paint-order: stroke fill; /* 兼容 WebKit */ +} + +.terminal-inner-container.has-text-shadow :deep(.xterm-rows span), +.terminal-inner-container.has-text-shadow :deep(.xterm-rows div > span), +.terminal-inner-container.has-text-shadow :deep(.xterm-rows div) { + text-shadow: var(--terminal-shadow); +} + /* 当最外层容器有背景图时,强制内部 xterm 视口和屏幕背景透明 */ .terminal-outer-wrapper.has-terminal-background .terminal-inner-container :deep(.xterm-viewport), .terminal-outer-wrapper.has-terminal-background .terminal-inner-container :deep(.xterm-screen) { diff --git a/packages/frontend/src/components/TransferProgressModal.vue b/packages/frontend/src/components/TransferProgressModal.vue index 30af084..dff471b 100644 --- a/packages/frontend/src/components/TransferProgressModal.vue +++ b/packages/frontend/src/components/TransferProgressModal.vue @@ -45,7 +45,6 @@ const formatTaskTitle = (task: TransferTask): string => { return `${sourceServerName} (${fileName} -> ${targetPath})`; }; -// --- 新增:文件传输相关 --- // 数据结构参考 interface TransferSubTask { diff --git a/packages/frontend/src/components/style-customizer/StyleCustomizerTerminalTab.vue b/packages/frontend/src/components/style-customizer/StyleCustomizerTerminalTab.vue index 374ffdd..f03d7bb 100644 --- a/packages/frontend/src/components/style-customizer/StyleCustomizerTerminalTab.vue +++ b/packages/frontend/src/components/style-customizer/StyleCustomizerTerminalTab.vue @@ -28,10 +28,28 @@ const { activeTerminalThemeId, currentTerminalFontFamily, currentTerminalFontSize, + terminalTextStrokeEnabled, + terminalTextStrokeWidth, + terminalTextStrokeColor, + terminalTextShadowEnabled, + terminalTextShadowOffsetX, + terminalTextShadowOffsetY, + terminalTextShadowBlur, + terminalTextShadowColor, } = storeToRefs(appearanceStore); const editableTerminalFontFamily = ref(''); const editableTerminalFontSize = ref(14); + +const editableTerminalTextStrokeEnabled = ref(false); +const editableTerminalTextStrokeWidth = ref(1); +const editableTerminalTextStrokeColor = ref('#000000'); + +const editableTerminalTextShadowEnabled = ref(false); +const editableTerminalTextShadowOffsetX = ref(0); +const editableTerminalTextShadowOffsetY = ref(0); +const editableTerminalTextShadowBlur = ref(0); +const editableTerminalTextShadowColor = ref('rgba(0,0,0,0.5)'); const themeSearchTerm = ref(''); const saveThemeError = ref(null); const editableTerminalThemeString = ref(''); @@ -56,11 +74,21 @@ brightBlue: #5555ff brightMagenta: #ff55ff brightCyan: #55ffff brightWhite: #ffffff`; -// Theme preview refs const initializeEditableState = () => { editableTerminalFontFamily.value = currentTerminalFontFamily.value; editableTerminalFontSize.value = currentTerminalFontSize.value; + + editableTerminalTextStrokeEnabled.value = terminalTextStrokeEnabled.value; + editableTerminalTextStrokeWidth.value = terminalTextStrokeWidth.value; + editableTerminalTextStrokeColor.value = terminalTextStrokeColor.value; + + editableTerminalTextShadowEnabled.value = terminalTextShadowEnabled.value; + editableTerminalTextShadowOffsetX.value = terminalTextShadowOffsetX.value; + editableTerminalTextShadowOffsetY.value = terminalTextShadowOffsetY.value; + editableTerminalTextShadowBlur.value = terminalTextShadowBlur.value; + editableTerminalTextShadowColor.value = terminalTextShadowColor.value; + saveThemeError.value = null; terminalThemeParseError.value = null; }; @@ -79,13 +107,27 @@ watch(currentTerminalFontSize, (newValue) => { }); // Initialize on mount and when relevant props change -watch(() => [currentTerminalFontFamily.value, currentTerminalFontSize.value], () => { - // Re-initialize only if not in the middle of editing a theme, - // as editing a theme might involve temporary font changes or different contexts. +watch( + () => [ + currentTerminalFontFamily.value, + currentTerminalFontSize.value, + terminalTextStrokeEnabled.value, + terminalTextStrokeWidth.value, + terminalTextStrokeColor.value, + terminalTextShadowEnabled.value, + terminalTextShadowOffsetX.value, + terminalTextShadowOffsetY.value, + terminalTextShadowBlur.value, + terminalTextShadowColor.value, + ], + () => { + // Re-initialize only if not in the middle of editing a theme if (!props.isEditingTheme) { - initializeEditableState(); + initializeEditableState(); } -}, { immediate: true }); + }, + { immediate: true, deep: true } +); // Methods @@ -114,6 +156,32 @@ const handleSaveTerminalFontSize = async () => { } }; +const handleSaveTerminalTextStroke = async () => { + try { + await appearanceStore.setTerminalTextStrokeEnabled(editableTerminalTextStrokeEnabled.value); + await appearanceStore.setTerminalTextStrokeWidth(Number(editableTerminalTextStrokeWidth.value)); + await appearanceStore.setTerminalTextStrokeColor(editableTerminalTextStrokeColor.value); + notificationsStore.addNotification({ type: 'success', message: t('styleCustomizer.textStrokeSettingsSaved') }); + } catch (error: any) { + console.error("保存文字描边设置失败:", error); + notificationsStore.addNotification({ type: 'error', message: t('styleCustomizer.textStrokeSettingsSaveFailed', { message: error.message }) }); + } +}; + +const handleSaveTerminalTextShadow = async () => { + try { + await appearanceStore.setTerminalTextShadowEnabled(editableTerminalTextShadowEnabled.value); + await appearanceStore.setTerminalTextShadowOffsetX(Number(editableTerminalTextShadowOffsetX.value)); + await appearanceStore.setTerminalTextShadowOffsetY(Number(editableTerminalTextShadowOffsetY.value)); + await appearanceStore.setTerminalTextShadowBlur(Number(editableTerminalTextShadowBlur.value)); + await appearanceStore.setTerminalTextShadowColor(editableTerminalTextShadowColor.value); + notificationsStore.addNotification({ type: 'success', message: t('styleCustomizer.textShadowSettingsSaved') }); + } catch (error: any) { + console.error("保存文字阴影设置失败:", error); + notificationsStore.addNotification({ type: 'error', message: t('styleCustomizer.textShadowSettingsSaveFailed', { message: error.message }) }); + } +}; + const handleApplyTheme = async (theme: TerminalTheme) => { if (!theme._id) return; const themeIdNum = parseInt(theme._id, 10); @@ -425,7 +493,69 @@ watch(() => props.isEditingTheme, (isEditing) => { -
+ +
+

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

+
+
+ + +
+ +
+
+ + +
+
+ +
+ + +
+
+
+
+ +
+
+ + +
+

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

+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ +
+
+ +

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

diff --git a/packages/frontend/src/locales/en-US.json b/packages/frontend/src/locales/en-US.json index 3dd1192..6f707f9 100644 --- a/packages/frontend/src/locales/en-US.json +++ b/packages/frontend/src/locales/en-US.json @@ -147,7 +147,23 @@ "noMatchingRemotePresetsFound": "No matching remote themes found", "editAsNewTooltip": "Edit as new custom theme", "presetTag": "Preset", - "customTag": "Custom" + "customTag": "Custom", + "textStrokeSettingsSaved": "Text stroke settings saved.", + "textStrokeSettingsSaveFailed": "Failed to save text stroke settings: {message}", + "textShadowSettingsSaved": "Text shadow settings saved.", + "textShadowSettingsSaveFailed": "Failed to save text shadow settings: {message}", + "textStrokeSettings": "Text Stroke Settings", + "enableTextStroke": "Enable Text Stroke", + "textStrokeWidth": "Stroke Width (px)", + "textStrokeColor": "Stroke Color", + "saveStrokeSettings": "Save Stroke Settings", + "textShadowSettings": "Text Shadow Settings", + "enableTextShadow": "Enable Text Shadow", + "textShadowOffsetX": "Shadow X Offset (px)", + "textShadowOffsetY": "Shadow Y Offset (px)", + "textShadowBlur": "Shadow Blur Radius (px)", + "textShadowColor": "Shadow Color", + "saveShadowSettings": "Save Shadow Settings" }, "login": { "title": "User Login", diff --git a/packages/frontend/src/locales/ja-JP.json b/packages/frontend/src/locales/ja-JP.json index 1e67e87..b53a324 100644 --- a/packages/frontend/src/locales/ja-JP.json +++ b/packages/frontend/src/locales/ja-JP.json @@ -1334,7 +1334,24 @@ "noMatchingRemotePresetsFound": "一致するリモートテーマが見つかりませんでした", "editAsNewTooltip": "新しいカスタムテーマとして編集", "presetTag": "プリセット", - "customTag": "カスタム" + "customTag": "カスタム", + "customHtmlResetSuccess": "カスタム HTML がリセットされました。", + "textStrokeSettingsSaved": "文字の縁取り設定が保存されました。", + "textStrokeSettingsSaveFailed": "文字の縁取り設定の保存に失敗しました: {message}", + "textShadowSettingsSaved": "文字の影設定が保存されました。", + "textShadowSettingsSaveFailed": "文字の影設定の保存に失敗しました: {message}", + "textStrokeSettings": "文字の縁取り設定", + "enableTextStroke": "文字の縁取りを有効にする", + "textStrokeWidth": "縁取りの太さ (px)", + "textStrokeColor": "縁取りの色", + "saveStrokeSettings": "縁取り設定を保存", + "textShadowSettings": "文字の影設定", + "enableTextShadow": "文字の影を有効にする", + "textShadowOffsetX": "影の X オフセット (px)", + "textShadowOffsetY": "影の Y オフセット (px)", + "textShadowBlur": "影のぼかし半径 (px)", + "textShadowColor": "影の色", + "saveShadowSettings": "影の設定を保存" }, "tags": { diff --git a/packages/frontend/src/locales/zh-CN.json b/packages/frontend/src/locales/zh-CN.json index 547e0b7..d8d4011 100644 --- a/packages/frontend/src/locales/zh-CN.json +++ b/packages/frontend/src/locales/zh-CN.json @@ -148,7 +148,23 @@ "editAsNewTooltip": "编辑为新自定义主题", "presetTag": "预设", "customTag": "自定义", - "customHtmlResetSuccess":"自定义 HTML 已重置。" + "customHtmlResetSuccess":"自定义 HTML 已重置。", + "textStrokeSettingsSaved": "文字描边设置已保存。", + "textStrokeSettingsSaveFailed": "保存文字描边设置失败: {message}", + "textShadowSettingsSaved": "文字阴影设置已保存。", + "textShadowSettingsSaveFailed": "保存文字阴影设置失败: {message}", + "textStrokeSettings": "文字描边设置", + "enableTextStroke": "启用文字描边", + "textStrokeWidth": "描边粗细 (px)", + "textStrokeColor": "描边颜色", + "saveStrokeSettings": "保存描边设置", + "textShadowSettings": "文字阴影设置", + "enableTextShadow": "启用文字阴影", + "textShadowOffsetX": "阴影 X 偏移 (px)", + "textShadowOffsetY": "阴影 Y 偏移 (px)", + "textShadowBlur": "阴影模糊半径 (px)", + "textShadowColor": "阴影颜色", + "saveShadowSettings": "保存阴影设置" }, "login": { "title": "用户登录", diff --git a/packages/frontend/src/stores/appearance.store.ts b/packages/frontend/src/stores/appearance.store.ts index d73253c..db695c3 100644 --- a/packages/frontend/src/stores/appearance.store.ts +++ b/packages/frontend/src/stores/appearance.store.ts @@ -145,6 +145,34 @@ export const useAppearanceStore = defineStore('appearance', () => { // 获取终端自定义 CSS const terminalCustomHTML = computed(() => appearanceSettings.value.terminal_custom_html ?? null); + // 文字描边设置 + const terminalTextStrokeEnabled = computed(() => { + return appearanceSettings.value.terminalTextStrokeEnabled ?? false; + }); + const terminalTextStrokeWidth = computed(() => { + return appearanceSettings.value.terminalTextStrokeWidth ?? 1; + }); + const terminalTextStrokeColor = computed(() => { + return appearanceSettings.value.terminalTextStrokeColor ?? '#000000'; + }); + + // 文字阴影设置 + const terminalTextShadowEnabled = computed(() => { + return appearanceSettings.value.terminalTextShadowEnabled ?? false; + }); + const terminalTextShadowOffsetX = computed(() => { + return appearanceSettings.value.terminalTextShadowOffsetX ?? 0; + }); + const terminalTextShadowOffsetY = computed(() => { + return appearanceSettings.value.terminalTextShadowOffsetY ?? 0; + }); + const terminalTextShadowBlur = computed(() => { + return appearanceSettings.value.terminalTextShadowBlur ?? 0; + }); + const terminalTextShadowColor = computed(() => { + return appearanceSettings.value.terminalTextShadowColor ?? 'rgba(0,0,0,0.5)'; + }); + // --- Actions --- /** @@ -202,7 +230,15 @@ export const useAppearanceStore = defineStore('appearance', () => { async function updateAppearanceSettings(updates: UpdateAppearanceDto) { try { // 移除预设主题闪烁修复逻辑,不再需要 - const response = await apiClient.put('/appearance', updates); // 使用 apiClient + + // Construct the full payload to send to the backend + // This includes all current settings merged with the specific updates + const payloadToSend: Partial = { + ...appearanceSettings.value, // Start with all current settings from the store + ...updates // Apply the specific changes passed to this function + }; + + const response = await apiClient.put('/appearance', payloadToSend); // 使用 apiClient, 发送合并后的 payload // 使用后端返回的最新设置更新本地状态 appearanceSettings.value = response.data; console.log('[AppearanceStore] 外观设置已更新:', appearanceSettings.value); @@ -338,6 +374,34 @@ export const useAppearanceStore = defineStore('appearance', () => { } } + // --- 文字描边 Actions --- + async function setTerminalTextStrokeEnabled(enabled: boolean) { + await updateAppearanceSettings({ terminalTextStrokeEnabled: enabled }); + } + async function setTerminalTextStrokeWidth(width: number) { + await updateAppearanceSettings({ terminalTextStrokeWidth: width }); + } + async function setTerminalTextStrokeColor(color: string) { + await updateAppearanceSettings({ terminalTextStrokeColor: color }); + } + + // --- 文字阴影 Actions --- + async function setTerminalTextShadowEnabled(enabled: boolean) { + await updateAppearanceSettings({ terminalTextShadowEnabled: enabled }); + } + async function setTerminalTextShadowOffsetX(offset: number) { + await updateAppearanceSettings({ terminalTextShadowOffsetX: offset }); + } + async function setTerminalTextShadowOffsetY(offset: number) { + await updateAppearanceSettings({ terminalTextShadowOffsetY: offset }); + } + async function setTerminalTextShadowBlur(blur: number) { + await updateAppearanceSettings({ terminalTextShadowBlur: blur }); + } + async function setTerminalTextShadowColor(color: string) { + await updateAppearanceSettings({ terminalTextShadowColor: color }); + } + // --- 终端主题列表管理 Actions --- /** @@ -847,13 +911,33 @@ export const useAppearanceStore = defineStore('appearance', () => { uploadTerminalBackground, setTerminalBackgroundOverlayOpacity, setTerminalCustomHTML, // 设置终端自定义 HTML + // 文字描边 Actions + setTerminalTextStrokeEnabled, + setTerminalTextStrokeWidth, + setTerminalTextStrokeColor, + // 文字阴影 Actions + setTerminalTextShadowEnabled, + setTerminalTextShadowOffsetX, + setTerminalTextShadowOffsetY, + setTerminalTextShadowBlur, + setTerminalTextShadowColor, removePageBackground, removeTerminalBackground, - loadTerminalThemeData, + loadTerminalThemeData, isTerminalBackgroundEnabled, terminalCustomHTML, // 获取终端自定义 HTML + // 文字描边 Computed + terminalTextStrokeEnabled, + terminalTextStrokeWidth, + terminalTextStrokeColor, + // 文字阴影 Computed + terminalTextShadowEnabled, + terminalTextShadowOffsetX, + terminalTextShadowOffsetY, + terminalTextShadowBlur, + terminalTextShadowColor, startTerminalThemePreview, - stopTerminalThemePreview, + stopTerminalThemePreview, // Visibility control isStyleCustomizerVisible, toggleStyleCustomizer, diff --git a/packages/frontend/src/types/appearance.types.ts b/packages/frontend/src/types/appearance.types.ts index 4766c6d..f81db70 100644 --- a/packages/frontend/src/types/appearance.types.ts +++ b/packages/frontend/src/types/appearance.types.ts @@ -17,6 +17,18 @@ export interface AppearanceSettings { terminalBackgroundOverlayOpacity?: number; // 终端背景蒙版透明度 (0-1) terminal_custom_html?: string | null; // 终端自定义 HTML remoteHtmlPresetsUrl?: string | null; // 远程 HTML 主题仓库链接 + + // 文字描边 + terminalTextStrokeEnabled?: boolean; + terminalTextStrokeWidth?: number; + terminalTextStrokeColor?: string; + + // 文字阴影 + terminalTextShadowEnabled?: boolean; + terminalTextShadowOffsetX?: number; + terminalTextShadowOffsetY?: number; + terminalTextShadowBlur?: number; + terminalTextShadowColor?: string; } // 前端用于更新外观设置的数据结构 (对应 API 请求体)