2024-04-06 04:03:38 +00:00
|
|
|
|
<template>
|
2024-04-12 14:39:08 +00:00
|
|
|
|
<div>
|
|
|
|
|
|
<div ref="viewportRef" class="viewport" :style="{ width: state.size.width + 'px', height: state.size.height + 'px' }">
|
|
|
|
|
|
<div ref="displayRef" class="display" tabindex="0" />
|
|
|
|
|
|
<div class="btn-box">
|
|
|
|
|
|
<SvgIcon name="DocumentCopy" @click="openPaste" :size="20" class="pointer-icon mr10" title="剪贴板" />
|
|
|
|
|
|
<SvgIcon name="FolderOpened" @click="openFilesystem" :size="20" class="pointer-icon mr10" title="文件管理" />
|
|
|
|
|
|
<SvgIcon name="FullScreen" @click="state.fullscreen ? closeFullScreen() : openFullScreen()" :size="20" class="pointer-icon mr10" title="全屏" />
|
2024-04-06 04:03:38 +00:00
|
|
|
|
|
2024-04-12 14:39:08 +00:00
|
|
|
|
<el-dropdown>
|
|
|
|
|
|
<SvgIcon name="Monitor" :size="20" class="pointer-icon mr10" title="发送快捷键" style="color: #fff" />
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65535'])"> Ctrl + Alt + Delete </el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65507', '65513', '65288'])"> Ctrl + Alt + Backspace </el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65515', '100'])"> Windows + D </el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65515', '101'])"> Windows + E </el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65515', '114'])"> Windows + R </el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item @click="openSendKeyboard(['65515'])"> Windows </el-dropdown-item>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
2024-04-12 07:53:42 +00:00
|
|
|
|
|
2024-04-12 14:39:08 +00:00
|
|
|
|
<SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-if="!state.fullscreen"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
:title="state.filesystemDialog.title"
|
|
|
|
|
|
v-model="state.filesystemDialog.visible"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
width="70%"
|
|
|
|
|
|
>
|
|
|
|
|
|
<machine-file
|
|
|
|
|
|
:machine-id="state.filesystemDialog.machineId"
|
|
|
|
|
|
:auth-cert-name="state.filesystemDialog.authCertName"
|
|
|
|
|
|
:protocol="state.filesystemDialog.protocol"
|
|
|
|
|
|
:file-id="state.filesystemDialog.fileId"
|
|
|
|
|
|
:path="state.filesystemDialog.path"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-dialog>
|
2024-04-06 04:03:38 +00:00
|
|
|
|
</div>
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-if="!state.fullscreen"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
:title="state.filesystemDialog.title"
|
|
|
|
|
|
v-model="state.filesystemDialog.visible"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
width="70%"
|
|
|
|
|
|
>
|
2024-04-06 04:03:38 +00:00
|
|
|
|
<machine-file
|
|
|
|
|
|
:machine-id="state.filesystemDialog.machineId"
|
2024-04-12 07:53:42 +00:00
|
|
|
|
:auth-cert-name="state.filesystemDialog.authCertName"
|
2024-04-06 04:03:38 +00:00
|
|
|
|
:protocol="state.filesystemDialog.protocol"
|
|
|
|
|
|
:file-id="state.filesystemDialog.fileId"
|
|
|
|
|
|
:path="state.filesystemDialog.path"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
|
import Guacamole from './guac/guacamole-common';
|
|
|
|
|
|
import { getMachineRdpSocketUrl } from '@/views/ops/machine/api';
|
|
|
|
|
|
import clipboard from './guac/clipboard';
|
|
|
|
|
|
import { reactive, ref } from 'vue';
|
|
|
|
|
|
import { TerminalStatus } from '@/components/terminal/common';
|
|
|
|
|
|
import ClipboardDialog from '@/components/terminal-rdp/guac/ClipboardDialog.vue';
|
|
|
|
|
|
import { TerminalExpose } from '@/components/terminal-rdp/index';
|
|
|
|
|
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
|
|
|
|
|
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
|
|
|
|
|
import { exitFullscreen, launchIntoFullscreen, unWatchFullscreenChange, watchFullscreenChange } from '@/components/terminal-rdp/guac/screen';
|
2024-04-12 07:53:42 +00:00
|
|
|
|
import { useEventListener } from '@vueuse/core';
|
|
|
|
|
|
import { debounce } from 'lodash';
|
|
|
|
|
|
import { ClientState, TunnelState } from '@/components/terminal-rdp/guac/states';
|
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
2024-04-19 11:27:29 +00:00
|
|
|
|
import { joinClientParams } from '@/common/request';
|
2024-04-06 04:03:38 +00:00
|
|
|
|
|
|
|
|
|
|
const viewportRef = ref({} as any);
|
|
|
|
|
|
const displayRef = ref({} as any);
|
|
|
|
|
|
const clipboardRef = ref({} as any);
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
machineId: {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
type: Number,
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
authCert: {
|
|
|
|
|
|
type: String,
|
2024-04-06 04:03:38 +00:00
|
|
|
|
required: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
clipboardList: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => [],
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['statusChange']);
|
|
|
|
|
|
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
client: null as any,
|
|
|
|
|
|
display: null as any,
|
|
|
|
|
|
displayElm: {} as any,
|
|
|
|
|
|
clipboard: {} as any,
|
|
|
|
|
|
keyboard: {} as any,
|
|
|
|
|
|
mouse: null as any,
|
|
|
|
|
|
touchpad: null as any,
|
|
|
|
|
|
errorMessage: '',
|
|
|
|
|
|
arguments: {},
|
|
|
|
|
|
status: TerminalStatus.NoConnected,
|
|
|
|
|
|
size: {
|
|
|
|
|
|
height: 710,
|
|
|
|
|
|
width: 1024,
|
|
|
|
|
|
force: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
enableClipboard: true,
|
|
|
|
|
|
clipboardDialog: {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
filesystemDialog: {
|
|
|
|
|
|
visible: false,
|
2024-04-12 07:53:42 +00:00
|
|
|
|
authCertName: '',
|
2024-04-06 04:03:38 +00:00
|
|
|
|
machineId: 0,
|
|
|
|
|
|
protocol: 1,
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
fileId: 0,
|
|
|
|
|
|
path: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
fullscreen: false,
|
|
|
|
|
|
beforeFullSize: {
|
|
|
|
|
|
height: 710,
|
|
|
|
|
|
width: 1024,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const installKeyboard = () => {
|
|
|
|
|
|
state.keyboard = new Guacamole.Keyboard(state.displayElm);
|
|
|
|
|
|
uninstallKeyboard();
|
|
|
|
|
|
state.keyboard.onkeydown = (keysym: any) => {
|
|
|
|
|
|
state.client.sendKeyEvent(1, keysym);
|
|
|
|
|
|
};
|
|
|
|
|
|
state.keyboard.onkeyup = (keysym: any) => {
|
|
|
|
|
|
state.client.sendKeyEvent(0, keysym);
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
const uninstallKeyboard = () => {
|
|
|
|
|
|
state.keyboard!.onkeydown = state.keyboard!.onkeyup = () => {};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const installMouse = () => {
|
|
|
|
|
|
state.mouse = new Guacamole.Mouse(state.displayElm);
|
|
|
|
|
|
// Hide software cursor when mouse leaves display
|
|
|
|
|
|
state.mouse.onmouseout = () => {
|
|
|
|
|
|
if (!state.display) return;
|
|
|
|
|
|
state.display.showCursor(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
state.mouse.onmousedown = state.mouse.onmouseup = state.mouse.onmousemove = handleMouseState;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const installTouchpad = () => {
|
|
|
|
|
|
state.touchpad = new Guacamole.Mouse.Touchpad(state.displayElm);
|
|
|
|
|
|
|
|
|
|
|
|
state.touchpad.onmousedown =
|
|
|
|
|
|
state.touchpad.onmouseup =
|
|
|
|
|
|
state.touchpad.onmousemove =
|
|
|
|
|
|
(st: any) => {
|
|
|
|
|
|
// 记录按下时,光标所在位置
|
|
|
|
|
|
console.log(st);
|
|
|
|
|
|
handleMouseState(st, true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 记录单指按压时候手在屏幕的位置
|
|
|
|
|
|
state.displayElm.ontouchend = (event: TouchEvent) => {
|
|
|
|
|
|
console.log('end', event);
|
|
|
|
|
|
state.displayElm.ontouchend = () => {};
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const setClipboard = (data: string) => {
|
|
|
|
|
|
clipboardRef.value.setValue(data);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const installClipboard = () => {
|
|
|
|
|
|
state.enableClipboard = clipboard.install(state.client) as any;
|
|
|
|
|
|
clipboard.installWatcher(props.clipboardList, setClipboard);
|
|
|
|
|
|
state.client.onclipboard = clipboard.onClipboard;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-12 07:53:42 +00:00
|
|
|
|
const installResize = () => {
|
|
|
|
|
|
// 在resize事件结束后300毫秒执行
|
|
|
|
|
|
useEventListener('resize', debounce(resize, 300));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const installDisplay = () => {
|
|
|
|
|
|
let { width, height, force } = state.size;
|
|
|
|
|
|
state.display = state.client.getDisplay();
|
|
|
|
|
|
const displayElm = displayRef.value;
|
|
|
|
|
|
displayElm.appendChild(state.display.getElement());
|
|
|
|
|
|
displayElm.addEventListener('contextmenu', (e: any) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
if (e.preventDefault) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
e.returnValue = false;
|
|
|
|
|
|
});
|
2024-04-19 11:27:29 +00:00
|
|
|
|
state.client.connect('width=' + width + '&height=' + height + '&force=' + force + '&' + joinClientParams());
|
2024-04-06 04:03:38 +00:00
|
|
|
|
window.onunload = () => state.client.disconnect();
|
|
|
|
|
|
|
|
|
|
|
|
// allows focusing on the display div so that keyboard doesn't always go to session
|
|
|
|
|
|
displayElm.onclick = () => {
|
|
|
|
|
|
displayElm.focus();
|
|
|
|
|
|
};
|
|
|
|
|
|
displayElm.onfocus = () => {
|
|
|
|
|
|
displayElm.className = 'focus';
|
|
|
|
|
|
};
|
|
|
|
|
|
displayElm.onblur = () => {
|
|
|
|
|
|
displayElm.className = '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
state.displayElm = displayElm;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const installClient = () => {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
let tunnel = new Guacamole.WebSocketTunnel(getMachineRdpSocketUrl(props.authCert)) as any;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
if (state.client) {
|
|
|
|
|
|
state.display?.scale(0);
|
|
|
|
|
|
uninstallKeyboard();
|
|
|
|
|
|
state.client.disconnect();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
state.client = new Guacamole.Client(tunnel);
|
|
|
|
|
|
|
|
|
|
|
|
tunnel.onerror = (status: any) => {
|
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
|
console.error(`Tunnel failed ${JSON.stringify(status)}`);
|
|
|
|
|
|
// state.connectionState = states.TUNNEL_ERROR;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
tunnel.onstatechange = (st: any) => {
|
|
|
|
|
|
console.log('statechange', st);
|
|
|
|
|
|
state.status = st;
|
|
|
|
|
|
switch (st) {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case TunnelState.CONNECTING: // 'CONNECTING'
|
2024-04-06 04:03:38 +00:00
|
|
|
|
break;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case TunnelState.OPEN: // 'OPEN'
|
|
|
|
|
|
state.status = TerminalStatus.Connected;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
emit('statusChange', TerminalStatus.Connected);
|
|
|
|
|
|
break;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case TunnelState.CLOSED: // 'CLOSED'
|
|
|
|
|
|
state.status = TerminalStatus.Disconnected;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
emit('statusChange', TerminalStatus.Disconnected);
|
|
|
|
|
|
break;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case TunnelState.UNSTABLE: // 'UNSTABLE'
|
|
|
|
|
|
state.status = TerminalStatus.Error;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
emit('statusChange', TerminalStatus.Error);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
state.client.onstatechange = (clientState: any) => {
|
|
|
|
|
|
console.log('clientState', clientState);
|
|
|
|
|
|
switch (clientState) {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case ClientState.IDLE:
|
|
|
|
|
|
console.log('连接空闲');
|
2024-04-06 04:03:38 +00:00
|
|
|
|
break;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case ClientState.CONNECTING:
|
2024-04-06 04:03:38 +00:00
|
|
|
|
console.log('连接中...');
|
|
|
|
|
|
break;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case ClientState.WAITING:
|
|
|
|
|
|
console.log('等待服务器响应...');
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ClientState.CONNECTED:
|
2024-04-06 04:03:38 +00:00
|
|
|
|
console.log('连接成功...');
|
2024-04-12 07:53:42 +00:00
|
|
|
|
break;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
2024-04-12 07:53:42 +00:00
|
|
|
|
case ClientState.DISCONNECTING:
|
|
|
|
|
|
console.log('断开连接中...');
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ClientState.DISCONNECTED:
|
|
|
|
|
|
console.log('已断开连接...');
|
2024-04-06 04:03:38 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
state.client.onerror = (error: any) => {
|
|
|
|
|
|
state.client.disconnect();
|
|
|
|
|
|
console.error(`Client error ${JSON.stringify(error)}`);
|
|
|
|
|
|
state.errorMessage = error.message;
|
|
|
|
|
|
// state.connectionState = states.CLIENT_ERROR;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
state.client.onsync = () => {};
|
|
|
|
|
|
|
|
|
|
|
|
state.client.onargv = (stream: any, mimetype: any, name: any) => {
|
|
|
|
|
|
if (mimetype !== 'text/plain') return;
|
|
|
|
|
|
|
|
|
|
|
|
const reader = new Guacamole.StringReader(stream);
|
|
|
|
|
|
|
|
|
|
|
|
// Assemble received data into a single string
|
|
|
|
|
|
let value = '';
|
|
|
|
|
|
reader.ontext = (text: any) => {
|
|
|
|
|
|
value += text;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Test mutability once stream is finished, storing the current value for the argument only if it is mutable
|
|
|
|
|
|
reader.onend = () => {
|
|
|
|
|
|
const stream = state.client.createArgumentValueStream('text/plain', name);
|
|
|
|
|
|
stream.onack = (status: any) => {
|
|
|
|
|
|
if (status.isError()) {
|
|
|
|
|
|
// ignore reject
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
state.arguments[name] = value;
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const resize = () => {
|
|
|
|
|
|
const elm = viewportRef.value;
|
|
|
|
|
|
if (!elm || !elm.offsetWidth) {
|
|
|
|
|
|
// resize is being called on the hidden window
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let box = elm.parentElement;
|
|
|
|
|
|
|
2024-04-12 07:53:42 +00:00
|
|
|
|
state.size.width = box.clientWidth;
|
|
|
|
|
|
state.size.height = box.clientHeight;
|
|
|
|
|
|
|
|
|
|
|
|
const width = parseInt(String(box.clientWidth));
|
|
|
|
|
|
const height = parseInt(String(box.clientHeight));
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
if (state.display.getWidth() !== width || state.display.getHeight() !== height) {
|
2024-04-12 07:53:42 +00:00
|
|
|
|
if (state.status !== TerminalStatus.Connected) {
|
|
|
|
|
|
connect(width, height);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
state.client.sendSize(width, height);
|
|
|
|
|
|
}
|
2024-04-06 04:03:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
// setting timeout so display has time to get the correct size
|
2024-04-12 07:53:42 +00:00
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
|
// const scale = Math.min(box.clientWidth / Math.max(state.display.getWidth(), 1), box.clientHeight / Math.max(state.display.getHeight(), 1));
|
|
|
|
|
|
// state.display.scale(scale);
|
|
|
|
|
|
// console.log(state.size, scale);
|
|
|
|
|
|
// }, 100);
|
2024-04-06 04:03:38 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleMouseState = (mouseState: any, showCursor = false) => {
|
|
|
|
|
|
state.client.getDisplay().showCursor(showCursor);
|
|
|
|
|
|
|
|
|
|
|
|
const scaledMouseState = Object.assign({}, mouseState, {
|
|
|
|
|
|
x: mouseState.x / state.display.getScale(),
|
|
|
|
|
|
y: mouseState.y / state.display.getScale(),
|
|
|
|
|
|
});
|
|
|
|
|
|
state.client.sendMouseState(scaledMouseState);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const connect = (width: number, height: number, force = false) => {
|
|
|
|
|
|
if (!width && !height) {
|
|
|
|
|
|
if (state.size && state.size.width && state.size.height) {
|
|
|
|
|
|
width = state.size.width;
|
|
|
|
|
|
height = state.size.height;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 获取当前viewportRef宽高
|
|
|
|
|
|
width = viewportRef.value.clientWidth;
|
|
|
|
|
|
height = viewportRef.value.clientHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
state.size = { width, height, force };
|
|
|
|
|
|
|
|
|
|
|
|
installClient();
|
|
|
|
|
|
installDisplay();
|
|
|
|
|
|
installKeyboard();
|
|
|
|
|
|
installMouse();
|
|
|
|
|
|
installTouchpad();
|
|
|
|
|
|
installClipboard();
|
2024-04-12 07:53:42 +00:00
|
|
|
|
installResize();
|
2024-04-06 04:03:38 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const disconnect = () => {
|
|
|
|
|
|
uninstallKeyboard();
|
|
|
|
|
|
state.client?.disconnect();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const blur = () => {
|
|
|
|
|
|
uninstallKeyboard();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const focus = () => {};
|
|
|
|
|
|
|
|
|
|
|
|
const openPaste = async () => {
|
|
|
|
|
|
state.clipboardDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closePaste = async () => {
|
|
|
|
|
|
installKeyboard();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onsubmitClipboard = (val: string) => {
|
|
|
|
|
|
state.clipboardDialog.visible = false;
|
|
|
|
|
|
installKeyboard();
|
|
|
|
|
|
clipboard.sendRemoteClipboard(state.client, val);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openFilesystem = async () => {
|
|
|
|
|
|
state.filesystemDialog.protocol = 2;
|
|
|
|
|
|
state.filesystemDialog.machineId = props.machineId;
|
2024-04-12 07:53:42 +00:00
|
|
|
|
state.filesystemDialog.authCertName = props.authCert;
|
2024-04-06 04:03:38 +00:00
|
|
|
|
state.filesystemDialog.fileId = props.machineId;
|
|
|
|
|
|
state.filesystemDialog.path = '/';
|
|
|
|
|
|
state.filesystemDialog.title = `远程桌面文件管理`;
|
|
|
|
|
|
state.filesystemDialog.visible = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openFullScreen = function () {
|
|
|
|
|
|
launchIntoFullscreen(viewportRef.value);
|
|
|
|
|
|
state.fullscreen = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 记录原始尺寸
|
|
|
|
|
|
state.beforeFullSize = {
|
|
|
|
|
|
width: state.size.width,
|
|
|
|
|
|
height: state.size.height,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新的宽高重新连接
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
connect(viewportRef.value.clientWidth, viewportRef.value.clientHeight, false);
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
watchFullscreenChange(watchFullscreen);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function watchFullscreen(event: Event, isFull: boolean) {
|
|
|
|
|
|
if (!isFull) {
|
|
|
|
|
|
closeFullScreen();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const closeFullScreen = function () {
|
|
|
|
|
|
exitFullscreen();
|
|
|
|
|
|
|
|
|
|
|
|
state.fullscreen = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新的宽高重新连接
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
connect(state.beforeFullSize.width, state.beforeFullSize.height, false);
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
// 取消注册esc事件,退出全屏
|
|
|
|
|
|
unWatchFullscreenChange(watchFullscreen);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-12 07:53:42 +00:00
|
|
|
|
const openSendKeyboard = (keys: string[]) => {
|
|
|
|
|
|
if (!state.client) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
|
|
state.client.sendKeyEvent(1, keys[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (let j = 0; j < keys.length; j++) {
|
|
|
|
|
|
state.client.sendKeyEvent(0, keys[j]);
|
|
|
|
|
|
}
|
|
|
|
|
|
ElMessage.success('发送组合键成功');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-04-06 04:03:38 +00:00
|
|
|
|
const exposes = {
|
|
|
|
|
|
connect,
|
|
|
|
|
|
disconnect,
|
|
|
|
|
|
init: connect,
|
|
|
|
|
|
close: disconnect,
|
|
|
|
|
|
fitTerminal: resize,
|
|
|
|
|
|
focus,
|
|
|
|
|
|
blur,
|
|
|
|
|
|
setRemoteClipboard: onsubmitClipboard,
|
|
|
|
|
|
} as TerminalExpose;
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose(exposes);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
.viewport {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 1024px;
|
|
|
|
|
|
min-height: 710px;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.display {
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-box {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 20px;
|
|
|
|
|
|
right: 30px;
|
|
|
|
|
|
padding: 5px 0 5px 10px;
|
|
|
|
|
|
background: #dddddd4a;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|