Bootstrap

Vue xtermjs 黑窗口 插件的使用

官网地址:https://xtermjs.org/
但其实感觉官网没什么用,只有一个最简单的demo,具体的使用手册,还是得靠前人栽树了

1.展示效果

因为我这里已经和后台对接了ws 所以会有重复的返回(单数行都是ws返回的信息)

在这里插入图片描述
2.具体代码
一、下载插件

  • xterm

npm install --save xterm

  • xterm-addon-fit

使终端的尺寸适合包含元素):
npm install --save xterm-addon-fit

  • xterm-addon-attach

xterm.js的附加组件,用于附加到Web Socket
npm install --save xterm-addon-attach

二、创建容器

  • html
<template>
    <div id="xterm" class="xterm" />
</template>
  • js

由于我已经写好了ws通用组件,所以这里并没有创建此组件和使用AttachAddon
只使用了this.$emit("websocketSend", wsParams, server);传递参数
ws的通信可以参考此博客

<script>
import "xterm/css/xterm.css";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
// import { AttachAddon } from "xterm-addon-attach";
export default {
	name: "terminal",
	data() {
		return {
			term: "",//terminal 黑窗口容器
			prefix: "[root@serverip ~]# ",//前缀
			inputText: "",//输入内容,每次回车后进行ws通信然后清空此数据
		};
	},
	watch: {},
	methods: {
		//初始化黑窗口
		async initTerm() {
			const fitAddon = new FitAddon();
			this.term = new Terminal({
				fontSize: 14,
				cursorBlink: true,
				allowProposedApi: true,
				disableStdin: false,
				LogLevel: "debug",
			});

			this.term.loadAddon(fitAddon);
			//开启Xterm终端
			this.term.open(document.getElementById("xterm"));
			this.term.writeln("\x1b[1;1;32mwellcom to web terminal!\x1b[0m");
			this.term.write(this.prefix); //黑窗口 前缀

			await this.termPromt(); //term.promt
			await this.termKeyCode(); //事件

			fitAddon.fit(); //黑窗口适应实际div宽高
			this.term.focus(); //自动聚焦
		},
		//事件
		termKeyCode() {
			const TERMINAL_INPUT_KEY = {
				BACK: 8, // 退格删除键
				ENTER: 13, // 回车键
				UP: 38, // 方向盘上键
				DOWN: 40, // 方向盘键
				LEFT: 37, // 方向盘左键
				RIGHT: 39, // 方向盘右键
			};
			const { eqpCode, server } = this.selectObj;
			let inputText = "";
			let currentIndex = 0;
			let inputTextList = [];
			this.term.onKey((e) => {
				const { key, domEvent } = e;
				const { keyCode, altKey, altGraphKey, ctrlKey, metaKey } = domEvent;

				const printAble = !(altKey || altGraphKey || ctrlKey || metaKey); // 禁止相关按键
				const totalOffsetLength = inputText.length + this.prefix.length; // 总偏移量
				const currentOffsetLength = this.term._core.buffer.x; // 当前x偏移量

				switch (keyCode) {
					//删除
					case TERMINAL_INPUT_KEY.BACK:
						if (currentOffsetLength > this.prefix.length) {
							const cursorOffSetLength = this.getCursorOffsetLength(totalOffsetLength - currentOffsetLength, "\x1b[D"); // 保留原来光标位置

							this.term._core.buffer.x = currentOffsetLength - 1;
							this.term.write("\x1b[?K" + inputText.slice(currentOffsetLength - this.prefix.length));
							this.term.write(cursorOffSetLength);
							inputText = `${inputText.slice(0, currentOffsetLength - this.prefix.length - 1)}${inputText.slice(
								currentOffsetLength - this.prefix.length
							)}`;
						}
						break;
					//回车
					case TERMINAL_INPUT_KEY.ENTER: {
						this.term.write("\r\n");
						console.log("inputText", inputText);
						//ws 通信参数
						let wsParams = { EqpCode: eqpCode, Action: "terminal", Data: inputText };
						this.$emit("websocketSend", wsParams, server);

						if (!inputText.trim()) {
							this.term.prompt();
							return;
						}

						if (inputTextList.indexOf(inputText) === -1) {
							inputTextList.push(inputText);
							currentIndex = inputTextList.length;
						}

						this.term.prompt();
						inputText = "";
						break;
					}

					case TERMINAL_INPUT_KEY.UP: {
						if (!inputTextList[currentIndex - 1]) break;

						const offsetLength = this.getCursorOffsetLength(inputText.length, "\x1b[D");

						inputText = inputTextList[currentIndex - 1];
						this.term.write(offsetLength + "\x1b[?K");
						this.term.write(inputTextList[currentIndex - 1]);
						this.term._core.buffer.x = totalOffsetLength;
						currentIndex--;

						break;
					}
					case TERMINAL_INPUT_KEY.LEFT:
						if (currentOffsetLength > this.prefix.length) {
							this.term.write(key); // '\x1b[D'
						}
						break;

					case TERMINAL_INPUT_KEY.RIGHT:
						if (currentOffsetLength < totalOffsetLength) {
							this.term.write(key); // '\x1b[C'
						}
						break;
					default: {
						// 在当前的坐标写上 key 和坐标后面的字符
						// 移动停留在当前位置的光标
						if (!printAble) break;
						if (totalOffsetLength >= this.term.cols) break;
						if (currentOffsetLength >= totalOffsetLength) {
							this.term.write(key);
							inputText += key;
							break;
						}
						let cursorOffSetLength = this.getCursorOffsetLength(totalOffsetLength - currentOffsetLength, "\x1b[D");
						this.term.write("\x1b[?K" + `${key}${inputText.slice(currentOffsetLength - this.prefix.length)}`);
						this.term.write(cursorOffSetLength);
						inputText = inputText.slice(0, currentOffsetLength) + key + inputText.slice(totalOffsetLength - currentOffsetLength);
						break;
					}
				}
			});
		},
		//限制和后端交互,只有输入回车键才显示结果
		termPromt() {
			this.term.prompt = () => {
				this.term.write(this.prefix);
			};
		},
		//获取光标当前位置
		getCursorOffsetLength(offsetLength, subString) {
			let cursorOffsetLength = "";
			for (let offset = 0; offset < offsetLength; offset++) {
				cursorOffsetLength += subString;
			}
			return cursorOffsetLength;
		},
		//写入黑窗口
		wirteTerm(data) {
			console.log("写入黑窗口", data);
			this.term.writeln(data);
			this.term.prompt();
		},
		//加载基础数据
		pageLoad(data) {
			this.selectObj = data;
			this.drawerFlag = true;
			this.$nextTick(() => {
				this.initTerm();
			});
		},
		cancelClick() {
			this.drawerFlag = false;
			//关闭弹框
			this.term.dispose(document.getElementById("xterm"));
		},
	},
};
</script>

