xterm.js + vue + websocket实现终端功能(xterm 3.x+xterm 4.x)-程序员宅基地

技术标签: tab切换命令  xterm粘贴  工具类  xterm  vue  终端  

之前使用的xterm 3.x的粘贴功能不能用了,于是又改用了4.x的,我把所有的代码都放在这里吧,大家有问题也可以积极来探讨哦~

xterm github官网

一、使用流程

1.引入xterm.js

2.创建xterm实例并挂载到dom上

3.xterm全屏调整

4.xterm与websocket结合发送数据并显示在屏幕上


二、需要注意

1.关于输入与粘贴

xterm4.x应该是在2019年3月份就开始更新了,但现在网上用的大多数的版本还都是3.x,我之前用的是3.12,输入方法和粘贴方法分别用的是

term.on(‘key’,function()}
term.on(‘paste’, fuction()}

而xterm4.x版本后API取消了on事件,而直接使用on+事件名,格式也有所调整

term.onData(function(key) {
let order = {
Data: key,
Op: “stdin”
};
_this.onSend(order);
});

这里坑也挺大的,一开始我没注意看文档,只看了是onData方法,认为只要使用term.onData = function(){}赋值就可以,但是会报错

xterm.js :Cannot set property onData of #<e> which has only a getter”

然后后来查官网和资料才发现(这里要吐槽资料真是少的可怜啊),是要给onData方法传一个方法做为参数去执行,而不是给它赋值。

2.关于全屏

然后就是关于全屏,因为之前受资料与知识范围限制发现4.x没有fullscreen方法所以才使用的3.x(在这里我要吐槽一下这个xterm.js的官网,真真是辣鸡的很,什么都不写清楚,就实例一下就完事了,好多参数和方法api里也不给个例子,还得自己到处去找,真的很辣鸡!!!),一开始以为4.0中不需要设置fit及fullscreen,引入css后直接设置行数和列数就能实现背景铺满全屏。

但遇到一个问题是,xterm默认代码不会占满一整行,而是会直接换行,所以这里我们需要使用到官网首页给到的插件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200610145046950.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODMxODI0NA==,size_16,color_FFFFFF,t_70

    import {
     FitAddon } from "xterm-addon-fit";

    // canvas背景全屏-默认
    var fitAddon = new FitAddon();
    term.loadAddon(fitAddon);
    fitAddon.fit();

    // 内容全屏显示-窗口大小发生改变时
    function resizeScreen(size) {
    
      console.log("size", size);
      try {
    
        fitAddon.fit();

        // 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定
        term.onResize(size => {
    
          _this.onSend({
     Op: "resize", Cols: size.cols, Rows: size.rows });
        });
      } catch (e) {
    
        console.log("e", e.message);
      }
    }

    window.addEventListener("resize", resizeScreen);

3.关于字符删除与上下键切换命令等

值得注意的是,在我们使用xterm实现仿终端功能时,不需要对输入字符进行判断,也不需要在输入事件中把输入的字符打出来。因为在输入事件中执行的web socket连接中,每输入一个字符都会自动传到后端,而后端会根据你输入的回车符来判断是否要为你换行及返回何种数据。(来,重要的话跟我念三遍~)

我们不必关心用户输入与想做的操作,只需要向后台传递参数就好。
我们不必关心用户输入与想做的操作,只需要向后台传递参数就好。
我们不必关心用户输入与想做的操作,只需要向后台传递参数就好。

4.实现web terminal代码

(1).xterm 4.4.0(最新)

(下面就是整个文件的方法啦,此实例不能直接复制使用哦,因为websocket是封装好的,大家看一下使用方法就好~)

<template>
  <div
    style="height: 100%;
    background: #002833;"
  >
    <div id="terminal" ref="terminal"></div> //terminal容器
  </div>
</template>

<script>
// 引入xterm,请注意这里和3.x版本的引入路径不一样
import {
     Terminal } from "xterm";
import "xterm/css/xterm.css";
import "xterm/lib/xterm.js";

