当前位置:首页 > 编程笔记 > 正文
已解决

vue网页使用远程终端加终端多开

来自网友在路上 11248124提问 提问时间:2023-11-12 22:39:18阅读次数: 124

最佳答案 问答题库1248位专家为你答疑解惑

       只会前端所以只有前端代码,想看ws服务怎么弄的就不用看了~

        这边终端用到的前端插件是xterm。首先需要三个插件,都是xterm的依赖。

        单独一个终端:使用xterm,创建一个标签后,将标签给予xterm作为终端标签。然后写入终端所需如光标、行数、样式。根据是否keep-alive缓存决定在created和mounted里面将带属性的xterm给予标签(initTerminal)链接ws服务(websocket)(在vue里面分别写成两个方法,括号内为我起的方法名)。

        多开终端:xterm获取标签时要根据id,那根据打开终端的个数动态创建标签并赋予id(比如用字母和循环的index数字组合)。然后将initTerminalwebsocket改为传参的方法,这样就可以确定要创建的标签和要链接的ws服务是哪个标签了。然后在添加删除终端的地方做好ws服务的断开就可以了,写一个数组存放服务,退出哪个根据index做好xterm带的close()方法。【所有方法要对应好标签名,根据参数传递的方法判断要执行的操作是哪一个终端的】

        多开除了麻烦一点和开一个是一摸一样的,就是做好服务关闭。否则服务多了后台受不了。

1、xterm

npm install xterm

2、xterm-addon-fit

npm install xterm-addon-fit

3、xterm-addon-attac

npm install xterm-addon-attach

还用到了Elementui,这个就不教怎么安装了一搜就有

直接粘贴代码,里面每一句都注释了,先看懂了再根据需求改吧。

