当前位置: 首页 > article >正文

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

 

目录

一、xterm介绍

二、效果展示

三、vue文件实现代码


一、xterm介绍

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

二、效果展示

三、vue文件实现代码

<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>


http://www.kler.cn/news/147811.html

相关文章:

  • ubuntu 安装python3.13
  • React自定义 Hook
  • 人工智能-优化算法和深度学习
  • Android Studio导入项目一直显示正在下载Gradle项目
  • 将图像的rgb数据转成DICOM医学图像格式
  • Git介绍和基础命令解析
  • 玩转微服务-技术篇-JSDOC教程
  • nvm安装以及解决踩坑
  • Java后端使用XWPFDocument生成word文档,踩坑
  • 【心得】XXE漏洞利用个人笔记
  • Python3.6.8升级Python3.12.0版本小记
  • Xshell远程登录AWS EC2 Linux实例
  • Linux—进程状态、僵尸进程、孤独进程、优先级
  • 【攻防世界-misc】reverseMe
  • LFM信号分析
  • 入侵redis之准备---Linux关于定时任务crontab相关知识了解配合理解shell反弹远程控制
  • 淘宝API接口系列:连接商户与消费者的桥梁
  • 【刷题笔记】分糖果||数组||暴力通过||符合思维方式||多案例分析
  • 饰品价格持续下跌,steam搬砖还有搞头吗?
  • 智能优化算法应用:基于蜻蜓算法无线传感器网络(WSN)覆盖优化 - 附代码
  • 锐捷:下一代防火墙修改密码
  • 【Qt】QStackedWidget、QRadioButton、QPushButton及布局实现程序首页自动展示功能
  • Android中根据字符串动态获取资源文件ID
  • 食品行业研发知识管理:企业网盘的选择与优势
  • 人民币已初步具备了国际使用的网络效应/首批疏解的在京部委所属4所高校雄安校区开工建设/墨茉点心局撤出北京市场
  • python读取PDF文件中的指定页码的范围并存储到指定的文件名
  • Rust语言入门教程(七) - 所有权系统
  • 阅读笔记——《Removing RLHF Protections in GPT-4 via Fine-Tuning》
  • C# 实现微信退款及对帐
  • QT 界面切换