二.利用xterm-addon-attach进行实时通信

<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { AttachAddon } from 'xterm-addon-attach'

export default {
  name: 'Xterm',
  props: {
    socketURI: {
      type: String,
      default: ''
    },
  },
  mounted() {
    this.initSocket()
  },
  beforeDestroy() {
    this.socket.close()
    this.term.dispose()
  },
  methods: {
    initTerm() {
      const term = new Terminal({
        fontSize: 14,
        cursorBlink: true
      });
      const attachAddon = new AttachAddon(this.socket);
      const fitAddon = new FitAddon();
      term.loadAddon(attachAddon);
      term.loadAddon(fitAddon);
      term.open(document.getElementById('xterm'));
      fitAddon.fit();
      term.focus();
      this.term = term
    },
    initSocket() {
      this.socket = new WebSocket(this.socketURI);
      this.socketOnClose();
      this.socketOnOpen();
      this.socketOnError();
    },
    socketOnOpen() {
      this.socket.onopen = () => {
        // 链接成功后
        this.initTerm()
      }
    },
    socketOnClose() {
      this.socket.onclose = () => {
        // console.log('close socket')
      }
    },
    socketOnError() {
      this.socket.onerror = () => {
        // console.log('socket 链接失败')
      }
    }
  }
}
</script>
;