export default {
    
  name: "Shell",
  data() {
    
    return {
    
      shellWs: "",
      term: "", // 保存terminal实例
      rows: 40,
      cols: 100
    };
  },

  created() {
    
    this.wsShell();
  },

  mounted() {
    
    let _this = this;
    // 获取容器宽高/字号大小,定义行数和列数
    this.rows = document.querySelector(".indexContainer").offsetHeight / 16 - 6;
    this.cols = document.querySelector(".indexContainer").offsetWidth / 14;

    let term = new Terminal({
    
      rendererType: "canvas", //渲染类型
      rows: parseInt(_this.rows), //行数
      cols: parseInt(_this.cols), // 不指定行数,自动回车后光标从下一行开始
      convertEol: true, //启用时,光标将设置为下一行的开头
      //   scrollback: 50, //终端中的回滚量
      disableStdin: false, //是否应禁用输入。
      cursorStyle: "underline", //光标样式
      cursorBlink: true, //光标闪烁
      theme: {
    
        foreground: "#7e9192", //字体
        background: "#002833", //背景色
        cursor: "help", //设置光标
        lineHeight: 16
      }
    });

    // 创建terminal实例
    term.open(this.$refs["terminal"]);

    // 换行并输入起始符“$”
    term.prompt = () => {
    
      term.write("\r\n$ ");
    };
    term.prompt();

    // // canvas背景全屏
    var fitAddon = new FitAddon();
    term.loadAddon(fitAddon);
    fitAddon.fit();

    window.addEventListener("resize", resizeScreen);

    // 内容全屏显示
    function resizeScreen() {
    
      // 不传size

      try {
    
        fitAddon.fit();

        // 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定
        // 这里不使用size默认参数,因为改变窗口大小只会改变size中的列数而不能改变行数,所以这里不使用size.clos,而直接使用获取我们根据窗口大小计算出来的行列数
        term.onResize(() => {
    
          _this.onSend({
     Op: "resize", Cols: term.cols, Rows: term.rows });
        });
      } catch (e) {
    
        console.log("e", e.message);
      }
    }

    function runFakeTerminal(_this) {
    
      if (term._initialized) {
    
        return;
      }
      // 初始化
      term._initialized = true;

      term.writeln("Welcome to use Superman. ");
      term.writeln(
        `This is Web Terminal of pod\x1B[1;3;31m ${
      
          _this.urlParam.podName
        }\x1B[0m in namespace\x1B[1;3;31m ${
      _this.urlParam.namespace}\x1B[0m`
      );

      term.prompt();

      // / **
      //     *添加事件监听器,用于按下键时的事件。事件值包含
      //     *将在data事件以及DOM事件中发送的字符串
      //     *触发了它。
      //     * @返回一个IDisposable停止监听。
      //  * /
      //   / ** 更新:xterm 4.x(新增)
      //  *为数据事件触发时添加事件侦听器。发生这种情况
      //  *用户输入或粘贴到终端时的示例。事件值
      //  *是`string`结果的结果,在典型的设置中,应该通过
      //  *到支持pty。
      //  * @返回一个IDisposable停止监听。
      //  * /
      // 支持输入与粘贴方法
      term.onData(function(key) {
    
        let order = {
    
          Data: key,
          Op: "stdin"
        };
        _this.onSend(order);
        // 为解决窗体resize方法才会向后端发送列数和行数,所以页面加载时也要触发此方法
        _this.onSend({
    
          Op: "resize",
          Cols: parseInt(term.cols),
          Rows: parseInt(term.rows)
        });
      });

      _this.term = term;
    }
    runFakeTerminal(_this);

  },

  methods: {
    
  
    /**
     * **wsShell 创建页面级别的websocket,加载页面数据
     * ws 接口:/xxx/xxx/xxx
     * 参数:无
     * ws参数:
     * @deployId   任务id
     * @tagString  当前节点
     * 返回:无
     * **/
    wsShell() {
    
      const _this = this;
      let tag = this.urlParam.Tag;
      let name= this.urlParam.name;
      let pod= this.urlParam.pod;

      let query = `?tag=${
      tag}&name=${
      name}&pod=${
      pod}`;
      let url = `xxxx/xxxx${
      query}`// websocket连接接口

      this.shellWs = this.base.WS({
    
        url,
        isInit: true,
        openFn() {
    
          //   _this.term.resize({ rows: _this.rows, cols: 100 }); //终端窗口重新设置大小 并触发term.on("resize")
        },
        messageFn(e) {
    
          console.log("message", e);
          if (e) {
    
            let data = JSON.parse(e.data);
            if (data.Data == "\n" || data.Data == "\r\nexit\r\n") {
    
              _this.$message("连接已关闭");
            }
            // 打印后端返回数据
            _this.term.write(data.Data);
          }
        },
        errorFn(e) {
    
          //出现错误关闭当前ws,并且提示
          console.log("error", e);
          _this.$message.error({
    
            message: "ws 请求失败,请刷新重试~",
            duration: 5000
          });
        }
      });
    },

    onSend(data) {
    
      data = this.base.isObject(data) ? JSON.stringify(data) : data;
      data = this.base.isArray(data) ? data.toString() : data;
      data = data.replace(/\\\\/, "\\");
      this.shellWs.onSend(data);
    },

    //删除左右两端的空格
    trim(str) {
    
      return str.replace(/(^\s*)|(\s*$)/g, "");
    }
  }
};
</script>


