|
|
|
<template>
|
|
|
|
<el-row>
|
|
|
|
<el-col :span="12">
|
|
|
|
<div id="terminal" v-loading="flag.loading" ref="terminal" :element-loading-text="t('tool.connect')"
|
|
|
|
@click.right.native="showClear($event)"></div>
|
|
|
|
</el-col>
|
|
|
|
<el-col :span="12">
|
|
|
|
<div id="response" v-loading="flag.loading" ref="response" :element-loading-text="t('tool.connect')"></div>
|
|
|
|
</el-col>
|
|
|
|
</el-row>
|
|
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import { ref, onMounted, onBeforeUnmount, watch, reactive } from "vue";
|
|
|
|
import { useStore } from "vuex";
|
|
|
|
import { Terminal } from "xterm";
|
|
|
|
import "xterm/css/xterm.css";
|
|
|
|
import { useI18n } from "vue3-i18n";
|
|
|
|
const { t } = useI18n();
|
|
|
|
const { state } = useStore();
|
|
|
|
const prop = defineProps({
|
|
|
|
url: {
|
|
|
|
type: String,
|
|
|
|
default: null
|
|
|
|
},
|
|
|
|
msgFilter: {
|
|
|
|
type: Function,
|
|
|
|
defalut: msg => {
|
|
|
|
return { unlock: false, data: msg };
|
|
|
|
}
|
|
|
|
},
|
|
|
|
prmt: {
|
|
|
|
type: String,
|
|
|
|
default: ">"
|
|
|
|
},
|
|
|
|
cols: {
|
|
|
|
type: Number,
|
|
|
|
default: 80
|
|
|
|
},
|
|
|
|
rows: {
|
|
|
|
type: Number,
|
|
|
|
default: 40
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const flag = reactive({
|
|
|
|
loading: true,
|
|
|
|
lock: true
|
|
|
|
});
|
|
|
|
const terminal = ref(null);
|
|
|
|
const response = ref(null);
|
|
|
|
|
|
|
|
const terminalSocket = ref();
|
|
|
|
const term = ref();
|
|
|
|
const rsp = ref();
|
|
|
|
let text = "";
|
|
|
|
|
|
|
|
const runRealTerminal = () => {
|
|
|
|
flag.loading = false;
|
|
|
|
term.value.write(prop.prmt);
|
|
|
|
flag.lock = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const onWSReceive = (message) => {
|
|
|
|
if (prop.msgFilter) {
|
|
|
|
const data = prop.msgFilter(message.data);
|
|
|
|
if (data) {
|
|
|
|
rsp.value.writeln(data)
|
|
|
|
} else {
|
|
|
|
flag.lock = false
|
|
|
|
term.value.write(prop.prmt)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rsp.value.writeln(message.data)
|
|
|
|
rsp.value.write(prop.prmt)
|
|
|
|
flag.lock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const errorRealTerminal = (ex) => {
|
|
|
|
let message = ex.message;
|
|
|
|
if (!message) message = 'disconnected'
|
|
|
|
term.value.write(`\x1b[31m${message}\x1b[m\r\n`)
|
|
|
|
console.log("err");
|
|
|
|
}
|
|
|
|
const closeRealTerminal = () => {
|
|
|
|
console.log("close");
|
|
|
|
}
|
|
|
|
|
|
|
|
const initWS = () => {
|
|
|
|
if (!prop.url) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
terminalSocket.value = new WebSocket(prop.url);
|
|
|
|
terminalSocket.value.onopen = runRealTerminal;
|
|
|
|
terminalSocket.value.onmessage = onWSReceive;
|
|
|
|
terminalSocket.value.onclose = closeRealTerminal;
|
|
|
|
terminalSocket.value.onerror = errorRealTerminal;
|
|
|
|
}
|
|
|
|
|
|
|
|
const onKey = e => {
|
|
|
|
if (flag.lock) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//\x1B ESC
|
|
|
|
//\x1BOP-\x1B[24~ F1-F12
|
|
|
|
const key = e.key;
|
|
|
|
|
|
|
|
if (key.indexOf("\u001b") !== -1 || !isWsOpen()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (back(e) || newLine(e) || cancel(e) || copy(e) || paste(e))
|
|
|
|
return;
|
|
|
|
text += key;
|
|
|
|
term.value.write(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
const back = e => {
|
|
|
|
if (e.key !== '\x7F') {//回退
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (text.length > 0) {
|
|
|
|
term.value.write("\b \b");
|
|
|
|
text = text.substring(0, text.length - 1)
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const newLine = (e) => {
|
|
|
|
const key = e.key;
|
|
|
|
if (key !== '\r') {//回车
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
term.value.writeln(key)
|
|
|
|
if (e.domEvent.ctrlKey) {
|
|
|
|
text += key;
|
|
|
|
} else {
|
|
|
|
sendText(text);
|
|
|
|
text = "";
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const cancel = (e) => {
|
|
|
|
if (e.key !== '\x1A') {//ctrl+z
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if(text){
|
|
|
|
term.value.writeln("\r");
|
|
|
|
term.value.write(prop.prmt);
|
|
|
|
text = "";
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const copy = e => {
|
|
|
|
if (e.key !== '\x03') {//ctrl+c
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const paste = (e) => {
|
|
|
|
if (e.key !== '\x16') {//粘贴
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
navigator.clipboard.readText().then(val => {
|
|
|
|
text += val;
|
|
|
|
term.value.write(val);
|
|
|
|
})
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const initTerm = () => {
|
|
|
|
const options: any = {
|
|
|
|
lineHeight: 1.2,
|
|
|
|
fontSize: 16,
|
|
|
|
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
|
|
|
theme: {
|
|
|
|
background: '#181d28',
|
|
|
|
},
|
|
|
|
// 光标闪烁
|
|
|
|
cursorBlink: true,
|
|
|
|
cursorStyle: 'underline',
|
|
|
|
scrollback: 100,
|
|
|
|
tabStopWidth: 2,
|
|
|
|
cols: prop.cols,
|
|
|
|
rows: prop.rows
|
|
|
|
}
|
|
|
|
term.value = new Terminal(options);
|
|
|
|
term.value.open(terminal.value);
|
|
|
|
term.value.onKey(onKey);
|
|
|
|
|
|
|
|
options.cursorBlink = false;
|
|
|
|
rsp.value = new Terminal(options);
|
|
|
|
rsp.value.open(response.value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const sendText = str => {
|
|
|
|
if (str) {
|
|
|
|
terminalSocket.value.send(str);
|
|
|
|
flag.lock = true;
|
|
|
|
} else {
|
|
|
|
term.value.write(prop.prmt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 是否连接中0 1 2 3
|
|
|
|
const isWsOpen = () => {
|
|
|
|
const readyState = terminalSocket.value && terminalSocket.value.readyState;
|
|
|
|
return readyState === 1
|
|
|
|
}
|
|
|
|
|
|
|
|
const showClear = e => {
|
|
|
|
// 去掉默认事件
|
|
|
|
document.oncontextmenu = function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
// navigator.clipboard.readText().then(content => {
|
|
|
|
// term.value.write(content)
|
|
|
|
// text += content;
|
|
|
|
// sendText(content);
|
|
|
|
// })
|
|
|
|
// term.value.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
//监听类型变化,重置term
|
|
|
|
watch(() => prop.url, () => {
|
|
|
|
term.value.reset();
|
|
|
|
rsp.value.reset();
|
|
|
|
flag.loading = true;
|
|
|
|
flag.lock = false;
|
|
|
|
terminalSocket.value && terminalSocket.value.close();
|
|
|
|
terminalSocket.value = null;
|
|
|
|
initWS();
|
|
|
|
// 重置
|
|
|
|
})
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
initWS();
|
|
|
|
initTerm();
|
|
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
terminalSocket.value && terminalSocket.value.close();
|
|
|
|
term.value.dispose();
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
#terminal {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
</style>
|