-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxterm.vue
More file actions
179 lines (156 loc) · 3.95 KB
/
Copy pathxterm.vue
File metadata and controls
179 lines (156 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<template>
<div ref="terminalRef" class="xterm-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
// 导入 xterm.js 的基本样式
import "@xterm/xterm/css/xterm.css";
// ---------------------------
// 1. Props 定义
// ---------------------------
const props = defineProps({
logData: {
type: Array as () => string[],
default: () => [],
required: true,
},
autoScroll: {
type: Boolean,
default: true,
},
height: {
type: String,
default: "500px",
},
// 可选:终端的字体大小
fontSize: {
type: Number,
default: 14,
},
});
// ---------------------------
// 2. 内部状态和实例
// ---------------------------
const terminalRef = ref<HTMLElement | null>(null); // DOM 容器引用
let terminal: Terminal | null = null; // xterm.js 实例
let fitAddon: FitAddon | null = null; // Fit 插件实例
let resizeObserver: ResizeObserver | null = null; // ResizeObserver 实例
let lastLogLength = 0; // 记录上次渲染的日志数量,用于增量更新
// ---------------------------
// 3. 核心逻辑:初始化与适配
// ---------------------------
const initTerminal = () => {
if (!terminalRef.value) return;
// 终端配置
terminal = new Terminal({
cursorBlink: false,
scrollback: 5000,
convertEol: true,
fontSize: props.fontSize, // 使用 props 传入字体大小
theme: {
background: "#1f1f1f",
foreground: "#cccccc",
selectionBackground: "#5c5c5c",
},
});
// 加载 Fit 插件
fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
// 挂载到 DOM
terminal.open(terminalRef.value);
// 初始适应大小
fitAddon.fit();
// 首次加载时,将所有日志写入
writeNewLogs(props.logData);
// 初始化 ResizeObserver
setupResizeObserver();
};
/**
* 设置 ResizeObserver 监听终端容器尺寸变化
*/
const setupResizeObserver = () => {
if (!terminalRef.value) return;
resizeObserver = new ResizeObserver(() => {
// 当容器尺寸变化时,调用 fitAddon.fit() 重新计算终端大小
fitTerminal();
});
// 监听 terminalRef.value 元素的尺寸变化
resizeObserver.observe(terminalRef.value);
};
/**
* 写入新的日志行
* @param logsToPrint 需要写入的日志行数组
*/
const writeNewLogs = (logsToPrint: string[]) => {
if (!terminal) return;
logsToPrint.forEach((log) => {
terminal!.writeln(log);
});
if (props.autoScroll) {
terminal.scrollToBottom();
}
};
/**
* 终端适应容器大小
*/
const fitTerminal = () => {
if (fitAddon) {
fitAddon.fit();
}
};
// ---------------------------
// 4. 数据监听:响应外部日志变化
// ---------------------------
watch(
() => props.logData,
(newLogs) => {
if (newLogs.length > lastLogLength) {
const newEntries = newLogs.slice(lastLogLength);
writeNewLogs(newEntries);
lastLogLength = newLogs.length;
} else if (newLogs.length < lastLogLength) {
// 处理日志被清空或截断的情况
terminal?.clear();
writeNewLogs(newLogs);
lastLogLength = newLogs.length;
}
},
{
immediate: true,
}
);
// ---------------------------
// 5. 生命周期管理
// ---------------------------
onMounted(() => {
initTerminal();
});
onBeforeUnmount(() => {
// 断开 ResizeObserver
if (resizeObserver && terminalRef.value) {
resizeObserver.unobserve(terminalRef.value);
resizeObserver.disconnect(); // 确保所有观察者都被断开
}
// 销毁 xterm 实例
if (terminal) {
terminal.dispose();
}
});
</script>
<style scoped>
.xterm-container {
width: 100%;
height: v-bind(height);
overflow: hidden;
border: 1px solid #444;
}
.xterm-container :deep(.xterm) {
padding: 10px;
}
</style>
<!--
npm install @xterm/xterm @xterm/addon-fit
# 或者使用 yarn
yarn add @xterm/xterm @xterm/addon-fit -->