♻️ 终端面板开发完成
This commit is contained in:
@@ -160,7 +160,7 @@ export default defineStore('terminal', {
|
||||
},
|
||||
|
||||
// 复制并且打开终端
|
||||
openCopyTerminal(hostId: number, panelIndex: number = 0) {
|
||||
copyTerminalSession(hostId: number, panelIndex: number = 0) {
|
||||
const host = this.hosts.hostList
|
||||
.find(s => s.id === hostId);
|
||||
if (host) {
|
||||
@@ -168,28 +168,35 @@ export default defineStore('terminal', {
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前终端会话
|
||||
getCurrentTerminalSession(tips: boolean = true) {
|
||||
// 获取当前 tab
|
||||
const tab = this.tabManager.getCurrentTab();
|
||||
if (!tab || tab.key !== TerminalTabs.TERMINAL_PANEL.key) {
|
||||
// 检查当前是否为终端页面 并且获取当前终端会话
|
||||
getAndCheckCurrentTerminalSession(tips: boolean = true) {
|
||||
// 获取当前 activeTab
|
||||
const activeTab = this.tabManager.active;
|
||||
if (activeTab !== TerminalTabs.TERMINAL_PANEL.key) {
|
||||
if (tips) {
|
||||
Message.warning('请切换到终端标签页');
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 获取当前会话
|
||||
const session = this.getCurrentTerminalSession();
|
||||
if (!session && tips) {
|
||||
Message.warning('请打开终端');
|
||||
}
|
||||
return session;
|
||||
},
|
||||
|
||||
// 获取当前终端会话
|
||||
getCurrentTerminalSession() {
|
||||
// 获取面板会话
|
||||
const activeTab = this.panelManager
|
||||
const panelTab = this.panelManager
|
||||
.getCurrentPanel()
|
||||
.getCurrentTab();
|
||||
if (!activeTab || activeTab.type !== TerminalPanelTabType.TERMINAL) {
|
||||
if (tips) {
|
||||
Message.warning('请打开终端');
|
||||
}
|
||||
if (!panelTab || panelTab.type !== TerminalPanelTabType.TERMINAL) {
|
||||
return;
|
||||
}
|
||||
// 获取会话
|
||||
return this.sessionManager.getSession(activeTab.key);
|
||||
return this.sessionManager.getSession(panelTab.key);
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
// 关闭回调
|
||||
const onClose = () => {
|
||||
// 聚焦终端
|
||||
getCurrentTerminalSession(false)?.focus();
|
||||
getCurrentTerminalSession()?.focus();
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
}>();
|
||||
|
||||
const { copy } = useCopy();
|
||||
const { getCurrentTerminalSession } = useTerminalStore();
|
||||
const { getAndCheckCurrentTerminalSession } = useTerminalStore();
|
||||
|
||||
let clickCount = 0;
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
|
||||
// 写入命令
|
||||
const write = (command: string) => {
|
||||
const handler = getCurrentTerminalSession()?.handler;
|
||||
const handler = getAndCheckCurrentTerminalSession()?.handler;
|
||||
if (handler && handler.enabledStatus('checkAppendMissing')) {
|
||||
handler.checkAppendMissing(command);
|
||||
}
|
||||
|
||||
@@ -45,43 +45,26 @@
|
||||
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
|
||||
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
|
||||
|
||||
const { preference, tabManager, sessionManager } = useTerminalStore();
|
||||
const { preference, tabManager, getCurrentTerminalSession } = useTerminalStore();
|
||||
|
||||
// fixme title逻辑 失焦逻辑
|
||||
// 监听 tab 修改
|
||||
// 监听 tab 切换
|
||||
watch(() => tabManager.active, (active, before) => {
|
||||
if (before) {
|
||||
// 失焦已经切换的终端
|
||||
const beforeTab = tabManager.items.find(s => s.key === before);
|
||||
if (beforeTab && beforeTab?.type === 'TerminalTabType.TERMINAL') {
|
||||
sessionManager.getSession(before)?.blur();
|
||||
}
|
||||
// 失焦 tab
|
||||
if (before === TerminalTabs.TERMINAL_PANEL.key) {
|
||||
getCurrentTerminalSession()?.blur();
|
||||
}
|
||||
if (active) {
|
||||
// 获取 activeTab
|
||||
const activeTab = tabManager.getCurrentTab();
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
// 修改标题
|
||||
document.title = activeTab.title;
|
||||
// 终端自动聚焦
|
||||
if (activeTab?.type === 'TerminalTabType.TERMINAL') {
|
||||
sessionManager.getSession(active)?.focus();
|
||||
}
|
||||
} else {
|
||||
// 修改标题
|
||||
document.title = TerminalTabs.TERMINAL_PANEL.title;
|
||||
// 聚焦 tab
|
||||
if (active === TerminalTabs.TERMINAL_PANEL.key) {
|
||||
getCurrentTerminalSession()?.focus();
|
||||
}
|
||||
// 修改标题
|
||||
document.title = Object.values(TerminalTabs)
|
||||
.find(s => s.key === active)?.title
|
||||
|| TerminalTabs.TERMINAL_PANEL.title;
|
||||
});
|
||||
|
||||
// 处理快捷键逻辑
|
||||
// 处理全局快捷键逻辑
|
||||
const handlerKeyboard = (event: Event) => {
|
||||
// 当前页面非 terminal 的时候再触发快捷键 (terminal 有内置逻辑)
|
||||
// fixme panel 无数据继续触发
|
||||
if (tabManager.getCurrentTab()?.key === TerminalTabs.TERMINAL_PANEL.key) {
|
||||
return;
|
||||
}
|
||||
const e = event as KeyboardEvent;
|
||||
// 检测触发的快捷键
|
||||
const key = preference.shortcutSetting.keys.find(key => {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
const emits = defineEmits(['openSftp', 'openTransfer']);
|
||||
|
||||
const { getCurrentTerminalSession } = useTerminalStore();
|
||||
const { getAndCheckCurrentTerminalSession } = useTerminalStore();
|
||||
|
||||
const snippetRef = ref();
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
// 终端截屏
|
||||
const screenshot = () => {
|
||||
const handler = getCurrentTerminalSession()?.handler;
|
||||
const handler = getAndCheckCurrentTerminalSession()?.handler;
|
||||
if (handler && handler.enabledStatus('screenshot')) {
|
||||
handler.screenshot();
|
||||
}
|
||||
|
||||
@@ -10,19 +10,23 @@
|
||||
@delete="k => panel.deleteTab(k as string)">
|
||||
<!-- 右侧按钮 -->
|
||||
<template #extra>
|
||||
<a-button>Action</a-button>
|
||||
<a-space class="panel-extra">
|
||||
<span class="extra-icon" @click="close">
|
||||
<icon-close />
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 终端面板 -->
|
||||
<a-tab-pane v-for="tab in panel.items"
|
||||
:key="tab.key">
|
||||
<!-- 标题 -->
|
||||
<template #title>
|
||||
<span class="tab-title-wrapper">
|
||||
<span class="tab-title-icon">
|
||||
<component :is="tab.icon" />
|
||||
</span>
|
||||
{{ tab.title }}
|
||||
<span class="tab-title-wrapper">
|
||||
<span class="tab-title-icon">
|
||||
<component :is="tab.icon" />
|
||||
</span>
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- 终端 -->
|
||||
<terminal-view :tab="tab" />
|
||||
@@ -41,28 +45,55 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ITerminalTabManager } from '../../types/terminal.type';
|
||||
import type { ITerminalTabManager, TerminalPanelTabItem } from '../../types/terminal.type';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { TerminalPanelTabType } from '../../types/terminal.const';
|
||||
import TerminalView from '../xterm/terminal-view.vue';
|
||||
import HostListModal from '../new-connection/host-list-modal.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
index: number,
|
||||
panel: ITerminalTabManager,
|
||||
panel: ITerminalTabManager<TerminalPanelTabItem>,
|
||||
}>();
|
||||
|
||||
const { openTerminal } = useTerminalStore();
|
||||
const emits = defineEmits(['close']);
|
||||
|
||||
const { sessionManager, openTerminal } = useTerminalStore();
|
||||
|
||||
const hostModal = ref();
|
||||
|
||||
// 监听 tab 切换
|
||||
watch(() => props.panel.active, (active, before) => {
|
||||
// 失焦自动终端
|
||||
if (before) {
|
||||
const beforeTab = props.panel.items.find(s => s.key === before);
|
||||
if (beforeTab && beforeTab?.type === TerminalPanelTabType.TERMINAL) {
|
||||
sessionManager.getSession(before)?.blur();
|
||||
}
|
||||
}
|
||||
// 终端自动聚焦
|
||||
if (active) {
|
||||
const activeTab = props.panel.items.find(s => s.key === active);
|
||||
if (activeTab && activeTab?.type === TerminalPanelTabType.TERMINAL) {
|
||||
sessionManager.getSession(active)?.focus();
|
||||
}
|
||||
}
|
||||
// 无终端自动关闭
|
||||
if (!props.panel.items.length) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
// 打开主机模态框
|
||||
const openHostModal = () => {
|
||||
hostModal.value.open();
|
||||
};
|
||||
|
||||
// FIXME 全部关闭展示新增
|
||||
onMounted(openHostModal);
|
||||
// 关闭面板
|
||||
const close = () => {
|
||||
emits('close', props.index);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
@@ -82,6 +113,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.panel-extra {
|
||||
margin-right: 8px;
|
||||
|
||||
.extra-icon {
|
||||
color: var(--color-panel-text-2);
|
||||
transition: 0.2s;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-panel-icon-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -129,7 +181,6 @@
|
||||
background: var(--color-bg-panel-icon-1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-type-line .arco-tabs-tab:hover .arco-tabs-tab-title::before) {
|
||||
@@ -214,7 +265,6 @@
|
||||
&:hover::after {
|
||||
background: linear-gradient(270deg, var(--color-panel-gradient-start) 45%, var(--color-panel-gradient-end) 120%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="terminal-panels-container">
|
||||
<!-- 面板 -->
|
||||
<terminal-panel v-for="panelIndex in panelManager.panels.length"
|
||||
:key="panelIndex"
|
||||
:index="panelIndex - 1"
|
||||
:panel="panelManager.panels[panelIndex - 1]" />
|
||||
<terminal-panel v-for="(panel, index) in panelManager.panels"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:panel="panel"
|
||||
@close="closePanel" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,11 +17,23 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTerminalStore } from '@/store';
|
||||
import TerminalPanel from './terminal-panel.vue';
|
||||
import { onUnmounted } from 'vue';
|
||||
import { TerminalTabs } from '../../types/terminal.const';
|
||||
import TerminalPanel from './terminal-panel.vue';
|
||||
|
||||
const { panelManager } = useTerminalStore();
|
||||
// FIXME 全部关闭则关闭
|
||||
const { tabManager, panelManager } = useTerminalStore();
|
||||
|
||||
// 移除面板
|
||||
const closePanel = (index: number) => {
|
||||
panelManager.getPanel(index)?.clear();
|
||||
if (panelManager.panels.length == 1) {
|
||||
// 关闭 tab
|
||||
tabManager.deleteTab(TerminalTabs.TERMINAL_PANEL.key);
|
||||
} else {
|
||||
// 关闭面板
|
||||
panelManager.removePanel(index);
|
||||
}
|
||||
};
|
||||
|
||||
// 卸载清空
|
||||
onUnmounted(() => {
|
||||
@@ -34,5 +47,6 @@
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--header-height));
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<terminal-shortcut-action-block v-model:enabled="enabled"
|
||||
@reset="loadDefaultPreference"
|
||||
@save="savePreference" />
|
||||
<!-- 系统快捷键 -->
|
||||
<terminal-shortcut-keys-block title="系统快捷键"
|
||||
:type="TerminalShortcutType.TAB"
|
||||
<!-- 全局快捷键 -->
|
||||
<terminal-shortcut-keys-block title="全局快捷键"
|
||||
:type="TerminalShortcutType.GLOBAL"
|
||||
:items="shortcutKeys"
|
||||
@set-editable="setEditableStatus"
|
||||
@clear-editable="clearEditableStatus"
|
||||
|
||||
@@ -29,6 +29,12 @@ export default class TerminalPanelManager<T extends TerminalPanelTabItem = Termi
|
||||
return this.panels[index];
|
||||
};
|
||||
|
||||
// 移除面板
|
||||
removePanel(index: number) {
|
||||
this.panels.splice(index, 1);
|
||||
this.active = index >= this.panels.length ? this.panels.length - 1 : index;
|
||||
};
|
||||
|
||||
// 重置
|
||||
reset() {
|
||||
for (let panel of this.panels) {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import type { ShortcutKey, TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
|
||||
import type { ITerminalSession, ITerminalSessionHandler, ITerminalTabManager, TerminalDomRef } from '../types/terminal.type';
|
||||
import type { ITerminalSession, ITerminalSessionHandler, TerminalDomRef } from '../types/terminal.type';
|
||||
import type { Terminal } from 'xterm';
|
||||
import useCopy from '@/hooks/copy';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { useTerminalStore, useUserStore } from '@/store';
|
||||
import { TerminalTabs } from '../types/terminal.const';
|
||||
import { TerminalShortcutItems } from '../types/terminal.const';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
// 组织默认行为的快捷键
|
||||
// 阻止默认行为的快捷键
|
||||
const preventKeys: Array<ShortcutKey> = [
|
||||
{
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
code: 'KeyC'
|
||||
}, {
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
@@ -39,8 +44,6 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
|
||||
private readonly shortcutKeys: Array<TerminalShortcutKey>;
|
||||
|
||||
private readonly tabManager: ITerminalTabManager;
|
||||
|
||||
constructor(session: ITerminalSession,
|
||||
domRef: TerminalDomRef) {
|
||||
this.session = session;
|
||||
@@ -49,7 +52,6 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
const { preference, tabManager } = useTerminalStore();
|
||||
this.interactSetting = preference.interactSetting;
|
||||
this.shortcutKeys = preference.shortcutSetting.keys;
|
||||
this.tabManager = tabManager;
|
||||
}
|
||||
|
||||
// 检测是否忽略默认行为
|
||||
@@ -94,22 +96,18 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
handler && handler.call(this);
|
||||
}
|
||||
|
||||
// 触发快捷键
|
||||
triggerShortcutKey(e: KeyboardEvent): boolean {
|
||||
// 检测触发的快捷键
|
||||
// 获取快捷键
|
||||
getShortcutKey(e: KeyboardEvent) {
|
||||
const key = this.shortcutKeys.find(key => {
|
||||
return key.code === e.code
|
||||
&& key.altKey === e.altKey
|
||||
&& key.shiftKey === e.shiftKey
|
||||
&& key.ctrlKey === e.ctrlKey;
|
||||
});
|
||||
if (key) {
|
||||
// 调用处理方法
|
||||
this.invokeHandle.call(this, key.item);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
if (!key) {
|
||||
return undefined;
|
||||
}
|
||||
return TerminalShortcutItems.find(s => s.item === key.item);
|
||||
}
|
||||
|
||||
// 复制选中
|
||||
@@ -306,29 +304,4 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭 tab
|
||||
closeTab() {
|
||||
this.tabManager.deleteTab(this.session.sessionId);
|
||||
}
|
||||
|
||||
// 切换到前一个 tab
|
||||
changeToPrevTab() {
|
||||
this.tabManager.changeToPrevTab();
|
||||
}
|
||||
|
||||
// 切换到后一个 tab
|
||||
changeToNextTab() {
|
||||
this.tabManager.changeToNextTab();
|
||||
}
|
||||
|
||||
// 复制终端 tab
|
||||
openCopyTerminalTab() {
|
||||
useTerminalStore().openCopyTerminal(this.session.hostId);
|
||||
}
|
||||
|
||||
// 打开新建连接 tab
|
||||
openNewConnectTab() {
|
||||
this.tabManager.openTab(TerminalTabs.NEW_CONNECTION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { TerminalPreference } from '@/store/modules/terminal/types';
|
||||
import type { ITerminalChannel, ITerminalSession, ITerminalSessionHandler, TerminalAddons, TerminalDomRef } from '../types/terminal.type';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { InputProtocol } from '../types/terminal.protocol';
|
||||
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
|
||||
import { fontFamilySuffix, TerminalShortcutType, TerminalStatus } from '../types/terminal.const';
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
@@ -12,8 +12,8 @@ import { ImageAddon } from 'xterm-addon-image';
|
||||
import { CanvasAddon } from 'xterm-addon-canvas';
|
||||
import { WebglAddon } from 'xterm-addon-webgl';
|
||||
import { playBell } from '@/utils/bell';
|
||||
import TerminalSessionHandler from './terminal-session-handler';
|
||||
import { addEventListen } from '@/utils/event';
|
||||
import TerminalSessionHandler from './terminal-session-handler';
|
||||
|
||||
// 终端会话实现
|
||||
export default class TerminalSession implements ITerminalSession {
|
||||
@@ -82,16 +82,21 @@ export default class TerminalSession implements ITerminalSession {
|
||||
private registerShortcut(preference: UnwrapRef<TerminalPreference>) {
|
||||
// 处理自定义按键
|
||||
this.inst.attachCustomKeyEventHandler((e: KeyboardEvent) => {
|
||||
// 检测是否忽略默认行为
|
||||
if (e.type !== 'keydown') {
|
||||
return true;
|
||||
}
|
||||
// 检测是否阻止默认行为
|
||||
if (this.handler.checkPreventDefault(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
// 触发快捷键检测
|
||||
if (e.type === 'keydown'
|
||||
&& preference.shortcutSetting.enabled
|
||||
&& preference.shortcutSetting.keys.length) {
|
||||
// 触发快捷键
|
||||
return this.handler.triggerShortcutKey(e);
|
||||
if (preference.shortcutSetting.enabled && preference.shortcutSetting.keys.length) {
|
||||
// 获取触发的快捷键
|
||||
const shortcutKey = this.handler.getShortcutKey(e);
|
||||
// 触发终端快捷键
|
||||
if (shortcutKey?.type === TerminalShortcutType.TERMINAL) {
|
||||
this.handler.invokeHandle.call(this.handler, shortcutKey.item);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -135,6 +135,8 @@ export interface ITerminalPanelManager<T extends TerminalPanelTabItem = Terminal
|
||||
setCurrentPanel: (active: number) => void;
|
||||
// 获取面板
|
||||
getPanel: (index: number) => ITerminalTabManager<T>;
|
||||
// 移除面板
|
||||
removePanel: (index: number) => void;
|
||||
// 重置
|
||||
reset: () => void;
|
||||
}
|
||||
@@ -232,8 +234,8 @@ export interface ITerminalSessionHandler {
|
||||
enabledStatus: (option: string) => boolean;
|
||||
// 调用处理方法
|
||||
invokeHandle: (option: string) => void;
|
||||
// 触发快捷键
|
||||
triggerShortcutKey: (e: KeyboardEvent) => boolean;
|
||||
// 获取快捷键
|
||||
getShortcutKey: (e: KeyboardEvent) => ShortcutKeyItem | undefined;
|
||||
|
||||
// 复制选中
|
||||
copy: () => void;
|
||||
@@ -269,14 +271,4 @@ export interface ITerminalSessionHandler {
|
||||
screenshot: () => void;
|
||||
// 检查追加缺失的部分
|
||||
checkAppendMissing: (value: string) => void;
|
||||
// 关闭 tab
|
||||
closeTab: () => void;
|
||||
// 切换到前一个 tab
|
||||
changeToPrevTab: () => void;
|
||||
// 切换到后一个 tab
|
||||
changeToNextTab: () => void;
|
||||
// 复制终端 tab
|
||||
openCopyTerminalTab: () => void;
|
||||
// 打开新建连接 tab
|
||||
openNewConnectTab: () => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user