feat: 主机分组显示

This commit is contained in:
lijiahang
2023-12-14 19:17:25 +08:00
parent 305bdfc008
commit 49697b2c87
9 changed files with 232 additions and 57 deletions

View File

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

View File

@@ -213,7 +213,7 @@ body {
::-webkit-scrollbar {
-webkit-appearance: none;
width: 6px;
height: 4px;
height: 6px;
}
::-webkit-scrollbar-track {

View File

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

View File

@@ -192,7 +192,6 @@
position: relative;
width: 100%;
height: calc(100% - 44px);
overflow: auto;
}
}

View File

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

View File

@@ -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 ? '点击退出全屏模式' : '点击切换全屏模式',

View File

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

View File

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

View File

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