(2).xterm 3.x

<template>
  <div
    style="height: 100%;
    background: #002833;"
  >
    <div id="terminal" ref="terminal"></div>
  </div>
</template>

<script>
import {
     Terminal } from "xterm";
import "xterm/dist/xterm.css";
import * as fit from "xterm/lib/addons/fit/fit";

import * as fullscreen from "xterm/lib/addons/fullscreen/fullscreen";
import * as attach from "xterm/lib/addons/attach/attach";

Terminal.applyAddon(fit);
Terminal.applyAddon(attach);
Terminal.applyAddon(fullscreen); // Apply the `fullscreen` addon

export default {
    
  name: "Shell",
  data() {
    
    return {
    
      order: "",
      urlParam: {
    
        fullTag: "",
        namespace: "",
        podName: ""
      },
      shellWs: "",
      inputValue: "",
      term: "", // 保存terminal实例
      showOrder: "", // 保存服务端返回的命令
      inputList: [], // 保存用户输入的命令,用以上下健切换
      beforeUnload_time: "",
      rows: 40,
      cols: 100
    };
  },

  created() {
    
    this.checkURLparam();
    this.wsShell();
  },

  mounted() {
    
    let _this = this;
    this.rows = document.querySelector(".indexContainer").offsetHeight / 16 - 5;
    this.cols = document.querySelector(".indexContainer").offsetWidth / 14;

    //this.cols = 400

    let term = new Terminal({
    
      rendererType: "canvas", //渲染类型
      rows: parseInt(_this.rows), //行数
      cols: parseInt(_this.cols), // 不指定行数,自动回车后光标从下一行开始
      convertEol: true, //启用时,光标将设置为下一行的开头
      //   scrollback: 50, //终端中的回滚量
      disableStdin: false, //是否应禁用输入。
      cursorStyle: "underline", //光标样式
      cursorBlink: true, //光标闪烁
      theme: {
    
        foreground: "#7e9192", //字体
        background: "#002833", //背景色
        cursor: "help", //设置光标
        lineHeight: 16
      }
    });
    // 换行并输入起始符“$”
    term.prompt = () => {
    
      term.write("\r\n$ ");
    };
    // Load WebLinksAddon on terminal, this is all that's needed to get web links
    // working in the terminal.
    // term.loadAddon(new WebLinksAddon());

    term.open(this.$refs["terminal"]);
    term.toggleFullScreen(); //全屏

    window.onresize = function() {
    
      term.fit();
      term.toggleFullScreen(); //全屏
      term.prompt();
    }
    function runFakeTerminal(_this) {
    
      if (term._initialized) {
    
        return;
      }

      term._initialized = true;

      term.prompt = () => {
    
        term.write("\r\n ");
      };

      term.writeln("Welcome to use Superman. ");
      term.writeln(
        `This is Web Terminal of pod\x1B[1;3;31m ${
      
          _this.urlParam.podName
        }\x1B[0m in namespace\x1B[1;3;31m ${
      _this.urlParam.namespace}\x1B[0m`
      );

      term.prompt();

      //   console.log("term", term);

      // 监控键盘输入事件
      // / **
      //     *添加事件监听器,用于按下键时的事件。事件值包含
      //     *将在data事件以及DOM事件中发送的字符串
      //     *触发了它。
      //     * @返回一个IDisposable停止监听。
      //  * /

      term.on("key", function(key) {
    
        let order = {
    
          Data: key,
          Op: "stdin"
        };

        _this.onSend(order);
      });

      term.on("paste", function(data) {
    
        _this.order = data;
        term.write(data);
      });

      term.on("resize", size => {
    
        let order = {
    
          Rows: parseInt(size.rows),
          Cols: parseInt(size.cols),
          Op: "resize"
        };

        _this.onSend(order);
      });

      _this.term = term;
    }
    runFakeTerminal(_this);
  },

  methods: {
    
    /**
     * **wsShell 创建页面级别的websocket,加载页面数据
     * ws 接口:/xxxx/xxxx
     * 参数:无
     * ws参数:
     * @deployId   任务id
     * @tagString  当前节点
     * 返回:无
     * **/
    wsShell() {
    
      const _this = this;
      let tag = this.urlParam.Tag;
      let name = this.urlParam.name;
      let pod = this.urlParam.pod;

      let query = `?tag=${
      tag}&name=${
      name}&pod=${
      pod}`;
      let url = `xxxx/xxxx${
      query}`;

      this.shellWs = this.base.WS({
    
        url,
        isInit: true,
        openFn() {
    
          _this.term.resize({
     rows: _this.rows, cols: 100 }); //终端窗口重新设置大小 并触发term.on("resize")
        },
        messageFn(e) {
    
          console.log("message", e);
          if (e) {
    
            let data = JSON.parse(e.data);
            if (data.Data == "\n" || data.Data == "\r\nexit\r\n") {
    
              _this.$message("连接已关闭");
            }
            _this.term.write(data.Data);
            _this.showOrder = data.Data;
            _this.order = "";
          }
        },
        errorFn(e) {
    
          //出现错误关闭当前ws,并且提示
          console.log("error", e);
          _this.$message.error({
    
            message: "ws 请求失败,请刷新重试~",
            duration: 5000
          });
        }
      });
    },

    onSend(data) {
    
      data = this.base.isObject(data) ? JSON.stringify(data) : data;
      data = this.base.isArray(data) ? data.toString() : data;
      data = data.replace(/\\\\/, "\\");
      this.shellWs.onSend(data);
    },

    //删除左右两端的空格
    trim(str) {
    
      return str.replace(/(^\s*)|(\s*$)/g, "");
    }
  }
};
</script>

