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>
相关推荐
热爱跑步的恒川2 小时前
【论文复现】基于图卷积网络的轻量化推荐模型
网络·人工智能·开源·aigc·ai编程
云飞云共享云桌面3 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
音徽编程5 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
23zhgjx-NanKon7 小时前
华为eNSP:QinQ
网络·安全·华为
23zhgjx-NanKon7 小时前
华为eNSP:mux-vlan
网络·安全·华为
点点滴滴的记录7 小时前
RPC核心实现原理
网络·网络协议·rpc
Lionhacker8 小时前
网络工程师这个行业可以一直干到退休吗?
网络·数据库·网络安全·黑客·黑客技术
程思扬8 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节
ZachOn1y8 小时前
计算机网络:运输层 —— 运输层概述
网络·tcp/ip·计算机网络·运输层