<template><div class="app-container"><!-- elementui的tabs标签,用于控制终端数量 --><el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit"><el-tab-pane:key="item.name"v-for="item in editableTabs":label="item.title":name="item.name"><!-- 用tab的标签名称命名 --><div :id='item.title' class="terminal" style="width: 100%"></div></el-tab-pane></el-tabs></div>
</template><script>
//引入依赖
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { AttachAddon } from 'xterm-addon-attach'let wsTime = null//终端出错用于储存展示出错信息。
export default {name: 'Terminal',data() {return {editableTabsValue: '0',//标签名称累加editableTabs: [{title: 'terminal_0',name: '0',}],//标签数组tabIndex: 0,//标签目前到哪一位term:[],//用于保存每一个终端配置//下面两个属性是我这边业务要链接ws的参数,根据你们业务改成自己的containerId:'',//容器id  containerIp:'',//容器ipws:[],// 保存websocket链接,用于统一关闭isConnected: false,//判断链接状态heartbeatInterval: null, // 保存心跳定时器的IDheartbeatTimeout: 1000, // 心跳间隔时间}},created(){// 默认初始化第一个终端this.initTerminal(0)},mounted() {//下面两个属性是我这边业务要链接ws的参数,根据你们业务改成自己的this.containerId = this.$route.query.containerIdthis.containerIp = this.$route.query.containerIp//业务不需要可以不用这个判断if(this.containerId !== ''){// 建立websocket建立第一个终端的连接// 我这边用了keep-alive所以可以保证mounted只执行一次,只要调用mounted就是第一次进页面所以可以写死this.websocket(0)}},//keep-alive缓存用到了,建议加个keep-alive//已开启一个容器的终端,但是没关闭又开了另一个容器的终端activated(){//这个属性还是根据你们业务改成自己的this.containerIp = this.$route.query.containerIp//这边业务是判断开启不同地址的终端还是同一个地址多开一个终端if(this.$route.query.containerId !== this.containerId){// 换成新的id建立websocket连接this.containerId = this.$route.query.containerId//tab增加方法this.handleTabsEdit(null, 'add')}},//销毁组件回调beforeDestroy() {//map循环保存ws服务的数组,循环一圈关闭wsthis.ws.map((item,index) =>{return this.ws[index].close()})},methods:{//elementui的tabs标签,去搜一下不难,就一个增加一个删除方法//添加和删除终端标签方法handleTabsEdit(targetName, action) {//增加tab标签if (action === 'add') {//动态根据数量命名idlet newTabName = ++this.tabIndex + '';//将新加的标签名加入标签名列表this.editableTabs.push({title: `terminal_${newTabName}`,name: newTabName,});//用于计数,保证下次增加是新的index并且加1了this.editableTabsValue = newTabName;//创建新的终端this.initTerminal(newTabName)//链接新的ws服务this.websocket(newTabName)}//删除tabs标签if (action === 'remove') {//获取所有标签列表let tabs = this.editableTabs;//获取要删除的是哪个tablet activeName = this.editableTabsValue;//关闭ws服务this.ws[targetName].close()// 如果删除的是当前正在使用的终端if (activeName === targetName) {tabs.forEach((tab, index) => {//精确判断删除的是哪个if (tab.name === targetName) {  //下面就是删除完正在使用的终端,如果还有其他终端,就跳过去。不让页面空着let nextTab = tabs[index + 1] || tabs[index - 1];if (nextTab) {activeName = nextTab.name;}}});}//计数index到几了this.editableTabsValue = activeName;//在保存标签数组中删除掉刚刚删除的标签this.editableTabs = tabs.filter(tab => tab.name !== targetName);}},// 初始化终端配置,index表示第几位置的initTerminal(index){this.term[index] = new Terminal({rendererType: "canvas", //渲染类型rows: 35, //行数,影响最小高度// 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'//字体});},// 自定义终端默认展示内容// 里面有属性是关于业务的可以删掉,如 ${this.$route.query.name},${this.containerId}writeDefaultInfo(index){let defaultInfo = ['',`\x1b[1;34m containerName:\x1b[1;31m ${this.$route.query.name}\x1b[0m\x1b[0m \x1b[1;34m containerId:\x1b[1;31m ${this.containerId}\x1b[0m\x1b[0m`,'┌\x1b[1m terminals \x1b[0m─────────────────────────────────────────────────────────────────┐ ','│                                                                            │ ',`│  \x1b[1;34m welcome Container Terminal\x1b[0m                                               │ `,'│                                                                            │ ',`└────────────────────────────────────────────────────────────────────────────┘\n `]//将默认展示内容写入终端this.term[index].write(defaultInfo.join('\n\r'))},// 建立websocket连接websocket(index) {// WebSocket startif ('WebSocket' in window) {//根据后端需要的参数业务改const url = 'ws://' + `${window.PLATFROM_CONFIG.filePath}:${window.PLATFROM_CONFIG.port}/ws/webdocker/`.split('//')[1] + `${this.containerIp}`//存一下新的ws服务const ws = new WebSocket(url)//将新的服务加入ws服务数组列表this.ws[index] = ws//链接成功回调ws.onopen = (event) => {//打印一下链接成功console.log('已建立连接:',event)//改变链接状态this.isConnected = true//输入换行符可让终端显示当前用户的工作路径,我这边顺便进入默认目录ws.send('cd C:/app\r\n')//窗口自适应插件const fitAddon = new FitAddon();//websocket自动收发消息插件const attachAddon = new AttachAddon(ws)//给新的这个ws服务使用依赖初始化this.term[index].loadAddon(attachAddon)//给新的这个ws服务使用依赖设置样式this.term[index].loadAddon(fitAddon)//将新的终端给予标签this.term[index].open(document.getElementById(`terminal_${index}`));// 聚焦闪烁光标this.term[index].focus()//先默认一次适应大小fitAddon.fit()// 窗口尺寸变化时,终端尺寸自适应window.onresize = () => {fitAddon.fit()}// 自定义终端默认展示内容this.writeDefaultInfo(index)};//发消息的回调,一般用不到ws.onmessage = (event) => {};//报错的回调ws.onerror = (event) => {console.log('错误信息:', event)//之前定义的wsTime全局属性用到了if (wsTime) {window.clearTimeout(wsTime)wsTime = null}//展示完清空一下,等待下次出错展示新的wsTime = window.setTimeout(() => {this.websocket()}, 3000)};//关闭链接回调ws.onclose = (event) => {console.log('已关闭连接:', event)};} else {//不多说了,换个浏览器提示console.log('浏览器不支持 WebSocket..')}}}
}
</script>
<style scoped>
/* 整体样式,自己删掉背景图吧动动手多改改就全会了哈哈 */
.app-container {position: absolute;width: 100%;height: 100%;min-height: 550px;padding: 0;background: url("../../assets/images/backImg-1.png") 50%;
}
/* tabs样式 */
::v-deep .el-tabs__header{margin: 0;
}
/* 终端大小占100% */
::v-deep .el-tabs{height: 100%;background-color: #2d2e2c;
}
/* tabs内容 */
::v-deep .el-tabs__content{height: 100%;width: 100%;
}
/* tabs忘了.. */
::v-deep .el-tab-pane{height: 100%;
}
/* tab的title颜色 */
::v-deep .el-tabs__item{color: white;height: 30px;line-height: 30px;padding: 0 5px;
}
/* 添加tab按钮 */
::v-deep .el-tabs__new-tab{margin: 12px 12px 0 10px;
}
/* tab选中时 */
::v-deep .el-tabs__item.is-active{color: #1890ff;background-color: #2b2927;border-bottom: none;
}
/* xterm默认样式改变 */
::v-deep .xterm .xterm-viewport{overflow-y: auto;
}
/* xterm默认样式改变 */
::v-deep .xterm .xterm-viewport::-webkit-scrollbar{width: 10px;
}
/* xterm默认样式改变 */
::v-deep .xterm .xterm-viewport::-webkit-scrollbar-thumb{border-radius: 10px;background: rgba(62,68,107);
}
/* xterm默认样式改变 */
::v-deep .xterm .xterm-viewport::-webkit-scrollbar-track{border-radius: 0;background: rgba(0,0,0,0.1);
}
</style>

  除了css里面几乎每句都有注释,复制完慢慢改。

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"vue网页使用远程终端加终端多开":http://eshow365.cn/6-38475-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!