5.封装的websocket方法

/**
 * ** WebSocket 封装
 * @ url         请求地址                   类型:string         默认:''       备注: 'web/msg'
 * @ isInit      是否自动执行                类型:boolean        默认:false    备注: false|true
 * @ openFn      自动执行open回调函数         类型:function       默认 : null    备注: 如果onOpen没有callBack,默认调用openFn
 * @ messageFn   自动执行消息回调函数         类型:function       默认: null    备注: 如果onMessage没有callBack,默认调用messageFn
 * @ errorFn     自动执行错误回调函数         类型:function       默认: null    备注: 如果onErrorFn没有callBack,默认调用errorFn
 *
 *
 * 方法:
 * isWebsocket   判断websocket 是否存在         返回 true|false      参数:无
 * onOpen        服务端与前端连接成功后触发开      返回 无              参数:callBack(e)
 * onMessage     服务端向前端发送消息时触发        返回 无              参数:callBack(e)
 * onError       WSC报错后触发                  返回 无              参数:callBack(e)
 * onClose       关闭WSC
 * onSend        前端向服务端发送消息时触发        返回 无              参数:data
 * readyState    获取WSC链接状态,只读不可修改
 * binaryType    获取WSC连接所传输二进制数据的类型,只读
 * get           获取当前实例                   返回 当前实例          参数:data
 * */
export class WS {
    
  constructor({
    
    url = "",
    openFn = null,
    messageFn = null,
    errorFn = null,
    isInit = false
  } = {
    }) {
    
    let loc = window.location;
    url = loc.host + "/" + url;
    this.url = /https/.test(loc.protocol) ? "wss://" + url : "ws://" + url;
    this.websocket = "WebSocket" in window ? new WebSocket(this.url) : null;
    this.error = "";
    this.messageFn =
      messageFn && typeof messageFn == "function"
        ? messageFn
        : e => {
    
            e;
          };
    this.errorFn =
      errorFn && typeof errorFn == "function"
        ? errorFn
        : e => {
    
            e;
          };
    this.openFn =
      openFn && typeof openFn == "function"
        ? openFn
        : e => {
    
            e;
          };
    if (isInit) {
    
      WS.init(this);
    }
  }

  //判断websocket 是否存在
  isWebsocket() {
    
    if (this.websocket) {
    
      this.error = "";
      return true;
    } else {
    
      this.error = "当前浏览器不支持WebSocket";
      return false;
    }
  }

