fix(frontend): preserve ansi colors in terminal effects

Limit stroke and shadow effects to default-foreground terminal text so
ANSI-colored output keeps its intended visual meaning.

Also enable terminal text stroke and shadow by default in frontend and
backend fallbacks, and align the dark preset and default xterm theme
with the new green night palette.
This commit is contained in:
yinjianm
2026-03-25 06:20:33 +08:00
parent d74e84c87b
commit 7e430cb983
18 changed files with 338 additions and 75 deletions
+4
View File
@@ -12,3 +12,7 @@
- 方案: [202603250532_quickcommands-theme-alignment](archive/2026-03/202603250532_quickcommands-theme-alignment/)
- **[frontend]**: 修复终端标签切换后的视口恢复逻辑,贴底终端重新激活后自动贴底,上翻终端保留历史位置 — by yinjianm
- 方案: [202603250547_terminal-tab-scroll-restore](archive/2026-03/202603250547_terminal-tab-scroll-restore/)
### 新增
- **[frontend]**: 将“黑暗模式”预设与终端默认主题统一调整为黑绿夜间风格 — by yinjianm
- 方案: [202603250603_dark-green-night-theme](archive/2026-03/202603250603_dark-green-night-theme/)
+1 -1
View File
@@ -31,7 +31,7 @@
```yaml
kb_version: 2.3.7
最后更新: 2026-03-25 05:52
最后更新: 2026-03-25 06:06
模块数量: 4
待执行方案: 0
```
@@ -0,0 +1 @@
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"done":5,"percent":100,"current":"已完成黑绿夜间主题预设调整与验证,等待归档","updated_at":"2026-03-25 06:06:54"}
@@ -0,0 +1,66 @@
# 变更提案: dark-green-night-theme
## 元信息
```yaml
类型: 功能增强
方案类型: implementation
优先级: P2
状态: 已完成
状态说明: 已完成黑绿夜间主题预设调整与前后端构建验证,待归档
创建: 2026-03-25
```
---
## 1. 需求
### 背景
当前外观里的“黑暗模式”仍偏通用深色,终端默认主题也没有完全贴近你提供的参考图,整体缺少黑绿监控/终端风格的一致性。
### 目标
- 将界面“黑暗模式”预设改成参考图这种黑绿夜间风格。
- 同步调整终端默认主题,使终端、工作区和状态面板的视觉方向一致。
- 保持浅色默认主题不变,只改黑暗模式预设与终端默认主题。
### 约束条件
```yaml
范围约束: 仅调整主题预设与默认主题定义,不改业务交互结构
一致性约束: 前后端默认主题定义保持一致,避免重置或回退后前后端风格漂移
体验约束: 保持终端文本可读性,避免纯荧光绿导致长时间阅读疲劳
验证约束: 以前端构建通过为基础,运行态视觉以本地手验为主
```
### 验收标准
- [ ] “黑暗模式”按钮应用后,界面主色调变为黑绿夜间风格
- [ ] 终端默认主题与黑暗模式预设在氛围上保持一致
- [ ] 不影响浅色默认主题和现有主题编辑功能
- [ ] 前端构建通过
---
## 2. 方案
### 技术方案
保留现有主题系统结构,只调整预设值。前端 `StyleCustomizerUiTab.vue` 中的 `darkModeTheme` 改为参考图对应的黑绿 UI 配色;前端 `default-themes.ts` 中的 `defaultXtermTheme` 改为黑底、绿系高亮、青绿辅助色的终端主题;后端 `config/default-themes.ts` 同步更新相同默认定义,确保初始化和回退逻辑一致。
### 影响范围
```yaml
涉及模块:
- frontend: 黑暗模式预设、终端默认主题
- backend: 默认主题定义镜像副本
预计变更文件: 3
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 绿色过亮导致正文和状态数字刺眼 | 低 | 正文采用浅灰绿,强调色才使用亮绿 |
| UI 黑绿预设与终端主题气质不一致 | 低 | 统一使用深炭黑背景 + 绿色强调 + 青绿辅助色 |
| 只改前端导致后端初始化后的默认值不一致 | 低 | 同步更新 backend 对应默认主题文件 |
### 实施结果
- `StyleCustomizerUiTab.vue``darkModeTheme` 已改为黑绿夜间风格,主背景、边框、按钮、图标 hover 和状态色全部切到深炭黑 + 绿色强调体系。
- 前后端 `defaultXtermTheme` 均改为黑底、浅灰绿正文、绿系高亮与青绿辅助色的终端默认主题。
- 浅色默认 `defaultUiTheme` 保持不变,仍保留当前默认浅色体系。
- `npm run build --workspace @nexus-terminal/frontend``npm run build --workspace @nexus-terminal/backend` 均通过。
- 受本地 `/appearance` 保存接口现场限制,本轮未在浏览器里直接点击“黑暗模式”做最终视觉手验。
@@ -0,0 +1,51 @@
# 任务清单: dark-green-night-theme
```yaml
@feature: dark-green-night-theme
@created: 2026-03-25
@status: completed
@mode: R2
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 方案与范围确认
- [√] 1.1 创建黑绿夜间主题方案包并锁定前后端默认主题定义范围 | depends_on: []
### 2. 主题预设调整
- [√] 2.1 统一黑暗模式预设的 UI 配色变量为黑绿夜间风格 | depends_on: [1.1]
- [√] 2.2 统一前后端终端默认主题为黑绿终端风格 | depends_on: [2.1]
### 3. 验证与同步
- [√] 3.1 运行前后端最小验证并记录结果 | depends_on: [2.2]
- [√] 3.2 更新 `.helloagents` 文档与变更记录 | depends_on: [3.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-03-25 06:03 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为黑暗模式预设与终端默认主题 |
| 2026-03-25 06:04 | 2.1 / 2.2 | 完成 | 统一黑暗模式预设与前后端默认终端主题为黑绿夜间风格 |
| 2026-03-25 06:05 | 3.1 | 完成 | 前端与后端构建均通过 |
| 2026-03-25 06:06 | 3.2 | 完成 | 更新 frontend/backend 模块文档并准备归档 |
---
## 执行备注
- 本次不改浅色默认主题,也不改主题编辑器交互。
- 后端默认主题定义当前主要用于初始化与一致性镜像,即使前端直接消费自己的默认值,也需要同步。
- 真实视觉观感仍建议由你在已登录环境里点击一次“黑暗模式”做最终目视确认。
+2
View File
@@ -10,6 +10,7 @@
| 202603250317 | ghcr-docker-publish | implementation | workspace-root | ghcr-docker-publish#D001 | ✅完成 |
| 202603250532 | quickcommands-theme-alignment | implementation | frontend | - | ✅完成 |
| 202603250547 | terminal-tab-scroll-restore | implementation | frontend | - | ✅完成 |
| 202603250603 | dark-green-night-theme | implementation | frontend, backend | - | ✅完成 |
| 202603251200 | workspace-workbench-monitor | implementation | frontend, backend | workspace-workbench-monitor#D001 | ✅完成 |
## 按月归档
@@ -18,6 +19,7 @@
- [202603250317_ghcr-docker-publish](./2026-03/202603250317_ghcr-docker-publish/) - 新增 GHCR 镜像发布 workflow 并切换 compose 镜像来源
- [202603250532_quickcommands-theme-alignment](./2026-03/202603250532_quickcommands-theme-alignment/) - 统一快捷指令视图按钮主题适配,移除残留硬编码 hover 色值
- [202603250547_terminal-tab-scroll-restore](./2026-03/202603250547_terminal-tab-scroll-restore/) - 修复终端标签切换后的贴底/历史滚动恢复逻辑
- [202603250603_dark-green-night-theme](./2026-03/202603250603_dark-green-night-theme/) - 将黑暗模式预设与终端默认主题统一调整为黑绿夜间风格
- [202603251200_workspace-workbench-monitor](./2026-03/202603251200_workspace-workbench-monitor/) - `/workspace` 改为三栏 Workbench 布局,并新增开机累计流量监控
## 结果状态说明
+5
View File
@@ -39,6 +39,11 @@
**行为**: 按 `controller/service/repository/routes` 的分层模式组织连接、通知、设置、快速命令、主题等功能。
**结果**: 新增后端能力时应优先延续现有业务域目录结构,而不是在入口文件堆叠逻辑。
### 外观默认值
**条件**: 数据库初始化、外观设置重置或前后端默认主题定义调整。
**行为**: `appearance.repository.ts` 负责写入默认 UI 外观设置,`config/default-themes.ts` 保持与前端同名默认主题定义一致,作为默认外观与终端主题的镜像基线。
**结果**: 前后端在默认主题和回退值上保持一致,避免前端回退与后端初始化出现风格漂移。
### 状态监控
**条件**: 前端工作区通过 WebSocket 订阅服务器状态。
**行为**: `StatusMonitorService` 通过 SSH 读取 `free``df``/proc/stat``/proc/net/dev`,同时计算瞬时网速与默认网卡自开机以来的累计上下行字节数。
+2 -2
View File
@@ -36,8 +36,8 @@
### 工作区交互
**条件**: 用户进入 `/workspace` 或相关管理页面。
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button``text-button-text``hover:bg-button-hover``hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复。
**结果**: 页面逻辑分散在 `views/``components/``stores/``composables/`,其中布局与交互微调优先落在 `layout.store.ts``LayoutRenderer.vue``WorkspaceWorkbench.vue``QuickCommandsView.vue``Terminal.vue`
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button``text-button-text``hover:bg-button-hover``hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复;样式编辑器中的“黑暗模式”预设与 `defaultXtermTheme` 已统一为黑绿夜间风格
**结果**: 页面逻辑分散在 `views/``components/``stores/``composables/`,其中布局与交互微调优先落在 `layout.store.ts``LayoutRenderer.vue``WorkspaceWorkbench.vue``QuickCommandsView.vue``Terminal.vue``StyleCustomizerUiTab.vue``features/appearance/config/default-themes.ts`
## 依赖关系
@@ -0,0 +1 @@
{"status":"in_progress","completed":1,"failed":0,"pending":4,"total":5,"done":1,"percent":20,"current":"分析 xterm 前景色输出规则并准备修复 Terminal.vue 的 ANSI 彩色文字效果","updated_at":"2026-03-25 06:14:00"}
@@ -0,0 +1,60 @@
# 变更提案: terminal-ansi-color-effects
## 元信息
```yaml
类型: 缺陷修复
方案类型: implementation
优先级: P1
状态: 进行中
状态说明: 已确认 ANSI 彩色字符需绕过描边/阴影,同时将终端文字效果默认开关改为开启
创建: 2026-03-25
```
---
## 1. 需求
### 背景
当前终端在启用文字描边或阴影后,ANSI 彩色输出视觉上会被统一的描边/阴影效果“压平”,看起来像所有字体都接近同一种颜色。用户希望保留黑绿主题下默认前景文字的装饰效果,但不能破坏终端原本的 ANSI 彩色语义。
### 目标
- 修复终端 ANSI 彩色字符被统一描边/阴影覆盖的问题。
- 仅让默认前景文字继续保留描边/阴影效果。
- 将相关终端文字效果的默认开关改为开启。
### 约束条件
```yaml
范围约束: 优先限制在前端终端组件与外观默认值配置,不改 SSH 数据流和终端主题结构
实现约束: 不改变 ANSI 颜色来源,只调整文字效果的应用范围
兼容约束: 兼容 xterm DOM renderer 当前的 class/style 输出方式
数据约束: 默认值变更只影响未保存该设置的新环境,不强行覆盖已有用户配置
```
### 验收标准
- [ ] 启用终端文字描边/阴影后,ANSI 彩色字符仍保留原有颜色层次
- [ ] 默认前景色文本仍可保留描边/阴影效果
- [ ] 终端文字效果默认开关改为开启
- [ ] 前端构建通过
---
## 2. 方案
### 技术方案
`Terminal.vue` 中收窄文字描边/阴影 CSS 选择器,不再对整行容器统一施加样式,而是仅对“未显式设置 ANSI 前景色”的字符片段应用样式。识别规则基于 xterm DOM renderer 的输出特征:ANSI 调色板颜色会产生 `xterm-fg-*` class,RGB 颜色会在字符节点上带 `style="color:..."`。同时在后端外观默认配置和前端缺省回退值中,把终端文字描边/阴影开关默认值改为开启。
### 影响范围
```yaml
涉及模块:
- frontend: Terminal.vue 终端文字效果应用逻辑
- frontend: appearance.store.ts 前端外观缺省回退
- backend: appearance.repository.ts 外观默认值
预计变更文件: 3
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| xterm 默认前景与 ANSI 颜色识别条件不完整 | 中 | 依据 xterm DOM renderer 实际 class/style 规则设计选择器,避免整行样式覆盖 |
| 移除行级样式后默认文本效果范围缩小 | 低 | 仅保留到字符 span 级别,确保默认文本仍有描边/阴影 |
| 默认开关改为开启影响旧用户认知 | 低 | 仅修改默认值与前端 fallback,不覆盖数据库中已有显式设置 |
@@ -0,0 +1,47 @@
# 任务清单: terminal-ansi-color-effects
```yaml
@feature: terminal-ansi-color-effects
@created: 2026-03-25
@status: in_progress
@mode: R2
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 1 | 0 | 0 | 5 |
---
## 任务列表
### 1. 方案与范围确认
- [√] 1.1 创建 ANSI 彩色字符文字效果修复方案包并锁定终端组件/外观默认值范围 | depends_on: []
### 2. 终端文字效果修复
- [ ] 2.1 盘点 xterm DOM renderer 的前景色输出规则并收窄 `Terminal.vue` 的文字效果选择器 | depends_on: [1.1]
- [ ] 2.2 在 `Terminal.vue` 中实现“ANSI 彩色字符跳过描边/阴影、默认前景文字保留效果”的渲染规则 | depends_on: [2.1]
### 3. 默认值与验证
- [ ] 3.1 调整前后端终端文字效果默认开关为开启 | depends_on: [2.2]
- [ ] 3.2 运行前端最小验证并同步 `.helloagents` 文档与变更记录 | depends_on: [3.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-03-25 06:14 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为 Terminal.vue 的 ANSI 彩色文字效果修复与外观默认值调整 |
---
## 执行备注
- 目标不是关闭终端文字效果,而是把效果限定在默认前景文字上。
- 默认开关改为开启仅影响未保存该项的新环境;已有显式设置保持原值。
@@ -185,12 +185,12 @@ const getDefaultAppearanceSettings = (): Omit<AppearanceSettings, '_id'> => {
terminal_custom_html: '', // 默认自定义 HTML 为空字符串
remoteHtmlPresetsUrl: null, // 默认远程 HTML 预设 URL 为 null
// 终端文本描边设置默认值
terminalTextStrokeEnabled: false,
terminalTextStrokeEnabled: true,
terminalTextStrokeWidth: 1,
terminalTextStrokeColor: '#000000',
// 终端文本阴影设置默认值
terminalTextShadowEnabled: false,
terminalTextShadowEnabled: true,
terminalTextShadowOffsetX: 2,
terminalTextShadowOffsetY: 2,
terminalTextShadowBlur: 0,
+20 -20
View File
@@ -3,26 +3,26 @@ import type { ITheme } from 'xterm';
// 默认 xterm 主题
// (与 backend/src/config/default-themes.ts 中的定义保持一致)
export const defaultXtermTheme: ITheme = {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#d4d4d4',
selectionBackground: '#264f78',
black: '#000000',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
magenta: '#bc3fbc',
cyan: '#11a8cd',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightMagenta: '#d670d6',
brightCyan: '#29b8db',
brightWhite: '#e5e5e5'
background: '#111411',
foreground: '#d8e6d2',
cursor: '#8ff7a7',
selectionBackground: '#21462b',
black: '#111411',
red: '#d96c5f',
green: '#59d971',
yellow: '#d8ba52',
blue: '#4ca89f',
magenta: '#6f8f8a',
cyan: '#4ec9b0',
white: '#d8e6d2',
brightBlack: '#5d685b',
brightRed: '#f07d6c',
brightGreen: '#7df79b',
brightYellow: '#ead17a',
brightBlue: '#76d0c8',
brightMagenta: '#92b8b2',
brightCyan: '#7ae7d6',
brightWhite: '#f3fff0'
};
// 默认 UI 主题 (CSS 变量)
+32 -6
View File
@@ -37,6 +37,7 @@ let observedElement: HTMLElement | null = null; // +++ Store the observed elemen
let debounceTimer: number | null = null; // 用于防抖的计时器 ID
let selectionListenerDisposable: IDisposable | null = null; // +++ 提升声明并添加类型 +++
let scrollListenerDisposable: IDisposable | null = null;
let renderListenerDisposable: IDisposable | null = null;
let lastResizeObserverWidth = 0;
let lastResizeObserverHeight = 0;
const RESIZE_THRESHOLD = 0.5; // px
@@ -132,6 +133,25 @@ const restoreViewportSnapshot = (term: Terminal, snapshot?: TerminalViewportSnap
syncViewportTracking(term);
};
const markExplicitForegroundSpans = () => {
const hostElement = terminalRef.value;
if (!hostElement) {
return;
}
const rowSpans = hostElement.querySelectorAll<HTMLElement>('.xterm-rows span');
rowSpans.forEach((span) => {
const hasExplicitForeground =
span.className.includes('xterm-fg-') || span.style.color !== '';
if (hasExplicitForeground) {
span.dataset.explicitForeground = 'true';
} else {
delete span.dataset.explicitForeground;
}
});
};
// 防抖处理由 ResizeObserver 触发的 resize 事件
const debouncedEmitResize = debounce((term: Terminal) => {
if (term && props.isActive) { // 仅当标签仍处于活动状态时才发送防抖后的 resize
@@ -311,6 +331,7 @@ onMounted(() => {
// terminal.open() 同步执行完毕后,可以认为 Xterm 已尝试附加到 DOM
isTerminalDomReady.value = true; // +++ 直接在此处设置 DOM 准备就绪状态 +++
console.log(`[Terminal ${props.sessionId}] Xterm open() called, considering DOM ready for initial style checks.`);
markExplicitForegroundSpans();
// 适应容器大小
fitAndEmitResizeNow(terminal);
@@ -320,6 +341,10 @@ onMounted(() => {
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data });
});
renderListenerDisposable = terminal.onRender(() => {
markExplicitForegroundSpans();
});
scrollListenerDisposable = terminal.onScroll(() => {
if (terminal && props.isActive) {
syncViewportTracking(terminal);
@@ -665,6 +690,10 @@ onBeforeUnmount(() => {
scrollListenerDisposable.dispose();
}
if (renderListenerDisposable) {
renderListenerDisposable.dispose();
}
// 确保在卸载时移除右键监听器
removeContextMenuListener();
@@ -739,6 +768,7 @@ const applyTerminalTextStyles = () => {
} else {
hostElement.style.removeProperty('--terminal-shadow');
}
markExplicitForegroundSpans();
// console.log('[Terminal] Applied text styles. Stroke enabled:', terminalTextStrokeEnabled.value, 'Shadow enabled:', terminalTextShadowEnabled.value);
}
};
@@ -814,9 +844,7 @@ watchEffect(() => {
}
/* 文字描边和阴影样式 */
.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 */
.terminal-inner-container.has-text-stroke :deep(.xterm-rows span:not([data-explicit-foreground="true"])) {
-webkit-text-stroke-width: var(--terminal-stroke-width);
-webkit-text-stroke-color: var(--terminal-stroke-color);
text-stroke-width: var(--terminal-stroke-width);
@@ -826,9 +854,7 @@ watchEffect(() => {
-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) {
.terminal-inner-container.has-text-shadow :deep(.xterm-rows span:not([data-explicit-foreground="true"])) {
text-shadow: var(--terminal-shadow);
}
@@ -41,11 +41,11 @@ const {
const editableTerminalFontFamily = ref('');
const editableTerminalFontSize = ref(14);
const editableTerminalTextStrokeEnabled = ref(false);
const editableTerminalTextStrokeEnabled = ref(true);
const editableTerminalTextStrokeWidth = ref(1);
const editableTerminalTextStrokeColor = ref('#000000');
const editableTerminalTextShadowEnabled = ref(false);
const editableTerminalTextShadowEnabled = ref(true);
const editableTerminalTextShadowOffsetX = ref(0);
const editableTerminalTextShadowOffsetY = ref(0);
const editableTerminalTextShadowBlur = ref(0);
@@ -725,4 +725,4 @@ watch(() => props.isEditingTheme, (isEditing) => {
<button @click="handleSaveEditingTheme" class="px-4 md:px-5 py-2 rounded font-bold border border-button bg-button text-button-text hover:bg-button-hover hover:border-button-hover disabled:opacity-60 disabled:cursor-not-allowed text-sm md:text-base">{{ t('common.save') }}</button>
</div>
</section>
</template>
</template>
@@ -18,30 +18,30 @@ const themeParseError = ref<string | null>(null);
// 定义黑暗模式主题变量
const darkModeTheme = {
'--app-bg-color': '#212529',
'--text-color': '#e9ecef',
'--text-color-secondary': '#adb5bd',
'--border-color': '#495057',
'--link-color': '#BB86FC',
'--link-hover-color': '#D1A9FF',
'--link-active-color': '#A06CD5',
'--link-active-bg-color': 'rgba(160, 108, 213, 0.2)',
'--app-bg-color': '#161816',
'--text-color': '#d8e6d2',
'--text-color-secondary': '#8d9887',
'--border-color': '#2b332c',
'--link-color': '#37c66a',
'--link-hover-color': '#62e38e',
'--link-active-color': '#45d978',
'--link-active-bg-color': 'rgba(69, 217, 120, 0.14)',
'--nav-item-active-bg-color': 'var(--link-active-bg-color)',
'--header-bg-color': '#343a40',
'--footer-bg-color': '#343a40',
'--button-bg-color': 'var(--link-active-color)',
'--button-text-color': '#ffffff',
'--button-hover-bg-color': '#8E44AD',
'--header-bg-color': '#1b1f1b',
'--footer-bg-color': '#1b1f1b',
'--button-bg-color': '#203126',
'--button-text-color': '#9aefad',
'--button-hover-bg-color': '#294232',
'--icon-color': 'var(--text-color-secondary)',
'--icon-hover-color': 'var(--link-hover-color)',
'--split-line-color': 'var(--border-color)',
'--split-line-hover-color': 'var(--border-color)',
'--split-line-hover-color': '#3b6045',
'--input-focus-border-color': 'var(--link-active-color)',
'--input-focus-glow': 'var(--link-active-color)',
'--overlay-bg-color': 'rgba(0, 0, 0, 0.8)',
'--color-success': '#5cb85c',
'--color-error': '#d9534f',
'--color-warning': '#f0ad4e',
'--overlay-bg-color': 'rgba(0, 0, 0, 0.84)',
'--color-success': '#3fdc78',
'--color-error': '#d86a4d',
'--color-warning': '#d1a445',
'--font-family-sans-serif': 'sans-serif',
'--base-padding': '1rem',
'--base-margin': '0.5rem'
@@ -265,4 +265,4 @@ defineExpose({
</div>
<p v-if="themeParseError" class="text-error-text bg-error/10 border border-error/30 px-3 py-2 rounded text-sm mt-2">{{ themeParseError }}</p>
</section>
</template>
</template>
@@ -3,26 +3,26 @@ import type { ITheme } from 'xterm';
// 默认 xterm 主题
// (与 backend/src/config/default-themes.ts 中的定义保持一致)
export const defaultXtermTheme: ITheme = {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#d4d4d4',
selectionBackground: '#264f78', // 使用 selectionBackground
black: '#000000',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
magenta: '#bc3fbc',
cyan: '#11a8cd',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightMagenta: '#d670d6',
brightCyan: '#29b8db',
brightWhite: '#e5e5e5'
background: '#111411',
foreground: '#d8e6d2',
cursor: '#8ff7a7',
selectionBackground: '#21462b',
black: '#111411',
red: '#d96c5f',
green: '#59d971',
yellow: '#d8ba52',
blue: '#4ca89f',
magenta: '#6f8f8a',
cyan: '#4ec9b0',
white: '#d8e6d2',
brightBlack: '#5d685b',
brightRed: '#f07d6c',
brightGreen: '#7df79b',
brightYellow: '#ead17a',
brightBlue: '#76d0c8',
brightMagenta: '#92b8b2',
brightCyan: '#7ae7d6',
brightWhite: '#f3fff0'
};
// 默认 UI 主题 (CSS 变量)
@@ -154,7 +154,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
// 文字描边设置
const terminalTextStrokeEnabled = computed<boolean>(() => {
return appearanceSettings.value.terminalTextStrokeEnabled ?? false;
return appearanceSettings.value.terminalTextStrokeEnabled ?? true;
});
const terminalTextStrokeWidth = computed<number>(() => {
return appearanceSettings.value.terminalTextStrokeWidth ?? 1;
@@ -165,7 +165,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
// 文字阴影设置
const terminalTextShadowEnabled = computed<boolean>(() => {
return appearanceSettings.value.terminalTextShadowEnabled ?? false;
return appearanceSettings.value.terminalTextShadowEnabled ?? true;
});
const terminalTextShadowOffsetX = computed<number>(() => {
return appearanceSettings.value.terminalTextShadowOffsetX ?? 0;