xterm + react 实现线上终端组件demo

初始工作

  • npm install xterm

  • npm install xterm-addon-fit


//引入
import { memo, useEffect, useRef } from "react";
import { Terminal } from "xterm"
import { ITerminalOptions, ITerminalInitOnlyOptions } from "xterm"
import "xterm/css/xterm.css"
import React from "react";

import { FitAddon } from 'xterm-addon-fit';

const Xterminal = ({ dispatch }, onKey, props) => {
   //dispatch按需,不用可以不加
  const fitPlugin = new FitAddon();
  
  const terminalRef = useRef(null)
  const options = { ...ITerminalOptions, ...ITerminalInitOnlyOptions }
  let count = 0
  const term = new Terminal({
    rendererType: "canvas", //渲染类型
    // rows: 0, //行数
    // convertEol: true, //启用时,光标将设置为下一行的开头
    scrollback: 50, //终端中的回滚量
    disableStdin: false, //是否应禁用输入。
    cursorStyle: "underline", //光标样式
    cursorBlink: true, //光标闪烁
    wraparoundMode: true,
    windowsMode: true, // 根据窗口换行
    scrollback: 10,
    options,
    theme: {
      foreground: "#ffffff", //字体
      background: "#1a1a1d", //背景色
      cursor: "help", //设置光标
      lineHeight: 16
    }
  });
    //自定义终端首段提示
  let terminalTitleTemplate = "[root@server]\x1B[0m $ "
  // 换行并输入起始符 $
  term.prompt = () => {
    term.write(`\r\n${terminalTitleTemplate}`)
  }

  const keyAction = () => {
    // 定义变量获取整行数据
    let currentLineData = ''
    // 历史行输入数据
    let historyLineData = []
    let last = 0
    // 使其能够输入汉字
    term.onData(async key => {
      //enter键
      if (key.charCodeAt(0) === 13) {
        // 将行数据进行添加进去
        if (currentLineData !== '') {
          //将当前行数据传入历史命令数组中存储
          historyLineData.push(currentLineData)
          //定义当前行命令在整个数组中的位置
          last = historyLineData.length - 1
        }
        //当输入clear时清空终端内容
        if (currentLineData === 'clear') {
          term.clear()
        }

        //在这可以进行发起请求将整行数据传入
        runJob(currentLineData)
        // 清空当前行数据
        currentLineData = ''

      }  else if (key.charCodeAt(0) === 127) {
        //删除键--》当前行偏移量x大于终端提示符所占位置时进行删除
        // console.log(term._core.buffer.x,"term._core.buffer.x")
        //这里我设置的是17个占位,你可以根据自己的需求酌情增减
        if (term._core.buffer.x >17) {
          currentLineData = currentLineData.slice(0, -1)
          term.write('\b \b')
        }
      } else if (key === '\u001b[A') {
        //up键的时候
        let len = 0
        if (historyLineData.length > 0) {
          len = historyLineData.length + 1
        }

        if (last < len && last > 0) {
          //当前行有数据的时候进行删除掉在进行渲染上存储的历史数据
          for (let i = 0; i < currentLineData.length; i++) {
            if (term._core.buffer.x > terminalTitleTemplate.length + 1) {
              term.write('\b \b')
            }
          }
          let text = historyLineData[last - 1]
          term.write(text)
          //重点,一定要记住存储当前行命令保证下次up或down时不会光标错乱覆盖终端提示符
          currentLineData = text

          last--
        }
      } else if (key === '\u001b[B') {
        //down键
        let lent = 0
        if (historyLineData.length > 0) {
          lent = historyLineData.length - 1
        }
        if (last < lent && last > -1) {
          for (let i = 0; i < currentLineData.length; i++) {
            if (term._core.buffer.x > terminalTitleTemplate.length + 1) {
              term.write('\b \b')
            }
          }
          let text = historyLineData[last + 1]
          term.write(text)
          currentLineData = text
          last++
        }
      } else {
        //啥也不做的时候就直接输入
        currentLineData += key
        term.write(key)
      }
    })
  }
  useEffect(() => {
    keyAction()
    // 打开一个已经初始化好的的终端
    // term.open(terminalRef.current);
    // 向终端中写入数据
    term.open(terminalRef.current);
   
    if (term._initialized) {
      return
    }
    term._initialized = true
    if (count == 0) {
        //这里是需求是默认加载一段后端返回的信息
      dispatch({
        type: 'task/queryTask',
        callback: (res) => {
          res.data.taskList.forEach(t => {
            term.writeln(t)
          });
          term.write(terminalTitleTemplate)
        }
      })
    }
    //光标聚焦
    term.focus();
    term.loadAddon(fitPlugin);
    fitPlugin.fit();
    //自适应窗口
    window.onresize = () => fitPlugin.fit()
    count++

    return () => {
      term.dispose()
    }
  }, [count])

  

  const runJob = (param) => {
    {
      param &&
        dispatch({
          type: 'task/terminalOperation',
          payload: { param: param },
          callback: (res) => {
            console.log(res.data, "res") 
            term.write(`\r\n`)
            if(res.data.taskListStr!=""){
              res.data.taskListStr.forEach(t => {
                term.writeln(t)
              });
            }else{
              term.writeln(res.data.errorData)
            }
            // 添加输出(为空也要输出换行)
            term.write(`\r\n${terminalTitleTemplate} `)
            term.focus();
          }
        })
    }
  }
  return (
    <div className="terminal-container">
//样式可以自行调整
      <div style={
    
    {height:'100%',overflow:'scroll'}} ref={terminalRef}></div>
    </div>
  )
}

export default memo(Xterminal)

猜你喜欢

转载自blog.csdn.net/weixin_38791717/article/details/128706361