♻️ 终端面板开发完成

This commit is contained in:
lijiahang
2024-02-02 18:03:46 +08:00
parent f59ccb3788
commit cff8b7e0d1
12 changed files with 163 additions and 133 deletions

View File

@@ -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);
},
},

View File

@@ -267,7 +267,7 @@
// 关闭回调
const onClose = () => {
// 聚焦终端
getCurrentTerminalSession(false)?.focus();
getCurrentTerminalSession()?.focus();
};
</script>

View File

@@ -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);
}

View File

@@ -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 => {

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;
});

View File

@@ -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;
}