feat: 主机分组显示
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
|
||||
// 垂直滚动
|
||||
.arco-scrollbar-track-direction-vertical {
|
||||
width: 9px;
|
||||
width: 6px;
|
||||
|
||||
.arco-scrollbar-thumb-bar {
|
||||
margin: 0;
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
// 水平滚动
|
||||
.arco-scrollbar-track-direction-horizontal {
|
||||
height: 9px;
|
||||
height: 6px;
|
||||
|
||||
.arco-scrollbar-thumb-bar {
|
||||
margin: 0;
|
||||
@@ -81,31 +81,42 @@
|
||||
|
||||
// 块状树
|
||||
.block-tree {
|
||||
@tree-node-hover-color: var(--color-fill-1);
|
||||
@tree-node-selected-color: var(--color-fill-2);
|
||||
@tree-node-selected-hover-color: var(--color-fill-2);
|
||||
|
||||
.arco-tree-node {
|
||||
cursor: unset;
|
||||
margin-bottom: 2px;
|
||||
|
||||
&-switcher {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-fill-1);
|
||||
background-color: @tree-node-hover-color;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
background-color: var(--color-fill-2);
|
||||
background-color: @tree-node-selected-color;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-fill-1);
|
||||
background-color: @tree-node-selected-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-selected .arco-tree-node-title:hover {
|
||||
background-color: unset;
|
||||
//background-color: @tree-node-selected-hover-color;
|
||||
}
|
||||
|
||||
&-title {
|
||||
padding: 0;
|
||||
padding: 0 16px 0 0;
|
||||
height: 32px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-fill-1);
|
||||
background-color: unset;
|
||||
//background-color: @tree-node-hover-color;
|
||||
}
|
||||
|
||||
&-text {
|
||||
|
||||
@@ -213,7 +213,7 @@ body {
|
||||
::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 6px;
|
||||
height: 4px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-list-item :title="`${item.name}(${item.code}) - ${ item.address}`">
|
||||
<a-list-item :title="`${item.name}(${item.code}) - ${item.address}`">
|
||||
<icon-desktop class="host-list-icon" />
|
||||
<span>{{ `${item.name}(${item.code}) - ` }}</span>
|
||||
<span class="span-blue">{{ item.address }}</span>
|
||||
|
||||
@@ -192,7 +192,6 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 44px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 亮色主题配色常量
|
||||
body {
|
||||
--color-bg-header: #232323;
|
||||
--color-bg-sidebar: #F2F3F5;
|
||||
--color-bg-sidebar: #F2F3F4;
|
||||
--color-bg-content: #FEFEFE;
|
||||
--color-sidebar-icon: #737070;
|
||||
--color-sidebar-icon-bg: #D7D8DB;
|
||||
@@ -16,7 +16,7 @@ body {
|
||||
body[terminal-theme='dark'] {
|
||||
--color-bg-header: #232323;
|
||||
--color-bg-sidebar: #2C2E31;
|
||||
--color-bg-content: #1A1B1F;
|
||||
--color-bg-content: #1A1B1C;
|
||||
--color-sidebar-icon: #C3C8CE;
|
||||
--color-sidebar-icon-bg: #43444C;
|
||||
--color-sidebar-tooltip-text: rgba(255, 255, 255, .9);
|
||||
@@ -213,6 +213,7 @@ body[terminal-theme='dark'] .host-layout {
|
||||
|
||||
.terminal-setting-wrapper {
|
||||
min-width: 932px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal-setting-title {
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { computed } from 'vue';
|
||||
import IconActions from '../layout/icon-actions.vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -67,24 +68,25 @@
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'clickTab', 'deleteTab', 'split', 'share']);
|
||||
const emits = defineEmits(['update:modelValue', 'clickTab', 'deleteTab', 'share']);
|
||||
|
||||
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
|
||||
const terminalStore = useTerminalStore();
|
||||
|
||||
// 顶部操作
|
||||
const actions = computed<Array<SidebarAction>>(() => [
|
||||
{
|
||||
icon: 'icon-interaction',
|
||||
content: '分屏',
|
||||
visible: false,
|
||||
click: () => emits('split')
|
||||
},
|
||||
{
|
||||
icon: 'icon-share-alt',
|
||||
content: '分享链接',
|
||||
visible: false,
|
||||
click: () => emits('share')
|
||||
},
|
||||
{
|
||||
// FIXME 持久化
|
||||
icon: terminalStore.isDarkTheme ? 'icon-sun-fill' : 'icon-moon-fill',
|
||||
content: terminalStore.isDarkTheme ? '点击切换为亮色模式' : '点击切换为暗色模式',
|
||||
click: () => terminalStore.changeDarkTheme(!terminalStore.isDarkTheme)
|
||||
},
|
||||
{
|
||||
icon: isFullscreen.value ? 'icon-fullscreen-exit' : 'icon-fullscreen',
|
||||
content: isFullscreen.value ? '点击退出全屏模式' : '点击切换全屏模式',
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="group-view-container">
|
||||
<!-- 主机分组 -->
|
||||
<a-scrollbar>
|
||||
<a-tree v-model:selected-keys="selectedGroup"
|
||||
:data="hosts.groupTree"
|
||||
:blockNode="true"
|
||||
class="host-tree block-tree"
|
||||
@select="chooseGroup">
|
||||
<!-- 标题 -->
|
||||
<template #extra="node">
|
||||
<span class="node-host-count span-blue">{{ hosts?.treeNodes[node.key]?.length || 0 }}</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-scrollbar>
|
||||
<!-- 主机列表 -->
|
||||
<div class="host-list">
|
||||
<a-list size="large"
|
||||
max-height="100%"
|
||||
:hoverable="true"
|
||||
:data="[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]">
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<span class="host-list-empty">当前分组未配置主机</span>
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-list-item :title="`${item.name}(${item.code}) - ${item.address}`">
|
||||
{{ hosts?.treeNodes[selectedGroup[0]] }}
|
||||
<icon-desktop class="host-list-icon" />
|
||||
<span>{{ `${item.name}(${item.code}) - ` }}</span>
|
||||
<span class="span-blue">{{ item.address }}</span>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'hostGroupView'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
hosts: AuthorizedHostQueryResponse
|
||||
}>();
|
||||
|
||||
const selectedGroup = ref([0]);
|
||||
|
||||
const chooseGroup = () => {
|
||||
console.log(selectedGroup.value[0]);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@tree-width: 298px;
|
||||
@tree-gap: 32px;
|
||||
|
||||
.group-view-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.host-tree {
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
|
||||
.node-host-count {
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.host-list {
|
||||
width: calc(100% - @tree-width - @tree-gap);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-scrollbar) {
|
||||
width: @tree-width;
|
||||
margin-right: @tree-gap;
|
||||
border-radius: 4px;
|
||||
|
||||
&-container {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -5,16 +5,20 @@
|
||||
<h2 class="terminal-setting-title">新建连接</h2>
|
||||
<!-- 操作栏 -->
|
||||
<div class="terminal-setting-block header-actions">
|
||||
<a-radio-group type="button">
|
||||
<a-radio value="1">分组</a-radio>
|
||||
<a-radio value="2">列表</a-radio>
|
||||
<a-radio value="3">最近连接</a-radio>
|
||||
</a-radio-group>
|
||||
<a-input-search class="host-filter"
|
||||
placeholder="输入名称/编码/IP 进行过滤" />
|
||||
<!-- 视图类型 -->
|
||||
<a-radio-group v-model="newConnectionType"
|
||||
type="button"
|
||||
class="usn"
|
||||
:options="toOptions(NewConnectionTypeKey)"
|
||||
@change="changeConnectionType" />
|
||||
<!-- 过滤 -->
|
||||
<a-input-search v-model="filterValue"
|
||||
class="host-filter"
|
||||
placeholder="输入名称/编码/IP @标签"
|
||||
:allow-clear="true" />
|
||||
</div>
|
||||
<!-- 授权主机 -->
|
||||
<div class="terminal-setting-block">
|
||||
<div class="terminal-setting-block" style="margin: 0;">
|
||||
<!-- 顶部 -->
|
||||
<div class="terminal-setting-subtitle-wrapper">
|
||||
<h3 class="terminal-setting-subtitle">
|
||||
@@ -22,17 +26,33 @@
|
||||
</h3>
|
||||
</div>
|
||||
<!-- 内容区域 -->
|
||||
<div class="terminal-setting-body hosts-container">
|
||||
<div class="host-tree">
|
||||
<a-tree :data="hosts.groupTree"
|
||||
:blockNode="true"
|
||||
>
|
||||
<div class="terminal-setting-body body-container">
|
||||
<!-- 加载中 -->
|
||||
<a-skeleton v-if="loading"
|
||||
class="hosts-skeleton"
|
||||
:animation="true">
|
||||
<a-skeleton-line :rows="6"
|
||||
:line-height="40"
|
||||
:line-spacing="20" />
|
||||
</a-skeleton>
|
||||
<!-- 无数据 -->
|
||||
<a-empty v-else-if="!hosts.hostList?.length">
|
||||
<template #image>
|
||||
<icon-desktop />
|
||||
</template>
|
||||
Oops! 无授权主机 请联系管理员授权后重试!
|
||||
</a-empty>
|
||||
<!-- 主机列表 -->
|
||||
<div v-else class="host-view-container">
|
||||
<!-- 分组视图列表 -->
|
||||
<host-group-view v-if="NewConnectionType.GROUP === newConnectionType"
|
||||
:hosts="hosts" />
|
||||
<!-- 列表视图 -->
|
||||
|
||||
<!-- 我的收藏 -->
|
||||
|
||||
<!-- 最近连接 -->
|
||||
|
||||
</a-tree>
|
||||
{{ }}
|
||||
</div>
|
||||
<div class="host-list">
|
||||
{{ hosts.treeNodes }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,13 +69,33 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { NewConnectionType, NewConnectionTypeKey } from '../../types/terminal.const';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import HostGroupView from './host-group-view.vue';
|
||||
import { useDictStore } from '@/store';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { toOptions } = useDictStore();
|
||||
|
||||
const newConnectionType = ref(NewConnectionType.GROUP);
|
||||
const filterValue = ref();
|
||||
const hosts = ref<AuthorizedHostQueryResponse>({} as AuthorizedHostQueryResponse);
|
||||
|
||||
onMounted(async () => {
|
||||
const { data } = await getCurrentAuthorizedHost();
|
||||
hosts.value = data;
|
||||
// 修改连接类型
|
||||
const changeConnectionType = () => {
|
||||
// FIXME 持久化
|
||||
};
|
||||
|
||||
// 加载主机信息
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await getCurrentAuthorizedHost();
|
||||
hosts.value = data;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -66,20 +106,21 @@
|
||||
justify-content: space-between;
|
||||
|
||||
.host-filter {
|
||||
width: 40%;
|
||||
width: 36%;
|
||||
}
|
||||
}
|
||||
|
||||
.hosts-container {
|
||||
.body-container {
|
||||
justify-content: space-between;
|
||||
|
||||
.host-tree {
|
||||
margin-right: 16px;
|
||||
width: 350px;
|
||||
.hosts-skeleton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.host-list {
|
||||
width: 490px;
|
||||
.host-view-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 240px);
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ export interface SidebarAction {
|
||||
click: () => void;
|
||||
}
|
||||
|
||||
// tab 元素
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
title: string;
|
||||
type: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// tab 类型
|
||||
export const TabType = {
|
||||
SETTING: 'setting',
|
||||
@@ -34,14 +43,13 @@ export const InnerTabs = {
|
||||
},
|
||||
};
|
||||
|
||||
// tab 元素
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
title: string;
|
||||
type: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
// 新建连接类型
|
||||
export const NewConnectionType = {
|
||||
GROUP: 'group',
|
||||
LIST: 'list',
|
||||
FAVORITE: 'favorite',
|
||||
LATEST: 'latest'
|
||||
};
|
||||
|
||||
// 字体后缀 兜底
|
||||
export const fontFamilySuffix = ',courier-new, courier, monospace';
|
||||
@@ -61,5 +69,12 @@ export const fontWeightKey = 'terminalFontWeight';
|
||||
// 终端光标样式
|
||||
export const cursorStyleKey = 'terminalCursorStyle';
|
||||
|
||||
// 终端新建连接类型
|
||||
export const NewConnectionTypeKey = 'terminalNewConnectionType';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [darkThemeKey, fontFamilyKey, fontSizeKey, fontWeightKey, cursorStyleKey];
|
||||
export const dictKeys = [
|
||||
darkThemeKey, fontFamilyKey,
|
||||
fontSizeKey, fontWeightKey,
|
||||
cursorStyleKey, NewConnectionTypeKey
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user