diff --git a/ssh-server/src/main/resources/static/js/index.js b/ssh-server/src/main/resources/static/js/index.js index 21d1883ecf6fa03367b28dff1f04827637e18484..06796f9685abd2915d4cf5ab8fb09e38e9e81e04 100644 --- a/ssh-server/src/main/resources/static/js/index.js +++ b/ssh-server/src/main/resources/static/js/index.js @@ -55,8 +55,8 @@ $(function() { protocol = 'wss://' } - // this._socket = new WebSocket(protocol + window.location.host + '/ssh') - this._socket = new WebSocket(protocol + window.location.hostname + ':8080/ssh') + this._socket = new WebSocket(protocol + window.location.host + '/ssh') + // this._socket = new WebSocket(protocol + window.location.hostname + ':8000/ssh') return this._socket }, diff --git a/ssh-vue/src/api/compile.js b/ssh-vue/src/api/compile.js new file mode 100644 index 0000000000000000000000000000000000000000..9c2a32f5b798595913b949c6af2702acdc8784d1 --- /dev/null +++ b/ssh-vue/src/api/compile.js @@ -0,0 +1,28 @@ +const SETTING_ITEM_KEY = 'compile_setting' + +/** + * 获取撰写窗口设置 + * @returns {{mode: string, visible: boolean}|any} + */ +export function getSettings() { + const setting = localStorage.getItem(SETTING_ITEM_KEY) + if (!setting) { + return { + visible: false, + mode: 'single' + } + } + + return JSON.parse(setting) +} + +/** + * 设置撰写窗口 + * @param key + * @param value + */ +export function setSettings(key, value) { + const setting = getSettings() + setting[key] = value + localStorage.setItem(SETTING_ITEM_KEY, JSON.stringify(setting)) +} \ No newline at end of file diff --git a/ssh-vue/src/components/compile/index.vue b/ssh-vue/src/components/compile/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..45dadb8975d5c9990303861b7c63aacbba9313ee --- /dev/null +++ b/ssh-vue/src/components/compile/index.vue @@ -0,0 +1,198 @@ + + + + + + \ No newline at end of file diff --git a/ssh-vue/src/components/xterm-terminal/index.vue b/ssh-vue/src/components/xterm-terminal/index.vue index e82ed3bb23ef57c31345f78823c4c9a23b6f2f21..ffab31c7de5b9ecea1d600bc74e86c6390552a8b 100644 --- a/ssh-vue/src/components/xterm-terminal/index.vue +++ b/ssh-vue/src/components/xterm-terminal/index.vue @@ -37,6 +37,13 @@ password: null } } + }, + + isActive: { + type: Function, + default: function (termKey) { + return true + } } }, @@ -94,7 +101,9 @@ theme: {} }, - socket: null + socket: null, + + uninstall: false } }, @@ -136,6 +145,22 @@ methods: { + /** + * 添加脚本命令 + * @param command 脚本命令 + * @param isExec 是否执行 + */ + addText(command, isExec) { + if (!this.term || !command || !this.socket) { + return + } + + this.socket.send({ + type: 'command', + message: command + (isExec ? '\r' : '') + }) + }, + /** * 根据容器宽度计算cols * @returns {number} @@ -181,21 +206,39 @@ this.socket.close() }, + /** + * 重置大小 + */ + resize() { + try { + this.fitAddon.fit() + + this.term.onResize(() => { + // TODO 告诉服务端列和行数被重新定义 + }) + } catch (e) { + } + }, + /** * 监听页面发生变化 */ litenerResize() { - const resizeScreen = debounce(() => { - try { - this.fitAddon.fit() - - this.term.onResize(() => { - // TODO 告诉服务端列和行数被重新定义 - }) - } catch (e) {} - }, 100) + /* + TODO + 记一个坑,后续找到办法再优化。过程如下: + 当这个组件被卸载之后,window.addEventListener没有被卸载,如果再卸载时调用 + window.removeEventListener可以移除,但是移除时函数的第二个参数为funciton,我又用到了 + 抖动函数,不知道咋写了。。尴尬。。。,目前就是定义一个uninstall变量,再使用时判断, + 大晚上的脑子处于短路状态,等想到解决办法再改把。 + */ + window.addEventListener('resize', debounce(() => { + if (this.uninstall || !this.isActive(this.connInfo._key)) { + return + } - window.addEventListener('resize', resizeScreen) + this.resize() + }, 100)) }, /** @@ -243,6 +286,13 @@ }).init() } + }, + + /** + * 组件销毁之前标记一下unisntall为true,表示已被卸载 + */ + beforeDestroy() { + this.uninstall = true } } diff --git a/ssh-vue/src/components/xterm-terminal/socket.js b/ssh-vue/src/components/xterm-terminal/socket.js index 7ffd2a92c9be6f5e1efb1c309cf319886379476f..44bb7b8fba4d53c728b265fec52a6bc825491e7a 100644 --- a/ssh-vue/src/components/xterm-terminal/socket.js +++ b/ssh-vue/src/components/xterm-terminal/socket.js @@ -86,6 +86,10 @@ export default function Socket(url, callbacks) { * @param data 要发送的消息,格式必须是JSON对象 */ this.send = function (data) { + if (!this.is()) { + return this + } + this.socket.send(JSON.stringify(data)) return this } diff --git a/ssh-vue/src/views/ssh/index.vue b/ssh-vue/src/views/ssh/index.vue index 5b4ba9af9bfad2eb1efd677c5200e47955a65830..72f68b28336ea3d076acb7039553170b2f91fc0d 100644 --- a/ssh-vue/src/views/ssh/index.vue +++ b/ssh-vue/src/views/ssh/index.vue @@ -7,9 +7,16 @@ @@ -40,6 +55,9 @@ import XTerminal from '@/components/xterm-terminal' import TabMenu from './tab-menu' + import Compile from '@/components/compile/index' + import {getSettings, setSettings} from "@/api/compile" + import {v1 as uuidv1} from 'uuid' export default { @@ -48,11 +66,15 @@ components: { TopTool, XTerminal, - TabMenu + TabMenu, + Compile }, data() { return { + /** + * 会话列表的tab对象 + */ terminalTabs: { active: 'tab-0', @@ -61,12 +83,42 @@ name: '新建会话', host: null, port: 22 - }] + }], + // tab组件中,height样式为calc(100% - ${height}px),其中${height}的height就是该字段值,默认为54 + height: 54 + + }, + + /** + * 撰写窗口组件对象 + */ + compile: { + visible: false } } }, + watch: { + /** + * 切换tab标签时,重置terminal大小,防止浏览器窗口大小改变时除当前外其他的tab标签内的terminal大小计算错误 + */ + 'terminalTabs.active'(name) { + const terminals = this.$refs.terminal + + // 如果首次创建新的连接,就不需要重置大小,因为默认就已经就算大小了 + if (terminals.length !== this.terminalTabs.tabs.length) { + return + } + + this.resizeTerminal(name) + } + }, + + mounted() { + this.compileCom().initSetting() + }, + methods: { /** @@ -131,6 +183,93 @@ */ renameTabName(name, index) { this.terminalTabs.tabs[index].name = name + }, + + /** + * 判断指定终端是否是激活状态 + * @param termKey terminal组件的key + * @return {boolean} + * - true:指定的终端是当前激活的 + * - false: 指定的终端不是当前激活的 + */ + termIsActive(termKey) { + const index = parseInt(this.terminalTabs.active.split('-')[1]) + const terminal = this.$refs.terminal[index] + return terminal.$props.connInfo._key === termKey + }, + + /** + * 重置指定tab中的终端尺寸 + * @param tabName tab中的name属性值 + */ + resizeTerminal(tabName) { + this.$nextTick(() => { + const index = parseInt(tabName.split('-')[1]) + this.$refs.terminal[index].resize() + }) + }, + + /** + * 设置tab的height,这里的height是calc(100% - ${height}px)中的${height} + */ + setTabHeight(height, isResize = true) { + if (typeof height !== 'number') { + return + } + + this.terminalTabs.height += height + + if (isResize) { + this.resizeTerminal(this.terminalTabs.active) + } + }, + + /** + * 撰写窗口组件的相关方法 + */ + compileCom() { + const _this = this + + return { + + /** + * 打开撰写窗口 + */ + open() { + _this.compile.visible = true + _this.$nextTick(() => { + _this.setTabHeight(_this.$refs.compileRef.getHeight()) + setSettings('visible', true) + }) + }, + + /** + * 关闭撰写窗口 + */ + close() { + _this.setTabHeight(0 - _this.$refs.compileRef.getHeight()) + _this.compile.visible = false + setSettings('visible', false) + }, + + /** + * 获取撰写窗口的打开状态 + */ + getStatus() { + return _this.compile.visible + }, + + /** + * 初始化撰写窗口的设置 + */ + initSetting() { + if (getSettings().visible) { + this.open() + } + } + + } + } }, @@ -142,6 +281,14 @@ */ tabsLen() { return this.terminalTabs.tabs.length + }, + + /** + * 当前tab页被激活的索引 + * @returns {number} + */ + tabActiveIndex() { + return parseInt(this.terminalTabs.active.split('-')[1]) } } } @@ -173,7 +320,7 @@ } /* 目的是让高度自适应以后容易计算出terminal的行数 */ - .el-tabs >>> .el-tabs__content { + .el-tabs > > > .el-tabs__content { height: calc(100% - 56px); .el-tab-pane { diff --git a/ssh-vue/src/views/ssh/top-tool.vue b/ssh-vue/src/views/ssh/top-tool.vue index 9a13efd3e0da2319e3987b4bc57c2a92a20cd73a..024611ce030ec4aa40faeed084eb4ad1d5a5c32f 100644 --- a/ssh-vue/src/views/ssh/top-tool.vue +++ b/ssh-vue/src/views/ssh/top-tool.vue @@ -11,6 +11,7 @@
会话列表 + 撰写窗口
+
@@ -34,9 +36,51 @@ components: {SessionList}, + props: { + + /** + * 终端列表 + */ + terminals: { + type: Array, + default: () => [] + }, + + /** + * 当前被激活的tab标签中name属性的值 + */ + tabActiveName: { + type: String, + default: () => null + }, + + /** + * 撰写窗口 + */ + compileCom: { + type: Object, + default: () => { + return { + // 打开 + open() {}, + + // 关闭 + close() {}, + + // 获取状态,打开还是关闭 + getStatus() {} + } + } + } + + }, + data() { return { + /** + * 会话列表对话框 + */ sessionList: { visible: false } @@ -46,13 +90,58 @@ methods: { + /** + * 打开会话列表对话框 + */ openSessionList() { this.sessionList.visible = true }, + /** + * 调用terminal连接指令 + * @param data + */ connect(data) { this.sessionList.visible = false this.$emit('connect', data) + }, + + /** + * 打开撰写窗口 + */ + openCompile() { + if (this.compileCom.getStatus()) { + this.compileCom.close() + } else { + this.compileCom.open() + } + } + + }, + + computed: { + + /** + * 当前tab的index + * @returns {number} + */ + index() { + if (this.tabActiveName) { + return parseInt(this.tabActiveName.split('-')[1]) + } + + return -1; + }, + + /** + * 当前tab内的terminal组件实例 + */ + terminal() { + if (this.index === -1) { + return null + } + + return this.terminals[this.index] } }