You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
4.8 KiB
203 lines
4.8 KiB
<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: ">" |
|
} |
|
}); |
|
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) { |
|
return; |
|
} |
|
if (isWsOpen()) { |
|
if (key === '\x7F') { |
|
if (text.length > 0) { |
|
term.value.write("\b \b"); |
|
text = text.substring(0, text.length - 1) |
|
} |
|
return; |
|
} |
|
if (key == '\r') { |
|
term.value.writeln(key) |
|
if (e.domEvent.ctrlKey) { |
|
text += key; |
|
} else { |
|
sendText(text); |
|
text = ""; |
|
} |
|
} else { |
|
text += key |
|
term.value.write(key) |
|
} |
|
} |
|
} |
|
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: 80 |
|
} |
|
options.cols = Math.ceil((window.innerWidth - 80) / 20) |
|
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> |