xterm.js + vue + websocket實現終端功能(xterm 3.x+xterm 4.x)

之前使用的xterm 3.x的粘貼功能不能用了,於是又改用了4.x的,我把所有的代碼都放在這裏吧,大家有問題也可以積極來探討哦~

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章