fix(terminal): WebSSH 固定黑底白字 ANSI 终端配色
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run

不再跟随 Vuetify 浅色主题,xterm 始终使用经典黑底白字与 16 色输出。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Agent
2026-06-08 12:09:00 +08:00
parent 09d4613e46
commit 835ef2c8ea
6 changed files with 107 additions and 115 deletions
@@ -0,0 +1,31 @@
# 审计 — WebSSH 黑底白字 ANSI 主题(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-webssh-classic-terminal-theme.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 主题 | `frontend/src/composables/terminal/xtermTheme.ts` |
| 会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 页面 | `frontend/src/pages/TerminalPage.vue` |
| 区域 | `frontend/src/components/terminal/TerminalArea.vue` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 固定黑底白字 | PASS — 不随 Vuetify 浅色切换 |
| H2 | ANSI 16 色 | PASS — drawBoldTextInBrightColors |
| H3 | 无后端变更 | PASS — 纯前端 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] frontend vite build
- [x] changelog
@@ -0,0 +1,24 @@
# 2026-06-08 — WebSSH 终端固定黑底白字 ANSI 配色
## 摘要
WebSSH xterm 不再跟随 Vuetify 浅色主题,固定经典黑底白字 + 16 色 ANSI。
## 动机
浅色模式下终端呈白底深字,不符合 SSH 终端习惯;用户要求黑底白字带字符颜色。
## 涉及文件
- `frontend/src/composables/terminal/xtermTheme.ts`(新增)
- `frontend/src/composables/terminal/useTerminalSessions.ts`
- `frontend/src/pages/TerminalPage.vue`
- `frontend/src/components/terminal/TerminalArea.vue`
## 验证
```bash
cd frontend && npm run build
```
浏览器:终端页连接 SSH`ls --color=auto``grep --color` 等应显示彩色输出。
@@ -92,6 +92,7 @@ defineExpose({ containerRef })
position: relative;
min-height: 0;
overflow: hidden;
background: #000000;
}
.terminal-area__overlay {
z-index: 4;
@@ -1,6 +1,5 @@
import { ref, computed, watch, nextTick, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTheme } from 'vuetify'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
@@ -25,13 +24,13 @@ import {
} from './types'
import { useTerminalSettings } from './useTerminalSettings'
import { useTerminalCmdBar } from './useTerminalCmdBar'
import { XTERM_CLASSIC_THEME } from './xtermTheme'
const MAX_TABS = 10
export function useTerminalSessions(nexusDrawer: Ref<boolean>) {
const route = useRoute()
const router = useRouter()
const theme = useTheme()
const snackbar = useSnackbar()
const settings = useTerminalSettings()
@@ -120,58 +119,13 @@ export function useTerminalSessions(nexusDrawer: Ref<boolean>) {
}
function createTerminal(): Terminal {
const isDark = theme.global.current.value.dark
return new Terminal({
cursorBlink: true,
cursorStyle: 'bar',
fontSize: settings.fontSize.value,
scrollback: settings.scrollback.value,
fontFamily: '"Cascadia Code","Fira Code","JetBrains Mono",Menlo,monospace',
theme: isDark
? {
background: '#0b1120',
foreground: '#e2e8f0',
cursor: '#7c8bf4',
selectionBackground: '#334155',
black: '#1e293b',
red: '#f87171',
green: '#4ade80',
yellow: '#fbbf24',
blue: '#60a5fa',
magenta: '#c084fc',
cyan: '#22d3ee',
white: '#e2e8f0',
brightBlack: '#64748b',
brightRed: '#fca5a5',
brightGreen: '#86efac',
brightYellow: '#fde047',
brightBlue: '#93c5fd',
brightMagenta: '#d8b4fe',
brightCyan: '#67e8f9',
brightWhite: '#f8fafc',
}
: {
background: '#f8fafc',
foreground: '#0f172a',
cursor: '#4f46e5',
selectionBackground: '#bfdbfe',
black: '#1e293b',
red: '#dc2626',
green: '#16a34a',
yellow: '#ca8a04',
blue: '#2563eb',
magenta: '#9333ea',
cyan: '#0891b2',
white: '#f1f5f9',
brightBlack: '#64748b',
brightRed: '#ef4444',
brightGreen: '#22c55e',
brightYellow: '#eab308',
brightBlue: '#3b82f6',
brightMagenta: '#a855f7',
brightCyan: '#06b6d4',
brightWhite: '#ffffff',
},
theme: XTERM_CLASSIC_THEME,
allowProposedApi: true,
drawBoldTextInBrightColors: true,
})
@@ -0,0 +1,27 @@
import type { ITheme } from '@xterm/xterm'
/** 经典终端:黑底白字 + 16 色 ANSI(不随 Vuetify 浅色/深色切换) */
export const XTERM_CLASSIC_THEME: ITheme = {
background: '#000000',
foreground: '#ffffff',
cursor: '#ffffff',
cursorAccent: '#000000',
selectionBackground: '#4d4d4d',
selectionForeground: '#ffffff',
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: '#ffffff',
}
+22 -67
View File
@@ -1,8 +1,7 @@
<template>
<v-container
fluid
class="pa-0 d-flex flex-column terminal-root"
:class="theme.global.current.value.dark ? 'terminal-root--dark' : 'terminal-root--light'"
class="pa-0 d-flex flex-column terminal-root terminal-root--classic"
>
<v-menu
v-model="showTermMenu"
@@ -110,7 +109,6 @@
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, inject, type Ref } from 'vue'
import { useRouter } from 'vue-router'
import { useTheme } from 'vuetify'
import '@xterm/xterm/css/xterm.css'
import { useSnackbar } from '@/composables/useSnackbar'
import { useTerminalSessions } from '@/composables/terminal/useTerminalSessions'
@@ -124,7 +122,6 @@ import TerminalServerPicker from '@/components/terminal/TerminalServerPicker.vue
defineOptions({ name: 'TerminalPage' })
const router = useRouter()
const theme = useTheme()
const snackbar = useSnackbar()
const nexusDrawer = inject<Ref<boolean>>('nexusDrawer', ref(true))
@@ -285,15 +282,17 @@ onBeforeUnmount(() => {
height: 100%;
box-sizing: border-box;
}
.terminal-root--dark {
background: #0b1120;
}
.terminal-root--light {
background: rgb(var(--v-theme-background));
.terminal-root--classic {
background: #000000;
}
.font-mono {
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Menlo, monospace;
}
:deep(.xterm),
:deep(.xterm-viewport),
:deep(.xterm-screen) {
background-color: #000000 !important;
}
:deep(.xterm) {
/* 顶部不加 padding,避免首行字符被 overflow 裁切 */
padding: 0 4px 4px;
@@ -313,92 +312,48 @@ onBeforeUnmount(() => {
:deep(.terminal-ctx-menu .v-icon) {
font-size: 1.35rem;
}
.terminal-root--dark :deep(.sh-keyword) {
.terminal-root--classic :deep(.sh-keyword) {
color: #60a5fa;
font-weight: 500;
}
.terminal-root--dark :deep(.sh-builtin) {
.terminal-root--classic :deep(.sh-builtin) {
color: #38bdf8;
}
.terminal-root--dark :deep(.sh-sudo) {
.terminal-root--classic :deep(.sh-sudo) {
color: #f87171;
font-weight: 600;
}
.terminal-root--dark :deep(.sh-pipe),
.terminal-root--dark :deep(.sh-operator) {
.terminal-root--classic :deep(.sh-pipe),
.terminal-root--classic :deep(.sh-operator) {
color: #fbbf24;
font-weight: 600;
}
.terminal-root--dark :deep(.sh-redirect) {
.terminal-root--classic :deep(.sh-redirect) {
color: #fb923c;
}
.terminal-root--dark :deep(.sh-string) {
.terminal-root--classic :deep(.sh-string) {
color: #4ade80;
}
.terminal-root--dark :deep(.sh-flag) {
.terminal-root--classic :deep(.sh-flag) {
color: #c084fc;
}
.terminal-root--dark :deep(.sh-variable) {
.terminal-root--classic :deep(.sh-variable) {
color: #f472b6;
}
.terminal-root--dark :deep(.sh-backtick) {
.terminal-root--classic :deep(.sh-backtick) {
color: #a78bfa;
}
.terminal-root--dark :deep(.sh-comment) {
.terminal-root--classic :deep(.sh-comment) {
color: #64748b;
font-style: italic;
}
.terminal-root--dark :deep(.sh-number) {
.terminal-root--classic :deep(.sh-number) {
color: #fbbf24;
}
.terminal-root--dark :deep(.sh-path) {
.terminal-root--classic :deep(.sh-path) {
color: #2dd4bf;
}
.terminal-root--dark :deep(.sh-text) {
.terminal-root--classic :deep(.sh-text) {
color: #e2e8f0;
}
.terminal-root--light :deep(.sh-keyword) {
color: #2563eb;
font-weight: 500;
}
.terminal-root--light :deep(.sh-builtin) {
color: #0284c7;
}
.terminal-root--light :deep(.sh-sudo) {
color: #dc2626;
font-weight: 600;
}
.terminal-root--light :deep(.sh-pipe),
.terminal-root--light :deep(.sh-operator) {
color: #ca8a04;
font-weight: 600;
}
.terminal-root--light :deep(.sh-redirect) {
color: #ea580c;
}
.terminal-root--light :deep(.sh-string) {
color: #16a34a;
}
.terminal-root--light :deep(.sh-flag) {
color: #9333ea;
}
.terminal-root--light :deep(.sh-variable) {
color: #db2777;
}
.terminal-root--light :deep(.sh-backtick) {
color: #7c3aed;
}
.terminal-root--light :deep(.sh-comment) {
color: #94a3b8;
font-style: italic;
}
.terminal-root--light :deep(.sh-number) {
color: #d97706;
}
.terminal-root--light :deep(.sh-path) {
color: #0d9488;
}
.terminal-root--light :deep(.sh-text) {
color: #0f172a;
}
</style>