  //直接开始执行链接,不需要手动设置打开 & 处理消息 & 错误
  static init(_this) {
    
    if (_this.isWebsocket()) {
    
      _this.websocket.onopen = e => {
    
        _this.openFn(e);
      };

      _this.websocket.onerror = e => {
    
        _this.errorFn(e);
      };
      _this.websocket.onmessage = e => {
    
        _this.messageFn(e);
      };
    } else {
    
      console.error(_this.error);
    }
  }

  //自定义WSC连接事件:服务端与前端连接成功后触发
  onOpen(callBack) {
    
    if (this.isWebsocket()) {
    
      //判断是否传递回调函数
      if (typeof callBack == "function") {
    
        this.websocket.onopen = e => {
    
          callBack(e);
        };
      } else {
    
        this.websocket.onopen = e => {
    
          this.openFn(e);
        };
      }
    } else {
    
      console.error(this.error);
    }
  }

  // WSC消息接收事件:服务端向前端发送消息时触发
  onMessage(callBack) {
    
    if (this.isWebsocket()) {
    
      if (typeof callBack == "function") {
    
        this.websocket.onmessage = e => {
    
          callBack(e);
        };
      } else {
    
        this.websocket.onmessage = e => {
    
          this.messageFn(e);
        };
      }
    } else {
    
      console.error(this.error);
    }
  }

  // 自定义WSC异常事件:WSC报错后触发
  onError(callBack) {
    
    if (this.isWebsocket()) {
    
      if (typeof callBack == "function") {
    
        this.websocket.onerror = e => {
    
          callBack(e);
        };
      } else {
    
        this.websocket.onerror = e => {
    
          this.errorFn(e);
        };
      }
    } else {
    
      console.error(this.error);
    }
  }

  // 自定义WSC关闭事件:WSC关闭后触发
  onClose() {
    
    if (this.isWebsocket()) {
    
      this.websocket.close();
    } else {
    
      console.error(this.error);
    }
  }

  //前端向服务端发送消息时触发
  onSend(data) {
    
    console.log("dataweb--数据已向后端发送", data, this.isWebsocket());
    if (this.isWebsocket()) {
    
      console.log("sendsendsend");
      this.websocket.send(data);
    } else {
    
      console.error(this.error);
    }
  }

  //WSC链接状态,只读不可修改
  readyState() {
    
    //1连接已打开并准备好进行通信。2连接正在关闭。 3连接已关闭或无法打开。
    if (this.isWebsocket()) {
    
      return this.websocket.readyState;
    } else {
    
      console.error(this.error);
    }
  }

  //获取WSC连接所传输二进制数据的类型,只读
  binaryType() {
    
    if (this.isWebsocket()) {
    
      return this.websocket.binaryType;
    } else {
    
      console.error(this.error);
    }
  }

  //获取当前实例
  get() {
    
    if (this.isWebsocket()) {
    
      return this.websocket;
    } else {
    
      console.error(this.error);
    }
  }
}

websocket引用
import {
     WS } from "@/config/service/websocket.config";

const install = function(Vue) {
    
  const base = {
    
    //参数&方法 
    WS({
     url, openFn, messageFn, errorFn, isInit = false } = {
    }) {
    
      return new WS({
     url, openFn, messageFn, errorFn, isInit });
    },
  };
  Vue.prototype.base = base;
};

export default {
    
  install
};

导入main.js

import base from "./config/libs/base"; //导入公共方法

Vue.use(base); //ui

正文其实已经结束了,下边都是废弃代码和废话,可以不用看哈~






然后剩下就是我之前的辣鸡代码了,判断了一堆输入键盘的字符,比如输入回车才发送数据,输入退格要把我输入的字符删一个再打印,输入上下键切换命令也要自行判断……是真的费了好多时间和精力,所以虽然他没什么用了,但是我舍不得彻底删掉它们,就在这里给它们留一席之地吧哈哈。

<template>
  <div>
    <div id="terminal" ref="terminal"></div>
  </div>
</template>

<script>
import {
     Terminal } from "xterm";
// import { WebLinksAddon } from "xterm-addon-web-links";
import "xterm/dist/xterm.css";
// import "xterm/dist/addons/fullscreen/fullscreen.css"; //如果不成功,请检查路径

import * as fit from "xterm/lib/addons/fit/fit";

