🔨 重构终端前端逻辑
This commit is contained in:
@@ -18,8 +18,17 @@
|
||||
@tab-click="k => tabManager.clickTab(k as string)"
|
||||
@delete="k => tabManager.deleteTab(k as string)">
|
||||
<a-tab-pane v-for="tab in tabManager.items"
|
||||
:key="tab.key"
|
||||
:title="tab.title" />
|
||||
:key="tab.key">
|
||||
<!-- 标题 -->
|
||||
<template #title>
|
||||
<span class="tab-title-wrapper">
|
||||
<span class="tab-title-icon">
|
||||
<component :is="tab.icon" />
|
||||
</span>
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<!-- 右侧操作 -->
|
||||
@@ -45,6 +54,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import IconActions from '../layout/icon-actions.vue';
|
||||
import DictKeySelector from '@/components/system/dict-key/dict-key-selector.vue';
|
||||
|
||||
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
|
||||
const { tabManager } = useTerminalStore();
|
||||
@@ -118,6 +128,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tab-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tab-title-icon {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav) {
|
||||
height: 100%;
|
||||
|
||||
@@ -151,7 +171,7 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--color-header-text-1);
|
||||
background: var(--color-header-tabs-bg);
|
||||
background: var(--color-bg-header-tabs);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
@@ -177,8 +197,9 @@
|
||||
|
||||
.arco-tabs-tab-title {
|
||||
padding: 11px 18px;
|
||||
background: var(--color-header-tabs-bg);
|
||||
font-size: 12px;
|
||||
background: var(--color-bg-header-tabs);
|
||||
font-size: 14px;
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -213,11 +234,11 @@
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-tab-active) {
|
||||
background: var(--color-header-tabs-bg-hover);
|
||||
background: var(--color-bg-header-tabs-active);
|
||||
color: var(--color-header-text-2) !important;
|
||||
|
||||
.arco-tabs-tab-title {
|
||||
background: var(--color-header-tabs-bg-hover);
|
||||
background: var(--color-bg-header-tabs-active);
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div class="terminal-content">
|
||||
<!-- 内容 tabs -->
|
||||
<a-tabs v-if="tabManager.active"
|
||||
v-model:active-key="tabManager.active">
|
||||
v-model:active-key="tabManager.active"
|
||||
class="main-tabs">
|
||||
<a-tab-pane v-for="tab in tabManager.items"
|
||||
:key="tab.key"
|
||||
:title="tab.title">
|
||||
@@ -16,8 +17,8 @@
|
||||
<terminal-theme-setting v-else-if="tab.key === TerminalTabs.THEME_SETTING.key" />
|
||||
<!-- 终端设置 -->
|
||||
<terminal-general-setting v-else-if="tab.key === TerminalTabs.TERMINAL_SETTING.key" />
|
||||
<!-- 主机终端 -->
|
||||
<terminal-panel-view v-else-if="tab.key === TerminalTabs.TERMINAL_PANEL.key" />
|
||||
<!-- 终端面板 -->
|
||||
<terminal-panels-view v-else-if="tab.key === TerminalTabs.TERMINAL_PANEL.key" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 承载页推荐 -->
|
||||
@@ -42,11 +43,11 @@
|
||||
import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue';
|
||||
import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue';
|
||||
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
|
||||
import TerminalPanelView from '@/views/host/terminal/components/layout/terminal-panel-view.vue';
|
||||
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
|
||||
|
||||
const { preference, tabManager, sessionManager } = useTerminalStore();
|
||||
|
||||
// fixme TerminalTabType.TERMINAL
|
||||
// fixme title逻辑 失焦逻辑
|
||||
// 监听 tab 修改
|
||||
watch(() => tabManager.active, (active, before) => {
|
||||
if (before) {
|
||||
@@ -58,11 +59,10 @@
|
||||
}
|
||||
if (active) {
|
||||
// 获取 activeTab
|
||||
const activeTab = tabManager.items.find(s => s.key === active);
|
||||
const activeTab = tabManager.getCurrentTab();
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
console.log(activeTab.title);
|
||||
// 修改标题
|
||||
document.title = activeTab.title;
|
||||
// 终端自动聚焦
|
||||
@@ -78,8 +78,8 @@
|
||||
// 处理快捷键逻辑
|
||||
const handlerKeyboard = (event: Event) => {
|
||||
// 当前页面非 terminal 的时候再触发快捷键 (terminal 有内置逻辑)
|
||||
if (tabManager.active
|
||||
&& tabManager.items.find(s => s.key === tabManager.active)?.type === 'TerminalTabType.TERMINAL') {
|
||||
// fixme panel 无数据继续触发
|
||||
if (tabManager.getCurrentTab()?.key === TerminalTabs.TERMINAL_PANEL.key) {
|
||||
return;
|
||||
}
|
||||
const e = event as KeyboardEvent;
|
||||
@@ -140,15 +140,15 @@
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
:deep(.arco-tabs) {
|
||||
:deep(.main-tabs) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.arco-tabs-nav {
|
||||
> .arco-tabs-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.arco-tabs-content {
|
||||
> .arco-tabs-content {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'terminalPanelView'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="terminal-panel-container">
|
||||
<a-tabs v-model:active-key="panel.active"
|
||||
:editable="true"
|
||||
:auto-switch="true"
|
||||
@tab-click="k => panel.clickTab(k as string)"
|
||||
@delete="k => panel.deleteTab(k as string)">
|
||||
<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>
|
||||
</template>
|
||||
<!-- 终端 -->
|
||||
<terminal-view :tab="tab" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'terminalPanel'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ITerminalTabManager } from '../../types/terminal.type';
|
||||
import TerminalView from '@/views/host/terminal/components/xterm/terminal-view.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
panel: ITerminalTabManager
|
||||
}>();
|
||||
|
||||
// FIXME 全部关闭则关闭
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.terminal-panel-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tab-title-icon {
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.arco-tabs-content {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - var(--panel-nav-height));
|
||||
overflow: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav) {
|
||||
height: var(--panel-nav-height);
|
||||
background: var(--color-bg-panel);
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-tab {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&-ink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-button .arco-icon-hover:hover {
|
||||
color: var(--color-panel-text-2);
|
||||
|
||||
&::before {
|
||||
background: var(--color-bg-panel-icon-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-type-line .arco-tabs-tab:hover .arco-tabs-tab-title::before) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-tab) {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--color-panel-text-1);
|
||||
background: var(--color-bg-panel-tabs);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-panel-text-2);
|
||||
transition: .2s;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 54px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background: linear-gradient(270deg, var(--color-panel-gradient-start) 45%, var(--color-panel-gradient-end) 120%);
|
||||
}
|
||||
|
||||
.arco-tabs-tab-title {
|
||||
padding: 11px 18px;
|
||||
background: var(--color-bg-panel-tabs);
|
||||
font-size: 14px;
|
||||
height: var(--panel-nav-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .arco-tabs-tab-close-btn {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
&-close-btn {
|
||||
margin: 0 8px 0 0;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 4;
|
||||
display: none;
|
||||
color: var(--color-panel-text-2);
|
||||
|
||||
&:hover {
|
||||
transition: .2s;
|
||||
background: var(--color-bg-panel-icon-1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-tab-active) {
|
||||
background: var(--color-bg-panel-tabs-active);
|
||||
color: var(--color-panel-text-2) !important;
|
||||
|
||||
.arco-tabs-tab-title {
|
||||
background: var(--color-bg-panel-tabs-active);
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background: linear-gradient(270deg, var(--color-panel-gradient-start) 45%, var(--color-panel-gradient-end) 120%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="terminal-panels-container">
|
||||
<!-- 面板 -->
|
||||
<terminal-panel v-for="panelIndex in panelManager.panels.length"
|
||||
:key="panelIndex"
|
||||
:panel="panelManager.panels[panelIndex - 1]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'terminalPanelView'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTerminalStore } from '@/store';
|
||||
import TerminalPanel from './terminal-panel.vue';
|
||||
import { onUnmounted } from 'vue';
|
||||
|
||||
const { panelManager } = useTerminalStore();
|
||||
// FIXME 全部关闭则关闭
|
||||
|
||||
// 卸载清空
|
||||
onUnmounted(() => {
|
||||
panelManager.reset();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.terminal-panels-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--header-height));
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@
|
||||
@save="savePreference" />
|
||||
<!-- 系统快捷键 -->
|
||||
<terminal-shortcut-keys-block title="系统快捷键"
|
||||
:type="TerminalShortcutType.SYSTEM"
|
||||
:type="TerminalShortcutType.TAB"
|
||||
:items="shortcutKeys"
|
||||
@set-editable="setEditableStatus"
|
||||
@clear-editable="clearEditableStatus"
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div class="terminal-container">
|
||||
<!-- 头部 -->
|
||||
<div class="terminal-header"
|
||||
:style="{
|
||||
background: preference.theme.headerBackgroundColor
|
||||
}">
|
||||
<div class="terminal-header">
|
||||
<!-- 左侧操作 -->
|
||||
<div class="terminal-header-left">
|
||||
<!-- 主机地址 -->
|
||||
@@ -161,7 +158,7 @@
|
||||
|
||||
.terminal-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--header-height));
|
||||
height: calc(100vh - var(--header-height) - var(--panel-nav-height));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -172,6 +169,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--color-bg-panel-bar);
|
||||
|
||||
&-left, &-right {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { ITerminalPanelManager, TerminalPanelTabItem } from '../types/terminal.type';
|
||||
import TerminalTabManager from '../handler/terminal-tab-manager';
|
||||
|
||||
// 终端面板管理器实现
|
||||
export default class TerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> implements ITerminalPanelManager<T> {
|
||||
|
||||
// 当前面板
|
||||
active: number;
|
||||
// 面板列表
|
||||
panels: Array<TerminalTabManager<T>>;
|
||||
|
||||
constructor() {
|
||||
this.active = 0;
|
||||
this.panels = [new TerminalTabManager()];
|
||||
}
|
||||
|
||||
// 获取当前面板
|
||||
getCurrentPanel(): TerminalTabManager<T> {
|
||||
return this.panels[this.active];
|
||||
}
|
||||
|
||||
// 设置当前面板
|
||||
setCurrentPanel(active: number): void {
|
||||
this.active = active;
|
||||
};
|
||||
|
||||
// 获取面板
|
||||
getPanel(index: number): TerminalTabManager<T> {
|
||||
return this.panels[index];
|
||||
};
|
||||
|
||||
// 重置
|
||||
reset() {
|
||||
for (let panel of this.panels) {
|
||||
panel.clear();
|
||||
}
|
||||
this.active = 0;
|
||||
this.panels = [new TerminalTabManager()];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type';
|
||||
|
||||
// 终端 tab 管理器实现
|
||||
export default class TerminalTabManager implements ITerminalTabManager {
|
||||
export default class TerminalTabManagerm<T extends TerminalTabItem = TerminalTabItem> implements ITerminalTabManager<T> {
|
||||
|
||||
public active: string;
|
||||
|
||||
public items: Array<TerminalTabItem>;
|
||||
public items: Array<T>;
|
||||
|
||||
constructor(def: TerminalTabItem | undefined = undefined) {
|
||||
constructor(def: T | undefined = undefined) {
|
||||
if (def) {
|
||||
this.active = def.key;
|
||||
this.items = [def];
|
||||
@@ -45,7 +45,7 @@ export default class TerminalTabManager implements ITerminalTabManager {
|
||||
}
|
||||
|
||||
// 打开 tab
|
||||
openTab(tab: TerminalTabItem): void {
|
||||
openTab(tab: T): void {
|
||||
// 不存在则创建 tab
|
||||
if (!this.items.find(s => s.key === tab.key)) {
|
||||
this.items.push(tab);
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
width: var(--sidebar-width);
|
||||
height: 100%;
|
||||
background: var(--color-bg-sidebar);
|
||||
border-top: 1px solid var(--color-bg-content);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ export const ExtraSshAuthType = {
|
||||
CUSTOM_IDENTITY: 'CUSTOM_IDENTITY',
|
||||
};
|
||||
|
||||
// 终端面板 tab 类型
|
||||
export const TerminalPanelTabType = {
|
||||
TERMINAL: 'terminal',
|
||||
SFTP: 'sftp',
|
||||
};
|
||||
|
||||
// 终端状态
|
||||
export const TerminalStatus = {
|
||||
// 连接中
|
||||
@@ -125,7 +131,7 @@ export const ActionBarItems = [
|
||||
|
||||
// 终端快捷键操作类型
|
||||
export const TerminalShortcutType = {
|
||||
SYSTEM: 1,
|
||||
TAB: 1,
|
||||
TERMINAL: 2
|
||||
};
|
||||
|
||||
@@ -146,19 +152,19 @@ export const TerminalShortcutItems: Array<ShortcutKeyItem> = [
|
||||
{
|
||||
item: TerminalShortcutKeys.CHANGE_TO_PREV_TAB,
|
||||
content: '切换为前一个 tab',
|
||||
type: TerminalShortcutType.SYSTEM
|
||||
type: TerminalShortcutType.TAB
|
||||
}, {
|
||||
item: TerminalShortcutKeys.CHANGE_TO_NEXT_TAB,
|
||||
content: '切换为后一个 tab',
|
||||
type: TerminalShortcutType.SYSTEM
|
||||
type: TerminalShortcutType.TAB
|
||||
}, {
|
||||
item: TerminalShortcutKeys.CLOSE_TAB,
|
||||
content: '关闭当前 tab',
|
||||
type: TerminalShortcutType.SYSTEM
|
||||
type: TerminalShortcutType.TAB
|
||||
}, {
|
||||
item: TerminalShortcutKeys.OPEN_NEW_CONNECT_TAB,
|
||||
content: '打开新建连接 tab',
|
||||
type: TerminalShortcutType.SYSTEM
|
||||
type: TerminalShortcutType.TAB
|
||||
}, {
|
||||
item: 'openCopyTerminalTab',
|
||||
content: '复制当前终端 tab',
|
||||
|
||||
@@ -12,11 +12,19 @@ import type { HostQueryResponse } from '@/api/asset/host';
|
||||
export interface TerminalTabItem {
|
||||
key: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
icon: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 终端面板 tab 元素
|
||||
export interface TerminalPanelTabItem extends TerminalTabItem {
|
||||
seq: number;
|
||||
hostId: number;
|
||||
address: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
// sidebar 操作类型
|
||||
export interface SidebarAction {
|
||||
icon: string;
|
||||
@@ -90,20 +98,20 @@ export interface TerminalDomRef {
|
||||
}
|
||||
|
||||
// 终端 tab 管理器定义
|
||||
export interface ITerminalTabManager {
|
||||
export interface ITerminalTabManager<T extends TerminalTabItem = TerminalTabItem> {
|
||||
// 当前 tab
|
||||
active: string;
|
||||
// 全部 tab
|
||||
items: Array<TerminalTabItem>;
|
||||
items: Array<T>;
|
||||
|
||||
// 获取当前 tab
|
||||
getCurrentTab: () => TerminalTabItem | undefined;
|
||||
getCurrentTab: () => T | undefined;
|
||||
// 点击 tab
|
||||
clickTab: (key: string) => void;
|
||||
// 删除 tab
|
||||
deleteTab: (key: string) => void;
|
||||
// 打开 tab
|
||||
openTab: (tab: TerminalTabItem) => void;
|
||||
openTab: (tab: T) => void;
|
||||
// 切换到前一个 tab
|
||||
changeToPrevTab: () => void;
|
||||
// 切换到后一个 tab
|
||||
@@ -114,6 +122,23 @@ export interface ITerminalTabManager {
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
// 终端面板管理器定义
|
||||
export interface ITerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> {
|
||||
// 当前面板
|
||||
active: number;
|
||||
// 面板列表
|
||||
panels: Array<ITerminalTabManager<T>>;
|
||||
|
||||
// 获取当前面板
|
||||
getCurrentPanel: () => ITerminalTabManager<T>;
|
||||
// 设置当前面板
|
||||
setCurrentPanel: (active: number) => void;
|
||||
// 获取面板
|
||||
getPanel: (index: number) => ITerminalTabManager<T>;
|
||||
// 重置
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
// 终端会话管理器定义
|
||||
export interface ITerminalSessionManager {
|
||||
// 打开终端会话
|
||||
|
||||
Reference in New Issue
Block a user