vue3 终端实现 (vue3+xterm+websocket)

目录

一、xterm介绍

二、效果展示

三、vue文件实现代码


一、xterm介绍

xterm是一个使用 TypeScript 编写的前端终端组件,可以直接在浏览器中实现一个命令行终端应用,通常与websocket一起使用。

二、效果展示

三、vue文件实现代码

javascript 复制代码
<template>
  <div class="bg-main">
    <div
      ref="terminal"
      v-loading="loading"
      class="terminal"
      element-loading-text="拼命连接中"
    ></div>
  </div>
</template>
<script setup>
  import { ref, onMounted, onBeforeUnmount } from 'vue'
  import { debounce } from 'lodash'
  import { Terminal } from 'xterm'
  import { FitAddon } from 'xterm-addon-fit'
  import 'xterm/css/xterm.css'

  const terminal = ref(null)
  const fitAddon = new FitAddon()

  let first = ref(true)
  let loading = ref(true)
  let terminalSocket = ref(null)
  let term = ref(null)

  // 初始化WS
  const initWS = () => {
    if (!terminalSocket.value) {
      createWS()
    }
    if (terminalSocket.value && terminalSocket.value.readyState > 1) {
      terminalSocket.value.close()
      createWS()
    }
  }

  // 创建WS
  const createWS = () => {
    // const url = `/access/Api/ws/ssh/b172df81-2485-453d-a6ff-120c03821536?userName=test&passwd=1`
    terminalSocket.value = new WebSocket(
      `wss://XXXX`
    )
    terminalSocket.value.onopen = runRealTerminal //WebSocket 连接已建立
    terminalSocket.value.onmessage = onWSReceive //收到服务器消息
    terminalSocket.value.onclose = closeRealTerminal //WebSocket 连接已关闭
    terminalSocket.value.onerror = errorRealTerminal //WebSocket 连接出错
  }

  //WebSocket 连接已建立
  const runRealTerminal = () => {
    loading.value = false
  }
  //WebSocket收到服务器消息
  const onWSReceive = (message) => {
    // 首次接收消息,发送给后端,进行同步适配尺寸
    if (first.value === true) {
      first.value = false
      resizeRemoteTerminal()
    }
    const data = message.data
    // base64解密
    const reader = new FileReader()
    reader.onload = function (e) {
      const base64Content = e.target.result
      console.log(base64Content, 1)
      term.value.write(base64Content)
    }
    reader.readAsText(data) // 以text文本显示readAsText
    term.value.element && term.value.focus()
  }
  //WebSocket 连接出错
  const errorRealTerminal = (ex) => {
    let message = ex.message
    if (!message) message = 'disconnected'
    term.value.write(`\x1b[31m${message}\x1b[m\r\n`)
    console.log('err')
  }
  //WebSocket 连接已关闭
  const closeRealTerminal = () => {
    console.log('close')
  }

  // 初始化Terminal
  const initTerm = () => {
    term.value = new Terminal({
      // lineHeight: 1.2,
      fontSize: 14,
      fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
      theme: {
        background: '#181d28',
      },
      // 光标闪烁
      cursorBlink: true,
      cursorStyle: 'underline',
      // scrollback: 100,
      // tabStopWidth: 4,
    })
    term.value.open(terminal.value) //挂载dom窗口
    term.value.loadAddon(fitAddon) //自适应尺寸
    // 不能初始化的时候fit,需要等terminal准备就绪,可以设置延时操作
    setTimeout(() => {
      fitAddon.fit()
    }, 5)
    termData() //Terminal 事件挂载
  }

  // 终端输入触发事件
  const termData = () => {
    // 输入与粘贴的情况,onData不能重复绑定,不然会发送多次
    term.value.onData((data) => {
      console.log(data, '传入服务器')
      if (isWsOpen()) {
        terminalSocket.value.send(
          JSON.stringify({
            type: 'terminal',
            data: {
              base64: btoa(data),
            },
          })
        )
      }
    })
    // 终端尺寸变化触发
    term.value.onResize(() => {
      resizeRemoteTerminal()
    })
  }

  //尺寸同步 发送给后端,调整后端终端大小,和前端保持一致,不然前端只是范围变大了,命令还是会换行
  const resizeRemoteTerminal = () => {
    const { cols, rows } = term.value
    if (isWsOpen()) {
      terminalSocket.value.send(
        JSON.stringify({
          type: 'resize',
          data: {
            rows: rows,
            cols: cols,
          },
        })
      )
    }
  }

  // 是否连接中0 1 2 3 状态
  const isWsOpen = () => {
    const readyState = terminalSocket.value && terminalSocket.value.readyState
    return readyState === 1
  }

  // 适应浏览器尺寸变化
  const fitTerm = () => {
    fitAddon.fit()
  }
  const onResize = debounce(() => fitTerm(), 500)
  const onTerminalResize = () => {
    window.addEventListener('resize', onResize)
  }
  const removeResizeListener = () => {
    window.removeEventListener('resize', onResize)
  }

  onMounted(() => {
    initWS()
    initTerm()
    onTerminalResize()
  })

  onBeforeUnmount(() => {
    removeResizeListener()
    terminalSocket.value && terminalSocket.value.close()
  })
</script>
<style lang="scss" scoped>
  .terminal {
    width: 100%;
    height: calc(100% - 62px);
  }
</style>
相关推荐
自由彩虹17 分钟前
靶场通关15--bossplayersctf
linux·服务器·网络·安全·web安全·网络安全
叼烟扛炮19 分钟前
Linux 学习笔记(四十五):Web 安全基石——Cookie、Session 与 HTTPS 原理解析
linux·前端·网络·笔记·学习·tcp/ip·安全
2401_8685347821 分钟前
网络规划设计师之案例分析 - 网络安全与等级保护
服务器·网络
是真的小外套25 分钟前
网络安全实战第10期:Nmap主机发现之一键扫清整个网段
网络·安全·web安全
北冥you鱼35 分钟前
区块链供应链金融如何降低中小企业融资成本
网络·金融·区块链
Linux_cy1 小时前
nginx 502 排查手册:九种原因分类、定位命令与检查顺序
linux·运维·服务器·网络·数据库·nginx·ssh
tedcloud1231 小时前
Docker Compose 部署 Uptime Kuma:配置 HTTPS、QQ 邮箱告警与数据备份
服务器·网络协议·ui·docker·容器·https·开源
嵌入式学习菌1 小时前
HTTP/HTTPS、MQTT/MQTTS 区别
网络协议·http·https
前端 贾公子2 小时前
第 6 章:MCP 服务器介绍与接入
运维·服务器·网络
2301_7807896611 小时前
跨平台适配!SDK 游戏盾兼容全终端游戏场景
java·网络·ddos