基於 xterm + websocket + vue 實現網頁版終端 terminal

cdn形式html頁面實現

在進行開發這個功能的時候,進行了百度,最後參考此博主的文章中的自定義版本,進行修改:https://blog.csdn.net/qq_25252769/article/details/127791918

開發過程中,由於後臺數據返回的格式需要進行處理,根據自身需求,代碼進行了少許修改

主要修改1:

根據後臺返回的數據,需要傳入一個對象,裏邊key值爲message,並對這個對象進行序列化
主要修改2:
// this.ws.send("\n");//這邊原來是放開狀態,但由於後臺數據返回原因會報錯,就直接註釋了
主要修改3:
接收信息時,
let message = "\n" + JSON.parse(event.data).message; //根據後臺返回數據進行處理成對象形式,獲取到message,進行顯示到頁面
const blob = new Blob([message], {
   type: "text/plain",
});
原內容是:
//將字符串轉換成 Blob對象
const blob = new Blob([event.data], {
type: 'text/plain'
})

,最終修改後的代碼如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>前端終端,操作後端的docker容器</title>
    <!-- 引入樣式 -->
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/css/xterm.css">
</head>

<body class="bodyCss">
    <div id="app">
        <div id="terminal" ></div>
    </div>
</body>
<script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
<script src="https://lib.baomitu.com/vue/2.6.14/vue.js"></script>
<script  src="https://unpkg.com/[email protected]/lib/xterm-addon-fit.js"></script>
  <script>
    let wsTime = null;
    new Vue({
      el: "#app",
      data() {
        return {
          // 終端
          term: {},
          // websocket
          ws: {},
          // 用戶輸入
          command: "",
        };
      },
      created() {
        // 初始化終端
        this.initTerminal();
      },
      mounted() {
        // 建立websocket連接
        this.websocket();
      },
      beforeDestroy() {
        this.ws.close();
        this.term.dispose();
      },
      methods: {
        // 初始化終端配置
        initTerminal() {
          this.term = new Terminal({
            rendererType: "canvas", //渲染類型
            // rows: 40, //行數,影響最小高度
            cols: 100, // 列數,影響最小寬度
            convertEol: true, //啓用時,光標將設置爲下一行的開頭
            // scrollback: 50, //終端中的滾動條回滾量
            disableStdin: false, //是否應禁用輸入。
            cursorStyle: "underline", //光標樣式
            cursorBlink: true, //光標閃爍
            theme: {
              foreground: "#F8F8F8",
              background: "#2D2E2C",
              cursor: "help", //設置光標
              lineHeight: 16,
            },
            fontFamily: '"Cascadia Code", Menlo, monospace',
          });
        },
        // 自定義終端默認展示內容
        writeDefaultInfo() {
          let defaultInfo = [
            "┌\x1b[1m terminals \x1b[0m─────────────────────────────────────────────────────────────────┐ ",
            "│                                                                            │ ",
            "│  \x1b[1;34m 歡迎使用Web Docker SSH \x1b[0m                                                  │ ",
            "│                                                                            │ ",
            "└────────────────────────────────────────────────────────────────────────────┘ ",
          ];
          // 測試顏色區間
          // let arr = Array.from({length:100},(v,i)=>v = i)
          // console.log(arr)
          // arr.map((item,i) => {
          //     defaultInfo.push(`Hello from \x1B[1;3;${i}m ${i} \x1B[0m  \u2764\ufe0f   ${i}`)
          // })
          this.term.write(defaultInfo.join("\n\r"));
          this.writeOfColor("我是加粗斜體紅色的字呀", "1;3;", "31m");
          // this.term.write('\n\r$ ')
        },
        //
        writeOfColor(txt, fontCss = "", bgColor = "") {
          // 在Linux腳本中以 \x1B[ 開始,中間前部分是樣式+內容,以 \x1B[0m 結尾
          // 示例 \x1B[1;3;31m 內容 \x1B[0m
          // fontCss
          // 0;-4;字體樣式(0;正常 1;加粗 2;變細 3;斜體 4;下劃線)
          // bgColor
          // 30m-37m字體顏色(30m:黑色 31m:紅色 32m:綠色 33m:棕色字 34m:藍色 35m:洋紅色/紫色 36m:藍綠色/淺藍色 37m:白色)
          // 40m-47m背景顏色(40m:黑色 41m:紅色 42m:綠色 43m:棕色字 44m:藍色 45m:洋紅色/紫色 46m:藍綠色/淺藍色 47m:白色)
          this.term.write(`\x1B[${fontCss}${bgColor}${txt}\x1B[0m`);
        },
        // 監聽輸入
        userWrite() {
          this.term.onData((e) => {
            switch (e) {
              case "\u0003": // Ctrl+C
                this.term.write("^C ");
                this.term.write("\r\n$ ");
                break;
              case "\r": // Enter
                this.ws.send(JSON.stringify({ message: this.command }));//根據後臺返回的數據,需要傳入一個對象,裏邊key值爲message,並對這個對象進行序列化
                // this.ws.send("\n");//這邊原來是放開狀態,但由於後臺數據返回原因會報錯,就直接註釋了
                this.command = "";
                // this.term.write('\r\n$ ')
                break;
              case "\u007F": // Backspace (DEL)
                // Do not delete the prompt
                if (this.term._core.buffer.x > 2) {
                  this.term.write("\b \b");
                  if (this.command.length > 0) {
                    this.command = this.command.substr(
                      0,
                      this.command.length - 1
                    );
                  }
                }
                break;
              default: // Print all other characters for demo
                if (
                  (e >= String.fromCharCode(0x20) &&
                    e <= String.fromCharCode(0x7e)) ||
                  e >= "\u00a0"
                ) {
                  this.command += e;
                  this.writeOfColor(e, "2;3;", "33m");
                  console.log("用戶輸入command", this.command);
                }
            }
          });
        },
        // 建立websocket連接
        websocket() {
          // WebSocket start
          if ("WebSocket" in window) {
            //需要修改ip和id
            //例如:const url = `ws://192.168.111.222:2375/v1.41/containers/0eb8aafb4e6e/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1`
            const url = `ws:172.16.188.58:5000/ws/chat/?host=172.16.188.58&username=root&password=admin`;
            const ws = new WebSocket(url);
            this.ws = ws;
            this.$nextTick(() => {
              this.userWrite();
            });
            ws.onopen = (event) => {
              console.log("已建立連接:", event);
              // 輸入換行符可讓終端顯示當前用戶的工作路徑
              // ws.send("\n");//這邊原來是放開狀態,但由於後臺數據返回原因會報錯,就直接註釋了
              // 窗口自適應插件
              const fitAddon = new FitAddon.FitAddon();
              // 窗口尺寸變化時,終端尺寸自適應
              window.onresize = () => {
                fitAddon.fit();
              };
              this.term.loadAddon(fitAddon);
              this.term.open(document.getElementById("terminal"));
              this.term.focus();
              // 自定義終端默認展示內容
              this.writeDefaultInfo();
            };
            ws.onmessage = (event) => {
              console.log("接收信息:", event.data);
              let message = "\n" + JSON.parse(event.data).message; //根據後臺返回數據進行處理成對象形式進行顯示到頁面,如果不需要處理,需要直接
              //將字符串轉換成 Blob對象
              const blob = new Blob([message], {
                type: "text/plain",
              });
              //將Blob 對象轉換成字符串
              const reader = new FileReader();
              reader.readAsText(blob, "utf-8");
              reader.onload = (e) => {
                // 可以根據返回值判斷使用何種顏色或者字體,不過返回值自帶了一些字體顏色
                this.writeOfColor(reader.result, "0;", "37m");
              };
            };
            ws.onerror = (event) => {
              console.log("錯誤信息:", event);
              if (wsTime) {
                window.clearTimeout(wsTime);
                wsTime = null;
              }
              wsTime = window.setTimeout(() => {
                this.websocket();
              }, 3000);
            };
            ws.onclose = (event) => {
              console.log("已關閉連接:", event);
              // if (wsTime) {
              //    window.clearTimeout(wsTime)
              //    wsTime = null
              // }
              // wsTime = window.setTimeout(() => {
              //    this.websocket()
              // }, 3000)
            };
          } else {
            console.log("瀏覽器不支持 WebSocket..");
          }
          // WebSocket end
        },
      },
    });
  </script>
</html>

 如果在vue-cli中使用可以參考此文章:https://blog.csdn.net/weekdawn/article/details/125202972

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