import * as fullscreen from "xterm/lib/addons/fullscreen/fullscreen";
import * as attach from "xterm/lib/addons/attach/attach";

Terminal.applyAddon(fit);
Terminal.applyAddon(attach);
Terminal.applyAddon(fullscreen); // Apply the `fullscreen` addon

export default {
    
  name: "Shell",
  data() {
    
    return {
    
      order: "",
      urlParam: {
    
        fullTag: "",
        namespace: "",
        podName: ""
      },
      shellWs: "", // ws实例
      term: "", // 保存terminal实例
      showOrder: "", // 保存服务端返回的命令
      inputList: [] // 保存用户输入的命令,用以上下健切换
    };
  },

  created() {
    
    this.checkURLparam();
    this.wsShell();
  },

  mounted() {
    
    let _this = this;
    // const terminal = new Terminal();
    let term = new Terminal({
    
      rendererType: "canvas", //渲染类型
      rows: 40, //行数
      convertEol: true, //启用时,光标将设置为下一行的开头
      scrollback: 10, //终端中的回滚量
      disableStdin: false, //是否应禁用输入。
      cursorStyle: "underline", //光标样式
      cursorBlink: true, //光标闪烁
      theme: {
    
        foreground: "yellow", //字体
        background: "#060101", //背景色
        cursor: "help" //设置光标
      }
    });
    // 换行并输入起始符“$”
    term.prompt = () => {
    
      term.write("\r\n$ ");
    };

    term.open(this.$refs["terminal"]);
    term.toggleFullScreen(); //全屏
    term.fit();

    term.writeln("Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ");
    term.prompt();

    function runFakeTerminal(_this) {
    
      if (term._initialized) {
    
        return;
      }

      term._initialized = true;

      term.prompt = () => {
    
        term.write("\r\n ");
      };

      term.writeln("Welcome to xterm.js");
      term.writeln(
        "This is a local terminal emulation, without a real terminal in the back-end."
      );
      term.writeln("Type some keys and commands to play around.");
      term.writeln("");
      term.prompt();

      // 监控键盘输入事件
      // / **
      //     *添加事件监听器,用于按下键时的事件。事件值包含
      //     *将在data事件以及DOM事件中发送的字符串
      //     *触发了它。
      //     * @返回一个IDisposable停止监听。
      //  * /
      let last = 0;

      term.on("key", function(key, ev) {
    
        // 可打印状态,即不是alt键ctrl等功能健时
        const printable =
          !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;

        // 因服务端返回命令包含乱码,但使用write方法输出时并不显示,故将真实显示内容截取出来
        let index = _this.showOrder.indexOf("sh");
        let show = _this.showOrder.substr(index, _this.showOrder.length - 1);

        //  当输入回车时
        if (ev.keyCode === 13) {
    
          if (_this.order == "cls" || _this.order == "clear") {
    
            _this.order = "";
            return false;
          }
          //先将数据发送
          term.prompt();
          // 判断如果不是英文给出提醒
          let reg = /[a-zA-Z]/;
          let order = {
    
            Data: _this.order,
            Op: "stdin"
          };

          if (!reg.test(_this.order)) {
    
            term.writeln("请输入有效指令~");
          } else {
    
            // 发送数据
            _this.inputList.push(_this.order);
            last = _this.inputList.length - 1;
            _this.onSend(order);
            // 清空输入内容变量
          }
        } else if (ev.keyCode === 8) {
    
          // 当输入退

          // Do not delete the prompt
          // 当前行字符长度如果等于后端返回字符就不进行删除
          if (term._core.buffer.x > _this.showOrder.length) {
    
            term.write("\b \b"); // 输出退格
          }

          // 将输入内容变量删除

          if (_this.trim(_this.order) == _this.trim(_this.showOrder)) {
    
            return false;
          } else {
    
            _this.order = _this.order.substr(0, _this.order.length - 1);
          }
        } else if (ev.keyCode == 38 || ev.keyCode == 40) {
    
          let len = _this.inputList.length;
          let code = ev.keyCode;

          if (code === 38 && last <= len && last >= 0) {
    
            // 直接取出字符串数组最后一个元素
            let inputVal = _this.inputList[last];
            term.write(inputVal);
            if (last > 0) {
    
              last--;
            }
          }
          if (code === 40 && last < len) {
    
            // last现在为当前元素
            if (last == len - 1) {
    
              return;
            }
            if (last < len - 1) {
    
              last++;
            }

            let inputVal = _this.inputList[last];
            term.write(inputVal);
          }
        } else if (ev.keyCode === 9) {
    
          // 如果按tab键前输入了之前后端返回字符串的第一个字符,就显示此命令
          if (_this.order !== "" && show.indexOf(_this.order) == 0) {
    
            term.write(_this.showOrder);
          }
        } else if (printable) {
    
          // 当为可打印内容时
          if (/[a-zA-Z]/.test(key)) {
    
            key = key.toLowerCase();
          }
          // 存入输入内容变量
          _this.order = _this.order + key;
          // 将变量写入终端内
          term.write(key);
        }
      });

      _this.term = term;

      // 粘贴事件
      term.on("paste", function(data) {
    
        _this.order = data;
        term.write(data);
      });
    }
    runFakeTerminal(_this);
  },

  methods: {
    
    // 检查url参数,必要参数不存在,返回到首页
    checkURLparam() {
    
      let urlObj = this.base.urlValue();

      let fullTag = urlObj.full_tag ? urlObj.full_tag : ""; //所在父节点
      fullTag = fullTag.replace(/(^ +| +$)/g, "");

      let namespace = urlObj.namespace ? urlObj.namespace : ""; //所在父节点
      namespace = namespace.replace(/(^ +| +$)/g, "");

      let podName = urlObj.pod_name ? urlObj.pod_name : ""; //所在父节点
      podName = podName.replace(/(^ +| +$)/g, "");

      if (!fullTag || !namespace) {
    
        //所在的父级节点为空或者deploy_id不存在的情况下,弹框提示然后返回首页
        this.$alert("缺少必要参数,马上返回首页~", "提示", {
    
          closeOnClickModal: false,
          closeOnPressEscape: false,
          showClose: false,
          confirmButtonText: "确定",
          type: "error",
          callback: () => {
    
            this.$router.replace("/web");
          }
        });
      } else {
    
        this.urlParam.fullTag = fullTag; //所在父节点
        this.urlParam.namespace = namespace; //当前部署任务详情id
        this.urlParam.podName = podName; //当前部署任务详情id
      }
    },
    /**
     * **wsShell 创建页面级别的websocket,加载页面数据
     * ws 接口:/v1/task/deploy/detail/container
     * 参数:无
     * ws参数:
     * @deployId   任务id
     * @tagString  当前节点
     * 返回:无
     * **/
    wsShell() {
    
      const _this = this;
      let tag_string = this.urlParam.fullTag;
      let namespace = this.urlParam.namespace;
      let pod_name = this.urlParam.podName;
      let query = `?tag_string=${
      tag_string}&namespace=${
      namespace}&pod_name=${
      pod_name}`;
      let url = `v1/container/terminal/ws${
      query}`;
      //   let loading; //初始化加载状态变量
      this.shellWs = this.base.WS({
    
        url,
        isInit: true,
        openFn() {
    
          console.log("open");
        },
        messageFn(e) {
    
          console.log("message", e);
          if (e) {
    
            let data = JSON.parse(e.data);
            // 如果返回字符包含这些字符显示close提示
            if (data.Data == "\n" || data.Data == "\r\nexit\r\n") {
    
              alert("closed");
            }
            _this.term.write(data.Data);
            _this.showOrder = data.Data;
            _this.order = "";
          }
        },
        errorFn(e) {
    
          //出现错误关闭当前ws,并且提示
          console.log("error", e);
          _this.$message.error({
    
            message: "ws 请求失败,请刷新重试~",
            duration: 5000
          });
        }
      });
    },

    onSend(data) {
    
      data = this.base.isObject(data) ? JSON.stringify(data) : data;
      data = this.base.isArray(data) ? data.toString() : data;
      data = data.replace(/\\\\/, "\\");
      this.shellWs.onSend(data);
    },

    //删除左右两端的空格
    trim(str) {
    
      return str.replace(/(^\s*)|(\s*$)/g, "");
    }
  }
};
</script>

然后就是要感谢我所看到的三个资料,把它们放在这里啦,大家自行查看吧
1.https://www.v2ex.com/t/633464
2.https://github.com/billchurch/webssh2/blob/master/app/client/src/js/index.js
3.https://github.com/knva/xtermtest/blob/master/index.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38318244/article/details/103908129

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签