Skip to content

Commit c0d434f

Browse files
committed
feat: 添加 XTerminal 组件并在环境和 SDK 页面中集成
1 parent 92b8b5e commit c0d434f

File tree

3 files changed

+294
-171
lines changed

3 files changed

+294
-171
lines changed

src/vue/components/XTerminal.vue

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
<template>
2+
<div ref="terminalRef" class="xterm-container"></div>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
7+
import { Terminal } from '@xterm/xterm';
8+
import { FitAddon } from '@xterm/addon-fit';
9+
import '@xterm/xterm/css/xterm.css';
10+
11+
// Props
12+
interface Props {
13+
rows?: number;
14+
cols?: number;
15+
fontSize?: number;
16+
fontFamily?: string;
17+
theme?: any;
18+
scrollback?: number;
19+
cursorBlink?: boolean;
20+
convertEol?: boolean;
21+
disableStdin?: boolean;
22+
}
23+
24+
const props = withDefaults(defineProps<Props>(), {
25+
rows: 20,
26+
cols: 80,
27+
fontSize: 13,
28+
fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace",
29+
theme: () => ({
30+
background: '#1e1e1e',
31+
foreground: '#d4d4d4',
32+
cursor: '#d4d4d4',
33+
black: '#000000',
34+
red: '#ff6b6b',
35+
green: '#51cf66',
36+
yellow: '#ffd93d',
37+
blue: '#339af0',
38+
magenta: '#ae3ec9',
39+
cyan: '#22b8cf',
40+
white: '#d4d4d4',
41+
brightBlack: '#868e96',
42+
brightRed: '#ff8787',
43+
brightGreen: '#8ce99a',
44+
brightYellow: '#ffe066',
45+
brightBlue: '#74c0fc',
46+
brightMagenta: '#d0bfff',
47+
brightCyan: '#66d9e8',
48+
brightWhite: '#ffffff'
49+
}),
50+
scrollback: 5000,
51+
cursorBlink: false,
52+
convertEol: true,
53+
disableStdin: true
54+
});
55+
56+
// Emits
57+
const emit = defineEmits<{
58+
ready: [terminal: Terminal];
59+
data: [data: string];
60+
}>();
61+
62+
// Refs
63+
const terminalRef = ref<HTMLElement>();
64+
let terminal: Terminal | null = null;
65+
let fitAddon: FitAddon | null = null;
66+
67+
// Initialize terminal
68+
const initTerminal = () => {
69+
if (!terminalRef.value || terminal) return;
70+
71+
// Create terminal instance
72+
terminal = new Terminal({
73+
rows: props.rows,
74+
cols: props.cols,
75+
fontSize: props.fontSize,
76+
fontFamily: props.fontFamily,
77+
theme: props.theme,
78+
scrollback: props.scrollback,
79+
cursorBlink: props.cursorBlink,
80+
convertEol: props.convertEol,
81+
disableStdin: props.disableStdin
82+
});
83+
84+
// Create fit addon
85+
fitAddon = new FitAddon();
86+
terminal.loadAddon(fitAddon);
87+
88+
// Mount to DOM
89+
terminal.open(terminalRef.value);
90+
91+
// Listen for terminal input (if not disabled)
92+
if (!props.disableStdin) {
93+
terminal.onData((data) => {
94+
emit('data', data);
95+
});
96+
}
97+
98+
// Fit to container
99+
nextTick(() => {
100+
fit();
101+
// Emit ready event
102+
if (terminal) {
103+
emit('ready', terminal);
104+
}
105+
});
106+
};
107+
108+
// Fit terminal to container
109+
const fit = () => {
110+
if (fitAddon) {
111+
fitAddon.fit();
112+
}
113+
};
114+
115+
// Write to terminal
116+
const write = (text: string) => {
117+
if (terminal) {
118+
terminal.write(text);
119+
}
120+
};
121+
122+
// Write line to terminal
123+
const writeln = (text: string) => {
124+
if (terminal) {
125+
terminal.writeln(text);
126+
}
127+
};
128+
129+
// Clear terminal
130+
const clear = () => {
131+
if (terminal) {
132+
terminal.clear();
133+
}
134+
};
135+
136+
// Reset terminal
137+
const reset = () => {
138+
if (terminal) {
139+
terminal.reset();
140+
}
141+
};
142+
143+
// Scroll to bottom
144+
const scrollToBottom = () => {
145+
if (terminal) {
146+
terminal.scrollToBottom();
147+
}
148+
};
149+
150+
// Scroll to top
151+
const scrollToTop = () => {
152+
if (terminal) {
153+
terminal.scrollToTop();
154+
}
155+
};
156+
157+
// Get terminal instance
158+
const getTerminal = () => terminal;
159+
160+
// Expose methods
161+
defineExpose({
162+
write,
163+
writeln,
164+
clear,
165+
reset,
166+
fit,
167+
scrollToBottom,
168+
scrollToTop,
169+
getTerminal
170+
});
171+
172+
// Lifecycle
173+
onMounted(() => {
174+
initTerminal();
175+
176+
// Listen for window resize
177+
const resizeObserver = new ResizeObserver(() => {
178+
fit();
179+
});
180+
181+
if (terminalRef.value) {
182+
resizeObserver.observe(terminalRef.value);
183+
}
184+
185+
// Store observer for cleanup
186+
(window as any).__xtermResizeObserver = resizeObserver;
187+
});
188+
189+
onUnmounted(() => {
190+
// Clean up resize observer
191+
const resizeObserver = (window as any).__xtermResizeObserver;
192+
if (resizeObserver) {
193+
resizeObserver.disconnect();
194+
delete (window as any).__xtermResizeObserver;
195+
}
196+
197+
// Clean up terminal
198+
if (terminal) {
199+
terminal.dispose();
200+
terminal = null;
201+
}
202+
203+
if (fitAddon) {
204+
fitAddon.dispose();
205+
fitAddon = null;
206+
}
207+
});
208+
</script>
209+
210+
<style scoped>
211+
.xterm-container {
212+
width: 100%;
213+
height: 100%;
214+
min-height: 200px;
215+
}
216+
217+
:deep(.xterm) {
218+
padding: 10px;
219+
}
220+
221+
:deep(.xterm-viewport) {
222+
background-color: transparent !important;
223+
}
224+
225+
:deep(.xterm-screen) {
226+
height: 100% !important;
227+
}
228+
</style>

0 commit comments

Comments
 (0)