🔨 添加个性化配置.

This commit is contained in:
lijiahangmax
2025-07-07 14:42:54 +08:00
parent aed5d10eed
commit 4643c37a5a
12 changed files with 507 additions and 43 deletions

View File

@@ -89,7 +89,6 @@ public class GuacdTunnel implements IGuacdTunnel {
@Override
public void connect() throws GuacdException {
try {
// TODO 端口转发
this.socket = new ConfiguredGuacamoleSocket(new InetGuacamoleSocket(serverAddress, serverPort), serverConfig, clientConfig);
this.tunnel = new CustomGuacamoleTunnel(uuid, socket);
} catch (GuacamoleException e) {

View File

@@ -9,6 +9,8 @@
<terminal-ssh-action-bar-block />
<!-- RDP 工具栏 -->
<terminal-rdp-action-bar-block />
<!-- VNC 工具栏 -->
<terminal-vnc-action-bar-block />
<!-- SSH 右键菜单 -->
<terminal-ssh-right-menu-block />
</div>
@@ -26,6 +28,7 @@
import TerminalSshActionBarBlock from './terminal-ssh-action-bar-block.vue';
import TerminalSshRightMenuBlock from './terminal-ssh-right-menu-block.vue';
import TerminalRdpActionBarBlock from './terminal-rdp-action-bar-block.vue';
import TerminalVncActionBarBlock from './terminal-vnc-action-bar-block.vue';
</script>

View File

@@ -12,12 +12,6 @@
:model="formModel"
layout="vertical">
<a-space size="large">
<!-- 工具栏按钮 -->
<a-form-item field="actions" label="工具栏按钮">
<icon-actions class="form-item-actions"
:actions="actions"
position="bottom" />
</a-form-item>
<!-- 工具栏方向 -->
<a-form-item field="position" label="工具栏方向">
<a-select v-model="formModel.position"
@@ -25,6 +19,12 @@
placeholder="请选择工具栏方向"
:options="toOptions(graphActionBarPositionKey)" />
</a-form-item>
<!-- 工具栏按钮 -->
<a-form-item field="actions" label="工具栏按钮">
<icon-actions class="form-item-actions"
:actions="actions"
position="bottom" />
</a-form-item>
</a-space>
</a-form>
</div>

View File

@@ -0,0 +1,112 @@
<template>
<div class="terminal-setting-block">
<!-- 顶部 -->
<div class="terminal-setting-subtitle-wrapper">
<h3 class="terminal-setting-subtitle">
VNC 工具栏设置
</h3>
</div>
<!-- 内容区域 -->
<div class="terminal-setting-body block-body setting-body">
<a-form class="terminal-setting-form"
:model="formModel"
layout="vertical">
<a-space size="large">
<!-- 工具栏方向 -->
<a-form-item field="position" label="工具栏方向">
<a-select v-model="formModel.position"
style="width: 148px;"
placeholder="请选择工具栏方向"
:options="toOptions(graphActionBarPositionKey)" />
</a-form-item>
<!-- 工具栏按钮 -->
<a-form-item field="actions" label="工具栏按钮">
<icon-actions class="form-item-actions"
:actions="actions"
position="bottom" />
</a-form-item>
</a-space>
</a-form>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'terminalVncActionBarBlock'
};
</script>
<script lang="ts" setup>
import type { TerminalVncActionBarSetting } from '@/store/modules/terminal/types';
import type { SidebarAction } from '@/views/terminal/types/define';
import { computed, ref, watch } from 'vue';
import { useTerminalStore, useDictStore } from '@/store';
import { TerminalPreferenceItem } from '@/store/modules/terminal';
import { VncActionBarItems, graphActionBarPositionKey } from '@/views/terminal/types/const';
import IconActions from '../../layout/icon-actions.vue';
const { toOptions } = useDictStore();
const { preference, updateTerminalPreference } = useTerminalStore();
const formModel = ref<TerminalVncActionBarSetting>({ ...preference.vncActionBarSetting });
// 监听同步
watch(formModel, (v) => {
if (!v) {
return;
}
// 同步
updateTerminalPreference(TerminalPreferenceItem.VNC_ACTION_BAR_SETTING, formModel.value, true);
}, { deep: true });
// 操作项
const actions = computed<Array<SidebarAction>>(() => {
return VncActionBarItems.map(s => {
return {
icon: s.icon,
content: (formModel.value[s.item] === false ? '显示 ' : '隐藏 ') + s.content,
checked: formModel.value[s.item] !== false,
click: () => {
formModel.value[s.item] = formModel.value[s.item] === false;
}
};
});
});
</script>
<style lang="less" scoped>
.form-item-actions {
display: flex;
background-color: var(--color-fill-2);
padding: 4px;
border-radius: 4px;
:deep(.terminal-sidebar-icon-wrapper) {
width: 40px;
height: 40px;
}
:deep(.terminal-sidebar-icon) {
width: 32px;
height: 32px;
font-size: 20px;
}
}
.form-item-actions {
margin-right: 24px;
}
:deep(.arco-form) {
.arco-form-item-label {
user-select: none;
}
.arco-form-item {
margin-bottom: 0;
}
}
</style>

View File

@@ -14,7 +14,9 @@
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin class="full" :loading="loading">
<a-spin v-if="hostId"
class="full"
:loading="loading">
<a-tabs v-model:active-key="activeItem"
position="left"
type="rounded"
@@ -22,7 +24,7 @@
<!-- 标签配置 -->
<a-tab-pane :key="ExtraSettingItems.LABEL" title="标签配置">
<label-extra-form ref="labelForm"
:host-id="hostId as number"
:host-id="hostId"
:item="ExtraSettingItems.LABEL" />
</a-tab-pane>
<!-- SSH 配置 -->
@@ -30,7 +32,7 @@
:key="ExtraSettingItems.SSH"
title="SSH 配置">
<ssh-extra-form ref="sshForm"
:host-id="hostId as number"
:host-id="hostId"
:item="ExtraSettingItems.SSH" />
</a-tab-pane>
<!-- RDP 配置 -->
@@ -38,9 +40,17 @@
:key="ExtraSettingItems.RDP"
title="RDP 配置">
<rdp-extra-form ref="rdpForm"
:host-id="hostId as number"
:host-id="hostId"
:item="ExtraSettingItems.RDP" />
</a-tab-pane>
<!-- VNC 配置 -->
<a-tab-pane v-if="host?.types.includes(HostType.VNC.value)"
:key="ExtraSettingItems.VNC"
title="VNC 配置">
<vnc-extra-form ref="vncForm"
:host-id="hostId"
:item="ExtraSettingItems.VNC" />
</a-tab-pane>
</a-tabs>
</a-spin>
</a-modal>
@@ -64,6 +74,7 @@
import LabelExtraForm from './label-extra-form.vue';
import SshExtraForm from './ssh-extra-form.vue';
import RdpExtraForm from './rdp-extra-form.vue';
import VncExtraForm from './vnc-extra-form.vue';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
@@ -75,6 +86,7 @@
const labelForm = ref();
const sshForm = ref();
const rdpForm = ref();
const vncForm = ref();
// 打开配置
const open = (record: HostQueryResponse) => {
@@ -101,6 +113,9 @@
} else if (activeItem.value === ExtraSettingItems.RDP) {
// RDP 配置
value = await rdpForm.value.getValue();
} else if (activeItem.value === ExtraSettingItems.VNC) {
// VNC 配置
value = await vncForm.value.getValue();
}
if (!value) {
return false;

View File

@@ -0,0 +1,72 @@
<template>
<a-form :model="formModel"
ref="formRef"
label-align="right"
:label-col-props="{ span: 5 }"
:wrapper-col-props="{ span: 18 }">
<!-- 自定义端口 -->
<a-form-item field="port"
label="自定义端口"
style="margin-bottom: 8px;"
help="通常情况下端口号为 5900 + N(屏幕)"
:rules="{ min: 1, max: 65535, message: '输入的端口不合法' }">
<a-input-number v-model="formModel.port"
placeholder="VNC 自定义端口"
allow-clear />
</a-form-item>
<!-- 低带宽模式 -->
<a-form-item field="lowBandwidthMode"
label="低带宽模式"
help="调整图形化配置以及禁用音频, 提升慢速网络下的响应速度">
<a-switch v-model="formModel.lowBandwidthMode" type="round" />
</a-form-item>
</a-form>
</template>
<script lang="ts">
export default {
name: 'vncExtraForm'
};
</script>
<script lang="ts" setup>
import type { HostVncExtraSettingModel } from '@/api/asset/host-extra';
import { onMounted, ref } from 'vue';
import { getHostExtraItem } from '@/api/asset/host-extra';
import { useDictStore } from '@/store';
const props = defineProps<{
hostId: number;
item: string;
}>();
const { toRadioOptions } = useDictStore();
const formRef = ref();
const formModel = ref<Partial<HostVncExtraSettingModel>>({});
// 渲染表单
const renderForm = async () => {
const { data } = await getHostExtraItem<HostVncExtraSettingModel>({ hostId: props.hostId, item: props.item });
formModel.value = data;
};
// 获取值
const getValue = async () => {
// 验证参数
const error = await formRef.value.validate();
if (error) {
return false;
}
return JSON.stringify(formModel.value);
};
defineExpose({ getValue });
onMounted(renderForm);
</script>
<style lang="less" scoped>
</style>

View File

@@ -11,6 +11,8 @@
<terminal-rdp-graph-block />
<!-- RDP 会话设置 -->
<terminal-rdp-session-block />
<!-- VNC 图形化设置 -->
<terminal-vnc-graph-block />
</div>
</div>
</template>
@@ -26,6 +28,7 @@
import TerminalSshPluginsBlock from './terminal-ssh-plugins-block.vue';
import TerminalRdpGraphBlock from './terminal-rdp-graph-block.vue';
import TerminalRdpSessionBlock from './terminal-rdp-session-block.vue';
import TerminalVncGraphBlock from './terminal-vnc-graph-block.vue';
</script>

View File

@@ -14,7 +14,7 @@
<!-- 显示分辨率 -->
<block-setting-item label="显示分辨率" desc="若选择为自适应, 则会根据窗口大小自动调整">
<a-select v-model="formModel.displaySize"
style="width: 198px;"
style="width: 168px;"
size="small"
:options="toOptions(screenResolutionKey)"
allow-create />
@@ -22,14 +22,14 @@
<!-- 颜色深度 -->
<block-setting-item label="颜色深度" desc="显示的颜色深度, 越高显示效果越好">
<a-select v-model="formModel.colorDepth"
style="width: 198px;"
style="width: 168px;"
size="small"
:options="toOptions(graphColorDepthKey)" />
</block-setting-item>
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 无损压缩 -->
<block-setting-item label="无损压缩" desc="是否启用对图更新的无损压缩">
<block-setting-item label="无损压缩" desc="是否启用对图更新的无损压缩">
<a-switch v-model="formModel.forceLossless" type="round" />
</block-setting-item>
<!-- 启用壁纸 -->
@@ -79,7 +79,7 @@
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 禁用图形加速 -->
<block-setting-item label="禁用图加速" desc="禁用后将不再使用 GFX 进行数据编码">
<block-setting-item label="禁用图加速" desc="禁用后将不再使用 GFX 进行数据编码">
<a-switch v-model="formModel.disableGfx" type="round" />
</block-setting-item>
</a-row>

View File

@@ -16,7 +16,7 @@
<!-- 驱动挂载模式 -->
<block-setting-item label="驱动挂载模式">
<a-select v-model="formModel.driveMountMode"
style="width: 198px;"
style="width: 168px;"
size="small"
:options="toOptions(driveMountModeKey)" />
<template #desc>

View File

@@ -67,7 +67,7 @@
<block-setting-item label="单词分隔符" desc="在终端中双击文本将使用该分隔符进行分割 (一般不用修改)">
<a-input v-model="formModel.wordSeparator"
size="small"
style="width: 198px"
style="width: 168px"
placeholder="单词分隔符"
allow-clear />
</block-setting-item>
@@ -76,14 +76,14 @@
<!-- 终端类型 -->
<block-setting-item label="终端类型" desc="若显示异常请尝试切换此选项 兼容性 vt100 > xterm > 16color > 256color">
<a-select v-model="formModel.terminalEmulationType"
style="width: 198px;"
style="width: 168px;"
size="small"
:options="toOptions(emulationTypeKey)" />
</block-setting-item>
<!-- 缓冲区行数 -->
<block-setting-item label="缓冲区行数" desc="保存在缓冲区的行数, 多出的行数会被忽略, 此值越大占用内存的内存会更多">
<a-input-number v-model="formModel.scrollBackLine"
style="width: 198px"
style="width: 168px"
size="small"
:min="1"
:max="100000"
@@ -92,6 +92,12 @@
hide-button />
</block-setting-item>
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 替换退格符 -->
<block-setting-item label="替换退格符" desc="开启后会将退格符 (Backspace) 替换为 (Ctrl+H)">
<a-switch v-model="formModel.replaceBackspace" type="round" />
</block-setting-item>
</a-row>
</div>
</div>
</template>

View File

@@ -0,0 +1,133 @@
<template>
<div class="terminal-setting-block">
<!-- 顶部 -->
<div class="terminal-setting-subtitle-wrapper">
<h3 class="terminal-setting-subtitle">
VNC 图形化设置
</h3>
</div>
<!-- 提示 -->
<a-alert class="mb16">修改后会立刻保存, 重新打开终端后生效. 配置调整后可能会占用更多的带宽, 若发生卡顿/无法加载请调整配置</a-alert>
<!-- 内容区域 -->
<div class="terminal-setting-body setting-body">
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 显示分辨率 -->
<block-setting-item label="显示分辨率" desc="若选择为自适应, 则会根据窗口大小自动调整">
<a-select v-model="formModel.displaySize"
style="width: 168px;"
size="small"
:options="toOptions(screenResolutionKey)"
allow-create />
</block-setting-item>
<!-- 颜色深度 -->
<block-setting-item label="颜色深度" desc="显示的颜色深度, 越高显示效果越好">
<a-select v-model="formModel.colorDepth"
style="width: 168px;"
size="small"
:options="toOptions(graphColorDepthKey)" />
</block-setting-item>
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 无损压缩 -->
<block-setting-item label="无损压缩" desc="是否启用对图像更新的无损压缩">
<a-switch v-model="formModel.forceLossless" type="round" />
</block-setting-item>
<!-- 交换红蓝 -->
<block-setting-item label="交换红蓝" desc="若显示的颜色出现错误(蓝色显示为橙色或红色等) 则需要启用此选项">
<a-switch v-model="formModel.swapRedBlue" type="round" />
</block-setting-item>
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 压缩等级 -->
<block-setting-item label="压缩等级" desc="设置服务器请求的压缩级别 0表示不压缩, 9表示最高压缩级别">
<a-input-number v-model="formModel.compressLevel"
style="width: 168px;"
size="small"
mode="button"
:min="0"
:max="9"
placeholder="图像压缩等级 0 ~ 9" />
</block-setting-item>
<!-- 质量等级 -->
<block-setting-item label="质量等级" desc="设置图像的质量等级 0表示最低的图像质量, 9表示最高的图像质量">
<a-input-number v-model="formModel.qualityLevel"
style="width: 168px;"
size="small"
mode="button"
:min="0"
:max="9"
placeholder="图像质量等级 0 ~ 9" />
</block-setting-item>
</a-row>
<a-row class="mb16" align="stretch" :gutter="16">
<!-- 光标 -->
<block-setting-item label="光标" desc="光标渲染方式, 远程光标会比本地光标慢">
<a-select v-model="formModel.cursor"
style="width: 168px;"
size="small"
:options="toOptions(vcnCursorKey)" />
</block-setting-item>
</a-row>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'terminalVncGraphBlock'
};
</script>
<script lang="ts" setup>
import type { TerminalVncGraphSetting } from '@/store/modules/terminal/types';
import { ref, watch } from 'vue';
import { useTerminalStore, useDictStore } from '@/store';
import { TerminalPreferenceItem } from '@/store/modules/terminal';
import { graphColorDepthKey, vcnCursorKey, fitDisplayValue, screenResolutionKey } from '@/views/terminal/types/const';
import { getDisplaySize } from '@/views/terminal/types/utils';
import BlockSettingItem from '../block-setting-item.vue';
const { toOptions, getDictValue } = useDictStore();
const { preference, updateTerminalPreference } = useTerminalStore();
const formModel = ref<TerminalVncGraphSetting>({ ...preference.vncGraphSetting });
// 监听内容变化
watch(formModel, (v) => {
if (!v) {
return;
}
// 同步大小
if (v.displaySize) {
// 自适应大小
if (v.displaySize === fitDisplayValue) {
v.displayWidth = 0;
v.displayHeight = 0;
} else {
// 解析大小
try {
const [width, height] = getDisplaySize(v.displaySize, true);
v.displayWidth = width;
v.displayHeight = height;
} catch (e) {
return;
}
}
}
// 同步
updateTerminalPreference(TerminalPreferenceItem.VNC_GRAPH_SETTING, formModel.value, true);
}, { deep: true });
</script>
<style lang="less" scoped>
.setting-body {
flex-direction: column;
}
:deep(.arco-input-number) {
input {
text-align: center;
}
}
</style>

View File

@@ -44,18 +44,31 @@ export const TerminalTabs = {
export const TerminalSessionTypes = {
SSH: {
type: 'SSH',
protocol: 'SSH',
channel: 'ssh',
icon: 'icon-desktop'
icon: 'icon-desktop',
connectIcon: 'icon-thunderbolt',
},
SFTP: {
type: 'SFTP',
protocol: 'SSH',
channel: 'sftp',
icon: 'icon-folder'
icon: 'icon-folder',
connectIcon: 'icon-folder',
},
RDP: {
type: 'RDP',
protocol: 'RDP',
channel: 'rdp',
icon: 'icon-computer'
icon: 'icon-desktop',
connectIcon: 'icon-desktop',
},
VNC: {
type: 'VNC',
protocol: 'VNC',
channel: 'vnc',
icon: 'icon-computer',
connectIcon: 'icon-computer',
},
};
@@ -72,6 +85,7 @@ export const ExtraSettingItems = {
LABEL: 'LABEL',
SSH: 'SSH',
RDP: 'RDP',
VNC: 'VNC',
};
// 主机额外配置认证方式
@@ -97,7 +111,7 @@ export const TerminalMessages = {
sessionClosed: '会话已结束...',
waitingReconnect: '输入回车重新连接...',
loggedElsewhere: '该账号已在另一台设备登录',
rdpConnectTimeout: '请检查远程计算机网络及其他配置是否正常',
connectTimeout: '请检查远程计算机网络及其他配置是否正常',
fileTransferError: '传输失败',
fileSaveError: '保存失败',
fileUploading: '已开始上传, 点击右侧传输列表查看进度',
@@ -214,8 +228,8 @@ export const SshActionBarItems = [
}
];
// 终端操作栏键 - RDP
export const RdpActionItemKeys = {
// guacd 终端操作栏键
export const GuacdActionItemKeys = {
DISPLAY: 'display',
COMBINATION_KEY: 'combinationKey',
CLIPBOARD: 'clipboard',
@@ -225,45 +239,71 @@ export const RdpActionItemKeys = {
CLOSE: 'close',
};
// 终端操作栏 - RDP
export const RdpActionBarItems = [
{
item: RdpActionItemKeys.DISPLAY,
// guacd 终端操作栏
export const GuacdActionBarItemMap = {
[GuacdActionItemKeys.DISPLAY]: {
item: GuacdActionItemKeys.DISPLAY,
icon: 'icon-desktop',
content: '显示设置',
},
{
item: RdpActionItemKeys.COMBINATION_KEY,
[GuacdActionItemKeys.COMBINATION_KEY]: {
item: GuacdActionItemKeys.COMBINATION_KEY,
icon: 'icon-command',
content: '组合键',
},
{
item: RdpActionItemKeys.CLIPBOARD,
[GuacdActionItemKeys.CLIPBOARD]: {
item: GuacdActionItemKeys.CLIPBOARD,
icon: 'icon-paste',
content: '剪切板',
},
{
item: RdpActionItemKeys.UPLOAD,
[GuacdActionItemKeys.UPLOAD]: {
item: GuacdActionItemKeys.UPLOAD,
icon: 'icon-upload',
content: '文件上传',
},
{
item: RdpActionItemKeys.SAVE_RDP,
[GuacdActionItemKeys.SAVE_RDP]: {
item: GuacdActionItemKeys.SAVE_RDP,
icon: 'icon-save',
content: '保存 rdp 文件',
},
{
item: RdpActionItemKeys.DISCONNECT,
[GuacdActionItemKeys.DISCONNECT]: {
item: GuacdActionItemKeys.DISCONNECT,
icon: 'icon-stop',
content: '断开连接',
},
{
item: RdpActionItemKeys.CLOSE,
[GuacdActionItemKeys.CLOSE]: {
item: GuacdActionItemKeys.CLOSE,
icon: 'icon-close',
content: '关闭工具栏',
},
};
// 终端操作栏 - RDP
export const RdpActionBarItems = [
GuacdActionBarItemMap[GuacdActionItemKeys.DISPLAY],
GuacdActionBarItemMap[GuacdActionItemKeys.COMBINATION_KEY],
GuacdActionBarItemMap[GuacdActionItemKeys.CLIPBOARD],
GuacdActionBarItemMap[GuacdActionItemKeys.UPLOAD],
GuacdActionBarItemMap[GuacdActionItemKeys.SAVE_RDP],
GuacdActionBarItemMap[GuacdActionItemKeys.DISCONNECT],
GuacdActionBarItemMap[GuacdActionItemKeys.CLOSE],
];
// 终端操作栏 - VNC
export const VncActionBarItems = [
GuacdActionBarItemMap[GuacdActionItemKeys.DISPLAY],
GuacdActionBarItemMap[GuacdActionItemKeys.COMBINATION_KEY],
GuacdActionBarItemMap[GuacdActionItemKeys.CLIPBOARD],
GuacdActionBarItemMap[GuacdActionItemKeys.DISCONNECT],
GuacdActionBarItemMap[GuacdActionItemKeys.CLOSE],
];
// 终端操作栏方向
export const ActionBarPosition = {
TOP: 'top',
RIGHT: 'right',
};
// 终端快捷键操作类型
export const TerminalShortcutType = {
GLOBAL: 1,
@@ -403,8 +443,8 @@ export const TerminalShortcutItems: Array<ShortcutKeyItem> = [
},
];
// RDP 组合键元素
export const RdpCombinationKeyItems: Array<CombinationKeyItem> = [
// Guacd 组合键元素
export const GuacdCombinationKeyItems: Array<CombinationKeyItem> = [
{
keys: [65307],
name: 'Esc'
@@ -444,9 +484,90 @@ export const RdpCombinationKeyItems: Array<CombinationKeyItem> = [
{
keys: [65515, 120],
name: 'Windows+X'
}, {
keys: [65507, 99],
name: 'Ctrl+C'
},
{
keys: [65507, 118],
name: 'Ctrl+V'
},
{
keys: [65507, 120],
name: 'Ctrl+X'
},
{
keys: [65507, 97],
name: 'Ctrl+A'
},
{
keys: [65507, 122],
name: 'Ctrl+Z'
},
{
keys: [65507, 65535],
name: 'Ctrl+Delete'
},
{
keys: [65507, 65288],
name: 'Ctrl+Backspace'
},
{
keys: [65507, 65513, 65470],
name: 'Ctrl+Alt+F1'
},
{
keys: [65507, 65513, 65471],
name: 'Ctrl+Alt+F2'
},
{
keys: [65507, 65513, 65472],
name: 'Ctrl+Alt+F3'
},
{
keys: [65507, 65513, 65473],
name: 'Ctrl+Alt+F4'
},
{
keys: [65507, 65513, 65474],
name: 'Ctrl+Alt+F5'
},
{
keys: [65507, 65513, 65475],
name: 'Ctrl+Alt+F6'
},
{
keys: [65507, 65513, 65476],
name: 'Ctrl+Alt+F7'
},
{
keys: [65507, 65513, 65477],
name: 'Ctrl+Alt+F8'
},
{
keys: [65507, 65513, 65478],
name: 'Ctrl+Alt+F9'
},
{
keys: [65507, 65513, 65479],
name: 'Ctrl+Alt+F10'
},
{
keys: [65507, 65513, 65480],
name: 'Ctrl+Alt+F11'
},
{
keys: [65507, 65513, 65481],
name: 'Ctrl+Alt+F12'
}
];
// backspace 字符
export const BACKSPACE_CHAR = String.fromCharCode(127);
// ctrl^h 字符
export const CTRL_H_CHAR = String.fromCharCode(8);
// 传输状态
export const TransferStatus = {
WAITING: 'waiting',