fix: 轻量版终端固定omc本机

This commit is contained in:
TsMask
2025-03-17 15:32:00 +08:00
parent c184357b73
commit 221c39995f
3 changed files with 391 additions and 301 deletions

View File

@@ -0,0 +1,339 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/es';
import { useFullscreen } from '@vueuse/core';
import { defineAsyncComponent, reactive, ref } from 'vue';
import { parseDuration } from '@/utils/date-utils';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const TerminalSSH = defineAsyncComponent(
() => import('@/components/TerminalSSH/index.vue')
);
const TerminalTelnet = defineAsyncComponent(
() => import('@/components/TerminalTelnet/index.vue')
);
const TerminalRedis = defineAsyncComponent(
() => import('@/components/TerminalRedis/index.vue')
);
const HostList = defineAsyncComponent(
() => import('./components/hostList.vue')
);
// 全屏
const terminalCard = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(terminalCard);
/**主机对象信息状态类型 */
type HostStateType = {
/**显示主机列表 */
show: boolean;
/**加载等待 */
loading: boolean;
/**查询参数 */
params: {
pageNum: number;
pageSize: number;
};
/**数据总数 */
total: number;
data: Record<string, any>[];
};
/**主机对象信息状态 */
const hostState: HostStateType = reactive({
show: false,
loading: false,
params: {
pageNum: 1,
pageSize: 20,
},
total: 0,
data: [],
});
/**连接主机 */
function fnConnectHost(data: Record<string, any>) {
const id = `${Date.now()}`;
tabState.panes.push({
id,
status: false,
host: data,
});
tabState.activeKey = id;
}
/**标签对象信息状态类型 */
type TabStateType = {
/**激活选中 */
activeKey: string;
/**页签数据 */
panes: {
id: string;
status: boolean;
host: Record<string, any>;
connectStamp?: string;
}[];
};
/**标签对象信息状态 */
const tabState: TabStateType = reactive({
activeKey: '0',
panes: [
{
id: '0',
host: {
id: 0,
title: t('views.tool.terminal.start'),
type: '0',
},
status: true,
},
],
});
/**
* 终端连接状态
* @param data 主机连接结果
*/
function fnTerminalConnect(data: Record<string, any>) {
const { id, timeStamp } = data;
const seconds = timeStamp / 1000;
// 获取当前项下标
const tab = tabState.panes.find(item => item.id === id);
if (tab) {
tab.status = true;
tab.connectStamp = parseDuration(seconds);
}
}
/**
* 终端关闭状态
* @param data 主机连接结果
*/
function fnTerminalClose(data: Record<string, any>) {
const { id } = data;
// 获取当前项下标
const tab = tabState.panes.find(item => item.id === id);
if (tab) {
tab.status = false;
}
}
/**
* 标签更多菜单项
* @param key 菜单key
*/
function fnTabMenu(key: string | number) {
// 刷新当前
if (key === 'reload') {
const tabIndex = tabState.panes.findIndex(
item => item.id === tabState.activeKey
);
if (tabIndex) {
const tab = tabState.panes[tabIndex];
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.reloadTip', {
num: `${tab.host.hostType} - ${tab.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
tab.host && fnConnectHost(tab.host);
},
});
}
}
// 关闭当前
if (key === 'current') {
fnTabClose(tabState.activeKey);
}
// 关闭其他
if (key === 'other') {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.otherTip'),
onOk() {
hostState.show = false;
tabState.panes = tabState.panes.filter(
tab => tab.id === '0' || tab.id === tabState.activeKey
);
tabState.activeKey = tabState.activeKey;
},
});
}
// 关闭全部
if (key === 'all') {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.allTip'),
onOk() {
hostState.show = false;
tabState.panes.splice(1);
tabState.activeKey = '0';
},
});
}
}
/**
* 导航标签关闭
* @param id 标签的key
*/
function fnTabClose(id: string) {
if (isFullscreen.value) toggle();
// 获取当前项下标
const tabIndex = tabState.panes.findIndex(tab => tab.id === id);
if (tabIndex === -1) return;
const item = tabState.panes[tabIndex];
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.closeTip', {
num: `${item.host.hostType.toUpperCase()} - ${item.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
// 激活前一项标签
tabState.activeKey = tabState.panes[tabIndex - 1].id;
},
});
}
</script>
<template>
<PageContainer>
<a-card
:bordered="false"
size="small"
:body-style="{ padding: '12px' }"
ref="terminalCard"
>
<a-tabs
class="terminal-tabs"
hide-add
size="small"
tab-position="top"
type="editable-card"
:tab-bar-gutter="8"
:tab-bar-style="{ margin: '0' }"
v-model:activeKey="tabState.activeKey"
@edit="(id:any) => fnTabClose(id)"
>
<a-tab-pane
v-for="pane in tabState.panes"
:key="pane.id"
:closable="pane.id !== '0'"
>
<template #tab>
<a-badge
:status="pane.status ? 'success' : 'error'"
:text="pane.host.title"
/>
</template>
<div class="pane-box">
<!-- 非开始页的ssh主机 -->
<TerminalSSH
v-if="pane.id !== '0' && pane.host.hostType === 'ssh'"
:id="pane.id"
:hostId="pane.host.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalSSH>
<!-- 非开始页的telnet主机 -->
<TerminalTelnet
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
:id="pane.id"
:hostId="pane.host.id"
init-cmd="help"
:disable="true"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalTelnet>
<!-- 非开始页的redis主机 -->
<TerminalRedis
v-if="pane.id !== '0' && pane.host.hostType === 'redis'"
:id="pane.id"
:hostId="pane.host.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalRedis>
<!-- 开始页 -->
<div v-if="pane.id === '0'">
<!-- 主机列表 -->
<HostList
v-show="tabState.activeKey === '0'"
@modal="() => (isFullscreen ? toggle() : null)"
@link="fnConnectHost"
></HostList>
</div>
</div>
</a-tab-pane>
<template #rightExtra>
<a-space :size="8" align="center">
<a-tooltip placement="topRight">
<template #title>
{{ t('loayouts.rightContent.fullscreen') }}
</template>
<a-button
type="default"
shape="circle"
size="small"
style="color: inherit"
@click="toggle"
>
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
</a-button>
</a-tooltip>
<!-- 非开始页的更多操作 -->
<div v-show="tabState.activeKey !== '0'">
<a-tooltip placement="topRight">
<template #title>
{{ t('views.tool.terminal.more') }}
</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="ghost" shape="circle" size="small">
<template #icon><EllipsisOutlined /></template>
</a-button>
<template #overlay>
<a-menu @click="({ key }:any) => fnTabMenu(key)">
<a-menu-item key="reload">
{{ t('views.tool.terminal.reload') }}
</a-menu-item>
<a-menu-item key="current">
{{ t('views.tool.terminal.current') }}
</a-menu-item>
<a-menu-item key="other">
{{ t('views.tool.terminal.other') }}
</a-menu-item>
<a-menu-item key="all">
{{ t('views.tool.terminal.all') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</div>
</a-space>
</template>
</a-tabs>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.pane-box {
height: calc(100vh - 200px);
overflow-x: hidden;
}
</style>

View File

@@ -1,203 +1,76 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/es';
import { useFullscreen } from '@vueuse/core';
import { defineAsyncComponent, reactive, ref } from 'vue';
import { parseDuration } from '@/utils/date-utils';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
import { listNeHost } from '@/api/ne/neHost';
import { defineAsyncComponent, onMounted, reactive } from 'vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const TerminalSSH = defineAsyncComponent(
() => import('@/components/TerminalSSH/index.vue')
);
const TerminalTelnet = defineAsyncComponent(
() => import('@/components/TerminalTelnet/index.vue')
);
const TerminalRedis = defineAsyncComponent(
() => import('@/components/TerminalRedis/index.vue')
);
const HostList = defineAsyncComponent(
() => import('./components/hostList.vue')
);
// 全屏
const terminalCard = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(terminalCard);
/**主机对象信息状态类型 */
type HostStateType = {
/**显示主机列表 */
show: boolean;
/**加载等待 */
loading: boolean;
/**查询参数 */
params: {
pageNum: number;
pageSize: number;
/**主机类型 */
hostType: 'ssh';
/**分组 */
groupId: '1';
/**名称 */
title: 'OMC';
/**当前页数 */
pageNum: 1;
/**每页条数 */
pageSize: 10;
sortField: 'createTime';
sortOrder: 'desc';
};
/**数据总数 */
total: number;
data: Record<string, any>[];
/**OMC主机 */
host: Record<string, any>;
};
/**主机对象信息状态 */
const hostState: HostStateType = reactive({
show: false,
loading: false,
params: {
/**主机类型 */
hostType: 'ssh',
/**分组 */
groupId: '1',
/**名称 */
title: 'OMC',
/**当前页数 */
pageNum: 1,
pageSize: 20,
/**每页条数 */
pageSize: 10,
sortField: 'createTime',
sortOrder: 'desc',
},
total: 0,
data: [],
host: {},
});
/**连接主机 */
function fnConnectHost(data: Record<string, any>) {
const id = `${Date.now()}`;
tabState.panes.push({
id,
status: false,
host: data,
/**查询信息列表, pageNum初始页数 */
function fnGetList() {
hostState.loading = true;
listNeHost(hostState.params)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const { total, rows } = res.data;
if (total > 0) {
hostState.host = rows[0];
}
}
})
.finally(() => {
hostState.loading = false;
});
tabState.activeKey = id;
}
/**标签对象信息状态类型 */
type TabStateType = {
/**激活选中 */
activeKey: string;
/**页签数据 */
panes: {
id: string;
status: boolean;
host: Record<string, any>;
connectStamp?: string;
}[];
};
/**标签对象信息状态 */
const tabState: TabStateType = reactive({
activeKey: '0',
panes: [
{
id: '0',
host: {
id: 0,
title: t('views.tool.terminal.start'),
type: '0',
},
status: true,
},
],
onMounted(() => {
// 获取列表数据
fnGetList();
});
/**
* 终端连接状态
* @param data 主机连接结果
*/
function fnTerminalConnect(data: Record<string, any>) {
const { id, timeStamp } = data;
const seconds = timeStamp / 1000;
// 获取当前项下标
const tab = tabState.panes.find(item => item.id === id);
if (tab) {
tab.status = true;
tab.connectStamp = parseDuration(seconds);
}
}
/**
* 终端关闭状态
* @param data 主机连接结果
*/
function fnTerminalClose(data: Record<string, any>) {
const { id } = data;
// 获取当前项下标
const tab = tabState.panes.find(item => item.id === id);
if (tab) {
tab.status = false;
}
}
/**
* 标签更多菜单项
* @param key 菜单key
*/
function fnTabMenu(key: string | number) {
// 刷新当前
if (key === 'reload') {
const tabIndex = tabState.panes.findIndex(
item => item.id === tabState.activeKey
);
if (tabIndex) {
const tab = tabState.panes[tabIndex];
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.reloadTip', {
num: `${tab.host.hostType} - ${tab.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
tab.host && fnConnectHost(tab.host);
},
});
}
}
// 关闭当前
if (key === 'current') {
fnTabClose(tabState.activeKey);
}
// 关闭其他
if (key === 'other') {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.otherTip'),
onOk() {
hostState.show = false;
tabState.panes = tabState.panes.filter(
tab => tab.id === '0' || tab.id === tabState.activeKey
);
tabState.activeKey = tabState.activeKey;
},
});
}
// 关闭全部
if (key === 'all') {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.allTip'),
onOk() {
hostState.show = false;
tabState.panes.splice(1);
tabState.activeKey = '0';
},
});
}
}
/**
* 导航标签关闭
* @param id 标签的key
*/
function fnTabClose(id: string) {
if (isFullscreen.value) toggle();
// 获取当前项下标
const tabIndex = tabState.panes.findIndex(tab => tab.id === id);
if (tabIndex === -1) return;
const item = tabState.panes[tabIndex];
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.tool.terminal.closeTip', {
num: `${item.host.hostType.toUpperCase()} - ${item.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
// 激活前一项标签
tabState.activeKey = tabState.panes[tabIndex - 1].id;
},
});
}
</script>
<template>
@@ -205,135 +78,13 @@ function fnTabClose(id: string) {
<a-card
:bordered="false"
size="small"
:body-style="{ padding: '12px' }"
ref="terminalCard"
>
<a-tabs
class="terminal-tabs"
hide-add
size="small"
tab-position="top"
type="editable-card"
:tab-bar-gutter="8"
:tab-bar-style="{ margin: '0' }"
v-model:activeKey="tabState.activeKey"
@edit="(id:any) => fnTabClose(id)"
>
<a-tab-pane
v-for="pane in tabState.panes"
:key="pane.id"
:closable="pane.id !== '0'"
>
<template #tab>
<a-badge
:status="pane.status ? 'success' : 'error'"
:text="pane.host.title"
/>
</template>
<div class="pane-box">
<!-- 非开始页的ssh主机 -->
<TerminalSSH
v-if="pane.id !== '0' && pane.host.hostType === 'ssh'"
:id="pane.id"
:hostId="pane.host.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
:body-style="{ padding: '12px', height: 'calc(100vh - 200px)' }"
:loading="hostState.loading"
>
<TerminalSSH :id="hostState.host.title" :hostId="hostState.host.id">
</TerminalSSH>
<!-- 非开始页的telnet主机 -->
<TerminalTelnet
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
:id="pane.id"
:hostId="pane.host.id"
init-cmd="help"
:disable="true"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalTelnet>
<!-- 非开始页的redis主机 -->
<TerminalRedis
v-if="pane.id !== '0' && pane.host.hostType === 'redis'"
:id="pane.id"
:hostId="pane.host.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalRedis>
<!-- 开始页 -->
<div v-if="pane.id === '0'">
<!-- 主机列表 -->
<HostList
v-show="tabState.activeKey === '0'"
@modal="() => (isFullscreen ? toggle() : null)"
@link="fnConnectHost"
></HostList>
</div>
</div>
</a-tab-pane>
<template #rightExtra>
<a-space :size="8" align="center">
<a-tooltip placement="topRight">
<template #title>
{{ t('loayouts.rightContent.fullscreen') }}
</template>
<a-button
type="default"
shape="circle"
size="small"
style="color: inherit"
@click="toggle"
>
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
</a-button>
</a-tooltip>
<!-- 非开始页的更多操作 -->
<div v-show="tabState.activeKey !== '0'">
<a-tooltip placement="topRight">
<template #title>
{{ t('views.tool.terminal.more') }}
</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="ghost" shape="circle" size="small">
<template #icon><EllipsisOutlined /></template>
</a-button>
<template #overlay>
<a-menu @click="({ key }:any) => fnTabMenu(key)">
<a-menu-item key="reload">
{{ t('views.tool.terminal.reload') }}
</a-menu-item>
<a-menu-item key="current">
{{ t('views.tool.terminal.current') }}
</a-menu-item>
<a-menu-item key="other">
{{ t('views.tool.terminal.other') }}
</a-menu-item>
<a-menu-item key="all">
{{ t('views.tool.terminal.all') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</div>
</a-space>
</template>
</a-tabs>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.pane-box {
height: calc(100vh - 200px);
overflow-x: hidden;
}
</style>
<style lang="less" scoped></style>