fix(terminal): WebSSH 固定黑底白字 ANSI 终端配色
不再跟随 Vuetify 浅色主题,xterm 始终使用经典黑底白字与 16 色输出。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 | 判定 |
|
||||
|---|------|
|
||||
| H1–H3 | 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',
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user