最近在做的项目需要使用xterminal实现网页远程连接Linux终端,引了这个插件后发现问题很多,接下来一一记录问题所在。

一、如何在vue项目中使用xterm.js

  • 安装xterm.js,博主使用的是3.x
    npm i xterm --save
  • 在项目中引用
    • 新建组件Xterminal.vue
<template>
  <div class="console" id="terminal'" v-loading="loading" element-loading-text="拼命连接中">
  </div>
</template>
<script>
import { Terminal } from "xterm";
import * as fit from "xterm/lib/addons/fit/fit";
import * as attach from "xterm/lib/addons/attach/attach";
import "xterm/dist/xterm.css";
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);

export default {
  name: "xterminal",
  data() {
    return {
      term: null,
      terminalSocket: null,
      copy: '',
      loading: true,
      cols: 80,
      content: ''
    };
  },
  methods: {
    runRealTerminal(res) {
      this.content.operate = 'connect';	// 根据后端需求调节建立连接时发送的数据
      this.terminalSocket.send(
        JSON.stringify(this.content)
      );
      this.loading = false;
      console.log("webSocket is finished");
    },
    closeRealTerminal() {
      console.log("close");
    }
  },
  mounted() {
    let terminalContainer = document.getElementById("terminal");
    this.term = new Terminal({
      // 光标闪烁
      cursorBlink: true
    });
    this.term.open(terminalContainer, true);
    // open websocket
    this.terminalSocket = new WebSocket("ws://********");	// 填入服务器的websocket连接地址

    this.terminalSocket.onopen = this.runRealTerminal;
    this.terminalSocket.onclose = this.closeRealTerminal;
    this.terminalSocket.onerror = this.errorRealTerminal;
    this.term.attach(this.terminalSocket);
    this.term._initialized = true;
    console.log("mounted is going on");
    this.term.on("data", (data) => {
      console.log("data", data);
      this.terminalSocket.send(
        JSON.stringify({
          operate: "command",
          command: data
        })
      );
    });
  },
  beforeDestroy() {
    this.terminalSocket.close();
    this.term.destroy();
  },
};
</script>
<style lang="scss">
</style>

以上代码是我对webssh的初步实现,当代码编译通过,在浏览器上展示时,博主发现终端窗口的大小并没有占满容器。
在这里插入图片描述
在这里插入图片描述
页面布局类似于上图。主要问题有以下几点:

  1. 终端的高度无法自适应容器大小
  2. 终端可显示区域未占满终端的100%宽度及高度
  3. 浏览器进行缩放或放大时无法自适应

针对这些问题,最初想到的办法是过去container的宽高,在初始化terminal时对cols及rows参数进行设置,但是container的高度只在终端初始化后才会被撑开,因此获取到的高度为0。最终想到的办法是获取浏览器的innerWidth和innerHeight,计算终端容器的宽高。如上图所示,终端的高度为 window.innerHeight - 140,宽度为window.innerWidth - 230px(只是举个例子)。之后初始化时进行设置,代码如下:

const width = window.innerWidth - 230;
const height = window.innerHeight - 140;
this.cols = parseInt(width/9, 10);		// 经过计算one col大约等同于9px
this.term = new Terminal({
   // 光标闪烁
   cursorBlink: true,
   cols: this.cols,
   rows: parseInt(height/17, 10),		// one row = 17px
 });

本以为到此开发结束,结果有一天心血来潮输入命令狂按a的时候,命令行过长不会换行而且把前面的内容覆盖了,淦!如下图所示。
在这里插入图片描述
百度了很多文章,却没有找到解决办法,转到github,在官方issue1359中找到了答案:
在这里插入图片描述
这只是第一种解决办法,删除term.fit()或者cols属性设置,但像他说的,这并不是一种好办法。
在这里插入图片描述
在这里插入图片描述
结合上面二者的回答原因是前后端设置的终端cols不同导致换行不正确。并且建议在设置cols后将其返回到后端,使后端设置成前端的cols。
所以博主在建立连接时,将cols也进行了传递,传递的方法很多,你可以在建立websocket连接时,将cols作为参数拼接到url后,而博主根据后端的需求将参数放在了content中进行了传递。

runRealTerminal(res) {
    this.content.operate = 'connect';
    this.content.cols = this.cols;
    this.terminalSocket.send(
      JSON.stringify(this.content)
    );
    this.loading = false;
    console.log("webSocket is finished");
  },

之后确实实现了可换行的效果,但是问题又出现了,就是在浏览器缩放或放大的时候,终端内容消失了而且经常无法输入。这是因为没有使用fit()函数使其自适应。解决办法:在浏览器resize的时候调用fit函数。
在mounted钩子函数中加入:window.addEventListener('resize',this.windowChange);
在methods中定义:

windowChange(){
      const width = window.innerWidth - 230;
      const height = window.innerHeight - 140;
      this.cols = parseInt(width/9, 10);
      this.term.fit();
      this.term.resize(this.cols, parseInt(height/17, 10));
      this.term.scrollToBottom();
    }

同时需要向后端通过websocket发送最新的cols值。
至此,问题全部解决。end.

Logo

前往低代码交流专区

更多推荐