8 changed files with 181 additions and 21 deletions
@ -0,0 +1,3 @@ |
|||||||
|
import Terminal from './src/terminal.vue' |
||||||
|
|
||||||
|
export default Terminal |
@ -0,0 +1,122 @@ |
|||||||
|
<template> |
||||||
|
<div id="terminal" v-loading="flag.loading" ref="terminal" element-loading-text="拼命连接中"></div> |
||||||
|
</template> |
||||||
|
<script lang="ts" setup> |
||||||
|
import { ref, onMounted, onBeforeUnmount, watch, reactive } from "vue"; |
||||||
|
import { Terminal } from "xterm"; |
||||||
|
import "xterm/css/xterm.css"; |
||||||
|
const prop = defineProps({ |
||||||
|
url: { |
||||||
|
type: String, |
||||||
|
default: null |
||||||
|
} |
||||||
|
}); |
||||||
|
const flag = reactive({ |
||||||
|
loading: true, |
||||||
|
lock: true |
||||||
|
}); |
||||||
|
const terminal = ref(null); |
||||||
|
|
||||||
|
const terminalSocket = ref(); |
||||||
|
const term = ref(); |
||||||
|
let text = ""; |
||||||
|
|
||||||
|
const runRealTerminal = () => { |
||||||
|
flag.loading = false; |
||||||
|
} |
||||||
|
|
||||||
|
const onWSReceive = (message) => { |
||||||
|
term.value.write(message.data) |
||||||
|
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 initTerm = () => { |
||||||
|
term.value = new Terminal({ |
||||||
|
lineHeight: 1.2, |
||||||
|
fontSize: 18, |
||||||
|
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace", |
||||||
|
theme: { |
||||||
|
background: '#181d28', |
||||||
|
}, |
||||||
|
// 光标闪烁 |
||||||
|
cursorBlink: true, |
||||||
|
cursorStyle: 'underline', |
||||||
|
scrollback: 100, |
||||||
|
tabStopWidth: 4, |
||||||
|
cols: 120 |
||||||
|
}); |
||||||
|
term.value.open(terminal.value); |
||||||
|
term.value.onKey(e => { |
||||||
|
if (flag.lock) { |
||||||
|
return; |
||||||
|
} |
||||||
|
//\x1B ESC |
||||||
|
//\x1BOP-\x1B[24~ F1-F12 |
||||||
|
const key = e.key; |
||||||
|
if (key.indexOf("\u001b") !== -1) { |
||||||
|
return; |
||||||
|
} |
||||||
|
console.log(e); |
||||||
|
if (isWsOpen()) { |
||||||
|
if (key == '\r') { |
||||||
|
term.value.writeln(key) |
||||||
|
terminalSocket.value.send(text); |
||||||
|
text = ""; |
||||||
|
} else { |
||||||
|
text += key |
||||||
|
term.value.write(key) |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
// 是否连接中0 1 2 3 |
||||||
|
const isWsOpen = () => { |
||||||
|
const readyState = terminalSocket.value && terminalSocket.value.readyState; |
||||||
|
return readyState === 1 |
||||||
|
} |
||||||
|
|
||||||
|
//监听类型变化,重置term |
||||||
|
watch(() => prop.url, () => { |
||||||
|
flag.loading = true; |
||||||
|
terminalSocket.value && terminalSocket.value.close(); |
||||||
|
terminalSocket.value = null; |
||||||
|
initWS(); |
||||||
|
// 重置 |
||||||
|
term.value.reset(); |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initWS(); |
||||||
|
initTerm(); |
||||||
|
}) |
||||||
|
onBeforeUnmount(() => { |
||||||
|
terminalSocket.value && terminalSocket.value.close(); |
||||||
|
term.value.dispose(); |
||||||
|
}) |
||||||
|
</script> |
||||||
|
<style lang="scss" scoped> |
||||||
|
#terminal { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
</style> |
Loading…
Reference in new issue