blob: 817d5c0411a13f4bef38c9316d29fe71de2ac56f [file] [log] [blame]
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<template>
<div>
<div
ref="terminal"
class="terminal-container"
:style="{ height: terminalHeight + 'px' }"
>
<Spin
v-show="loading"
size="large"
fix/>
</div>
</div>
</template>
<script>
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import 'xterm/css/xterm.css'
export default {
name: 'TerminalLog',
props: {
logs: {
type: String,
default: ''
},
scriptViewState: {
type: Object,
default: () => {
return {};
},
},
loading: {
type: Boolean,
default: false
}
},
data() {
return {
terminal: null,
fitAddon: null,
terminalHeight: 400,
}
},
watch: {
logs(newVal) {
if (this.terminal && newVal) {
this.terminal.clear();
this.terminal.write(newVal)
setTimeout(() => {
this.terminal.scrollToTop();
}, 0)
}
},
scriptViewState: {
handler(newVal) {
this.terminalHeight = newVal.bottomContentHeight
this.$nextTick(() => {
this.fitAddon?.fit()
})
},
deep: true,
immediate: true,
}
},
mounted() {
this.initTerminal()
window.addEventListener('resize', this.onResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize)
if (this.terminal) {
this.terminal.dispose()
}
},
methods: {
copyToClipboard(text) {
try {
// 优先使用 navigator.clipboard
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
// 降级使用 execCommand
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
return Promise.resolve();
} catch (err) {
return Promise.reject(err);
}
},
initTerminal() {
this.terminal = new Terminal({
cursorBlink: false,
disableStdin: true,
theme: {
background: '#f5f5f5', // 浅灰色背景
foreground: "black", // 黑色文字
selectionBackground: '#cccccc', // 浅灰色选中背景色
// selectionForeground: '#000000',
},
screenKeys: true,
fontSize: 14,
fontWeight: 'bold',
convertEol: true, // 确保换行符正确处理
allowTransparency: true, // 允许透明度
lineHeight: 1.2, // 调整行高
padding: 8
})
this.fitAddon = new FitAddon()
this.terminal.loadAddon(this.fitAddon)
this.terminal.open(this.$refs.terminal)
this.fitAddon.fit()
if (this.logs) {
this.terminal.writeln(this.logs)
setTimeout(() => {
this.terminal.scrollToTop();
}, 0)
}
this.terminal.attachCustomKeyEventHandler((arg) => {
if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
const selection = this.terminal.getSelection();
if (selection) {
this.copyToClipboard(selection);
return false;
}
}
return true;
});
},
onResize() {
this.fitAddon?.fit()
}
}
}
</script>
<style scoped>
.terminal-container {
width: 100%;
overflow: hidden;
}
</style>