♻️ 终端面板开发完成

This commit is contained in:
lijiahang
2024-02-02 15:45:08 +08:00
parent 9bf4a8e291
commit f59ccb3788
8 changed files with 317 additions and 122 deletions

View File

@@ -42,7 +42,7 @@
// 垂直滚动
.arco-scrollbar-track-direction-vertical {
width: 6px;
width: 10px;
.arco-scrollbar-thumb-bar {
margin: 0;
@@ -51,7 +51,7 @@
// 水平滚动
.arco-scrollbar-track-direction-horizontal {
height: 6px;
width: 10px;
.arco-scrollbar-thumb-bar {
margin: 0;

View File

@@ -45,7 +45,7 @@ body[terminal-theme='dark'] {
--color-content-text-2: rgba(255, 255, 255, .85);
--color-content-text-3: rgba(255, 255, 255, .95);
--color-bg-panel-tabs: var(--color-bg-panel);
--color-bg-panel-tabs-active: #434343;
--color-bg-panel-tabs-active: #383838;
--color-bg-panel-icon-1: var(--color-bg-panel-tabs-active);
--color-bg-panel-bar: #323538;
--color-panel-text-1: var(--color-content-text-1);

View File

@@ -130,10 +130,10 @@
.tab-title-wrapper {
display: flex;
align-items: stretch;
align-items: center;
.tab-title-icon {
font-size: 16px;
font-size: 18px;
margin-right: 6px;
}
}
@@ -196,7 +196,7 @@
}
.arco-tabs-tab-title {
padding: 11px 18px;
padding: 11px 18px 11px 14px;
background: var(--color-bg-header-tabs);
font-size: 14px;
height: var(--header-height);

View File

@@ -1,10 +1,18 @@
<template>
<div class="terminal-panel-container">
<!-- 终端 tab -->
<a-tabs v-model:active-key="panel.active"
:editable="true"
:auto-switch="true"
:show-add-button="true"
@add="openHostModal"
@tab-click="k => panel.clickTab(k as string)"
@delete="k => panel.deleteTab(k as string)">
<!-- 右侧按钮 -->
<template #extra>
<a-button>Action</a-button>
</template>
<!-- 终端面板 -->
<a-tab-pane v-for="tab in panel.items"
:key="tab.key">
<!-- 标题 -->
@@ -20,6 +28,9 @@
<terminal-view :tab="tab" />
</a-tab-pane>
</a-tabs>
<!-- 新建连接模态框 -->
<host-list-modal ref="hostModal"
@choose="item => openTerminal(item, index)" />
</div>
</template>
@@ -31,13 +42,27 @@
<script lang="ts" setup>
import type { ITerminalTabManager } from '../../types/terminal.type';
import TerminalView from '@/views/host/terminal/components/xterm/terminal-view.vue';
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<{
panel: ITerminalTabManager
index: number,
panel: ITerminalTabManager,
}>();
const { openTerminal } = useTerminalStore();
const hostModal = ref();
// 打开主机模态框
const openHostModal = () => {
hostModal.value.open();
};
// FIXME 全部关闭展示新增
onMounted(openHostModal);
</script>
@@ -49,10 +74,10 @@
.tab-title-wrapper {
display: flex;
align-items: stretch;
align-items: center;
.tab-title-icon {
font-size: 16px;
font-size: 18px;
margin-right: 6px;
}
}
@@ -89,6 +114,14 @@
display: none;
}
&-add-btn {
padding: 0 14px;
.arco-icon-hover {
font-size: 14px;
}
}
&-button .arco-icon-hover:hover {
color: var(--color-panel-text-2);
@@ -96,6 +129,7 @@
background: var(--color-bg-panel-icon-1);
}
}
}
:deep(.arco-tabs-nav-type-line .arco-tabs-tab:hover .arco-tabs-tab-title::before) {
@@ -132,7 +166,7 @@
}
.arco-tabs-tab-title {
padding: 11px 18px;
padding: 11px 18px 11px 14px;
background: var(--color-bg-panel-tabs);
font-size: 14px;
height: var(--panel-nav-height);

View File

@@ -3,6 +3,7 @@
<!-- 面板 -->
<terminal-panel v-for="panelIndex in panelManager.panels.length"
:key="panelIndex"
:index="panelIndex - 1"
:panel="panelManager.panels[panelIndex - 1]" />
</div>
</template>

View File

@@ -0,0 +1,153 @@
<template>
<a-modal v-model:visible="visible"
title-align="start"
:top="128"
:align-center="false"
:draggable="true"
:unmount-on-close="true"
:body-style="{ padding: 0 }"
:hide-title="true"
:footer="false">
<div class="host-list-wrapper">
<!-- 主机搜索框 -->
<div class="host-filter-wrapper">
<span class="host-label">主机列表</span>
<a-input v-model="filterValue"
class="host-filter"
placeholder="别名/名称/编码/IP" />
</div>
<!-- 主机列表 -->
<a-list class="host-list"
max-height="50vh"
:hoverable="true"
:split="false"
:bordered="false"
:data="hostList">
<template #item="{ item }">
<a-list-item class="host-item-wrapper" @click="clickHost(item)">
<div class="host-item">
<!-- 图标 -->
<span class="host-item-icon">
<icon-desktop />
</span>
<!-- 名称 -->
<span class="host-item-name">
{{ `${item.alias || item.name} (${item.address})` }}
</span>
</div>
</a-list-item>
</template>
</a-list>
</div>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'hostListModal'
};
</script>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useTerminalStore } from '@/store';
import useVisible from '@/hooks/visible';
import { HostQueryResponse } from '@/api/asset/host';
const emits = defineEmits(['choose']);
const { hosts } = useTerminalStore();
const { visible, setVisible } = useVisible();
const filterValue = ref('');
const hostList = computed(() => {
const filterVal = filterValue.value.toLowerCase();
let list;
// 过滤
if (filterVal) {
list = hosts.hostList.filter(item => {
return (item.name as string)?.toLowerCase().indexOf(filterVal) > -1
|| (item.code as string)?.toLowerCase().indexOf(filterVal) > -1
|| (item.alias as string)?.toLowerCase().indexOf(filterVal) > -1
|| (item.address as string)?.toLowerCase().indexOf(filterVal) > -1;
});
} else {
list = hosts.hostList;
}
// 排序
return list.sort((o1, o2) => {
if (o1.favorite || o2.favorite) {
if (o1.favorite && o2.favorite) {
return o2.id < o1.id ? 1 : -1;
}
return o2.favorite ? 1 : -1;
} else {
return o2.id < o1.id ? 1 : -1;
}
});
});
// 打开配置
const open = () => {
setVisible(true);
};
defineExpose({ open });
// 打开终端
const clickHost = (item: HostQueryResponse) => {
emits('choose', item);
setVisible(false);
};
</script>
<style lang="less" scoped>
.host-list-wrapper {
padding: 12px;
}
.host-filter-wrapper {
height: 32px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
.host-label {
margin-left: 8px;
user-select: none;
font-size: 18px;
font-weight: 600;
letter-spacing: 2px;
}
.host-filter {
width: 60%;
}
}
.host-item-wrapper {
padding: 12px 16px !important;
border-radius: 4px;
&:hover {
background: var(--color-fill-2);
}
}
.host-item {
display: flex;
align-items: center;
cursor: pointer;
&-icon {
font-size: 20px;
margin-right: 12px;
}
&-text {
font-size: 14px;
}
}
</style>

View File

@@ -1,30 +1,30 @@
<template>
<div class="hosts-list-container">
<a-list size="large"
max-height="100%"
:hoverable="true"
:data="hostList">
<!-- 空数据 -->
<template #empty>
<a-empty>
<template #image>
<icon-desktop />
</template>
{{ emptyValue }}
</a-empty>
</template>
<!-- 数据 -->
<template #item="{ item }">
<a-list-item class="host-item-wrapper">
<div class="host-item">
<!-- 左侧图标-名称 -->
<div class="flex-center host-item-left">
<!-- 图标 -->
<span class="host-item-left-icon" @click="openTerminal(item)">
<a-list class="hosts-list-container"
size="large"
max-height="100%"
:hoverable="true"
:data="hostList">
<!-- 空数据 -->
<template #empty>
<a-empty>
<template #image>
<icon-desktop />
</template>
{{ emptyValue }}
</a-empty>
</template>
<!-- 数据 -->
<template #item="{ item }">
<a-list-item class="host-item-wrapper">
<div class="host-item">
<!-- 左侧图标-名称 -->
<div class="flex-center host-item-left">
<!-- 图标 -->
<span class="host-item-left-icon" @click="openTerminal(item)">
<icon-desktop />
</span>
<!-- 名称 -->
<span class="host-item-left-name">
<!-- 名称 -->
<span class="host-item-left-name">
<!-- 名称文本 -->
<template v-if="!item.editable">
<!-- 文本 -->
@@ -53,7 +53,7 @@
@click="clickEditAlias(item)" />
</a-tooltip>
</template>
<!-- 名称输入框 -->
<!-- 名称输入框 -->
<template v-else>
<a-input v-model="item.alias"
ref="aliasNameInput"
@@ -77,87 +77,86 @@
</a-input>
</template>
</span>
</div>
<!-- 中间ip -->
<div class="flex-center host-item-center">
<!-- ip -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.address">
<span class="host-item-text host-item-center-address">
{{ item.address }}
</span>
</a-tooltip>
</div>
<!-- 右侧tag-操作 -->
<div class="flex-center host-item-right">
<!-- tags -->
<div class="host-item-right-tags">
<template v-if="item.tags?.length">
<a-tag v-for="(tag, i) in item.tags"
class="host-item-text"
:key="tag.id"
:style="{
maxWidth: `calc(${100 / item.tags.length}% - ${i !== item.tags.length - 1 ? '8px' : '0px'})`,
marginRight: `${i !== item.tags.length - 1 ? '8px' : '0'}`,
}"
:color="dataColor(tag.name, tagColor)">
{{ tag.name }}
</a-tag>
</template>
</div>
<!-- 中间ip -->
<div class="flex-center host-item-center">
<!-- ip -->
<!-- 操作 -->
<div class="host-item-right-actions">
<!-- 连接主机 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.address">
<span class="host-item-text host-item-center-address">
{{ item.address }}
</span>
content="连接主机">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openTerminal(item)">
<icon-thunderbolt />
</div>
</div>
</a-tooltip>
<!-- 连接设置 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="连接设置">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openSetting(item)">
<icon-settings />
</div>
</div>
</a-tooltip>
<!-- 收藏 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="收藏">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="setFavorite(item)">
<icon-star-fill class="favorite" v-if="item.favorite" />
<icon-star v-else />
</div>
</div>
</a-tooltip>
</div>
<!-- 右侧tag-操作 -->
<div class="flex-center host-item-right">
<!-- tags -->
<div class="host-item-right-tags">
<template v-if="item.tags?.length">
<a-tag v-for="(tag, i) in item.tags"
class="host-item-text"
:key="tag.id"
:style="{
maxWidth: `calc(${100 / item.tags.length}% - ${i !== item.tags.length - 1 ? '8px' : '0px'})`,
marginRight: `${i !== item.tags.length - 1 ? '8px' : '0'}`,
}"
:color="dataColor(tag.name, tagColor)">
{{ tag.name }}
</a-tag>
</template>
</div>
<!-- 操作 -->
<div class="host-item-right-actions">
<!-- 连接主机 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="连接主机">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openTerminal(item)">
<icon-thunderbolt />
</div>
</div>
</a-tooltip>
<!-- 连接设置 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="连接设置">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openSetting(item)">
<icon-settings />
</div>
</div>
</a-tooltip>
<!-- 收藏 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="收藏">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="setFavorite(item)">
<icon-star-fill class="favorite" v-if="item.favorite" />
<icon-star v-else />
</div>
</div>
</a-tooltip>
</div>
</div>
</div>
</a-list-item>
</template>
</a-list>
</div>
</div>
</a-list-item>
</template>
</a-list>
</template>
<script lang="ts">
@@ -234,24 +233,32 @@
<style lang="less" scoped>
@host-item-height: 56px;
:deep(.arco-list-bordered) {
border: 1px solid var(--color-fill-3);
.hosts-list-container {
height: 100%;
}
.arco-empty {
padding: 16px 0;
flex-direction: column;
:deep(.arco-scrollbar) {
height: 100%;
.arco-empty-image {
margin-bottom: 0;
.arco-list-bordered {
border: 1px solid var(--color-fill-3);
.arco-empty {
padding: 16px 0;
flex-direction: column;
.arco-empty-image {
margin-bottom: 0;
}
}
}
.arco-list-item:not(:last-child) {
border-bottom: 1px solid var(--color-fill-3);
}
.arco-list-item:not(:last-child) {
border-bottom: 1px solid var(--color-fill-3);
}
.arco-list-item:hover {
background-color: var(--color-fill-2);
.arco-list-item:hover {
background-color: var(--color-fill-2);
}
}
}

View File

@@ -1,7 +1,7 @@
import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type';
// 终端 tab 管理器实现
export default class TerminalTabManagerm<T extends TerminalTabItem = TerminalTabItem> implements ITerminalTabManager<T> {
export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabItem> implements ITerminalTabManager<T> {
public active: string;