优化传输列表显示.

This commit is contained in:
lijiahang
2024-05-06 12:50:04 +08:00
parent 69bad68cbc
commit 320c4c272a
13 changed files with 362 additions and 210 deletions

View File

@@ -3,7 +3,7 @@
:width="388"
:footer="false"
@close="onClose">
<!-- 表头 -->
<!-- 标题 -->
<template #title>
<span class="snippet-drawer-title usn">
命令片段
@@ -32,9 +32,7 @@
<a-input-search class="snippet-header-input"
v-model="filterValue"
placeholder="名称/命令"
allow-clear
@search="filterSnippet"
@keyup.enter="filterSnippet" />
allow-clear />
</div>
<!-- 加载中 -->
<a-skeleton v-if="loading"
@@ -73,14 +71,14 @@
<script lang="ts">
export default {
name: 'commandSnippetListDrawer'
name: 'commandSnippetDrawer'
};
</script>
<script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { ref, provide } from 'vue';
import { ref, watch, provide } from 'vue';
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
@@ -143,6 +141,9 @@
});
};
//
watch(filterValue, filterSnippet);
//
const openAdd = () => {
formDrawer.value.openAdd();

View File

@@ -3,7 +3,7 @@
:width="388"
:footer="false"
@close="onClose">
<!-- 表头 -->
<!-- 标题 -->
<template #title>
<span class="path-drawer-title usn">
路径书签
@@ -32,9 +32,7 @@
<a-input-search class="path-header-input"
v-model="filterValue"
placeholder="名称/路径"
allow-clear
@search="filterPath"
@keyup.enter="filterPath" />
allow-clear />
</div>
<!-- 加载中 -->
<a-skeleton v-if="loading"
@@ -73,14 +71,14 @@
<script lang="ts">
export default {
name: 'pathBookmarkListDrawer'
name: 'pathBookmarkDrawer'
};
</script>
<script lang="ts" setup>
import { ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { ref, provide, onMounted } from 'vue';
import { ref, provide, onMounted, watch } from 'vue';
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
@@ -143,6 +141,9 @@
});
};
//
watch(filterValue, filterPath);
//
const openAdd = () => {
formDrawer.value.openAdd();

View File

@@ -14,7 +14,7 @@
</div>
</div>
<!-- 其他颜色 -->
<template v-for="color in toOptions(terminalTabColorKey)">
<template v-for="color in toOptions(tabColorKey)">
<div class="color-wrapper"
:class="[formModel.color === color.value ? 'selected-color' : '']"
:style="{ '--color': `${color.value}` }"
@@ -37,7 +37,7 @@
<script lang="ts" setup>
import type { LabelExtraSettingModel } from '../../../types/terminal.type';
import { onMounted, ref } from 'vue';
import { terminalTabColorKey } from '../../../types/terminal.const';
import { tabColorKey } from '../../../types/terminal.const';
import { getHostExtraItem } from '@/api/asset/host-extra';
import { useDictStore } from '@/store';

View File

@@ -14,7 +14,7 @@
<a-select style="width: 160px;"
v-model="formModel.terminalEmulationType"
size="small"
:options="toOptions(terminalEmulationTypeKey)" />
:options="toOptions(emulationTypeKey)" />
</block-setting-item>
<!-- 缓冲区行数 -->
<block-setting-item label="缓冲区行数" desc="保存在缓冲区的行数, 多出的行数会被忽略, 此值越大占用内存的内存会更多">
@@ -42,7 +42,7 @@
import { ref, watch } from 'vue';
import { useDictStore, useTerminalStore } from '@/store';
import { TerminalPreferenceItem } from '@/store/modules/terminal';
import { terminalEmulationTypeKey } from '../../../types/terminal.const';
import { emulationTypeKey } from '../../../types/terminal.const';
import BlockSettingItem from '../block-setting-item.vue';
const { toOptions } = useDictStore();

View File

@@ -83,8 +83,6 @@
import { useTerminalStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import useVisible from '@/hooks/visible';
import { TransferStatus, TransferType } from '../../types/terminal.const';
import { nextId } from '@/utils';
const { visible, setVisible } = useVisible();
const { transferManager } = useTerminalStore();

View File

@@ -1,11 +1,47 @@
<template>
<a-drawer v-model:visible="visible"
title="文件传输列表"
class="transfer-drawer"
:width="388"
:unmount-on-close="false"
:footer="false">
<a-spin class="full" :loading="loading">
<a-list class="hosts-list-container"
<!-- 标题 -->
<template #title>
<span class="path-drawer-title usn">
文件传输列表
</span>
</template>
<a-spin class="full transfer-container" :loading="loading">
<!-- 头部操作 -->
<div class="transfer-header">
<!-- 左侧按钮 -->
<a-space size="small">
<!-- 清空 -->
<span class="click-icon-wrapper transfer-header-icon"
title="清空"
@click="removeAllTask">
<icon-close />
</span>
</a-space>
<!-- 右侧数量 -->
<a-space size="small">
<a-tag v-for="option in toOptions(transferStatusKey)"
class="pointer"
:color="option.color"
:title="option.label"
:bordered="option.value === filterStatus"
:checked="option.value === filterStatus"
@click="checkFilterStatus(option.value)">
<!-- 图标 -->
<component :is="option.icon" />
<!-- 数量 -->
<span class="status-count">
{{ transferManager.transferList.filter(s => s.status === option.value).length }}
</span>
</a-tag>
</a-space>
</div>
<!-- 文件列表 -->
<a-list class="transfer-item-container"
size="small"
max-height="100%"
:hoverable="true"
@@ -17,88 +53,8 @@
</template>
<!-- 数据 -->
<template #item="{ item }">
<a-list-item class="transfer-item-wrapper">
<div class="transfer-item">
<!-- 左侧图标 -->
<div class="transfer-item-left">
<span class="file-icon">
<icon-upload v-if="item.type === TransferType.UPLOAD" />
<icon-download v-else-if="item.type === TransferType.DOWNLOAD" />
</span>
</div>
<!-- 中间信息 -->
<div class="transfer-item-center">
<!-- 文件名称 -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.name">
<span class="file-name">
{{ item.name }}
</span>
</a-tooltip>
<!-- 传输进度 -->
<span class="transfer-progress">
<!-- 当前大小 -->
<span v-if="item.status === TransferStatus.TRANSFERRING">{{ getFileSize(item.currentSize) }}</span>
<span class="mx4" v-if="item.status === TransferStatus.TRANSFERRING">/</span>
<!-- 总大小 -->
<span>{{ getFileSize(item.totalSize) }}</span>
<!-- 进度百分比 -->
<span class="ml8" v-if="item.status === TransferStatus.TRANSFERRING">
{{ item.progress }}%
</span>
</span>
<!-- 目标目录 -->
<a-tooltip v-if="item.parentPath"
position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.parentPath">
<span class="target-path">
{{ item.parentPath }}
</span>
</a-tooltip>
<!-- 错误信息 -->
<a-tooltip v-if="item.errorMessage"
position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.errorMessage">
<span class="error-message">
{{ item.errorMessage }}
</span>
</a-tooltip>
</div>
<!-- 右侧状态/操作-->
<div class="transfer-item-right">
<!-- 传输状态 -->
<div class="transfer-item-right-progress">
<!-- 等待传输 -->
<icon-clock-circle v-if="item.status === TransferStatus.WAITING" />
<!-- 传输进度 -->
<a-progress v-else
type="circle"
size="mini"
:status="item.status"
:percent="item.currentSize / item.totalSize" />
</div>
<!-- 传输操作 -->
<div class="transfer-item-right-actions">
<!-- 关闭 -->
<span class="close-icon" @click="removeTask(item.fileId)">
<icon-close />
</span>
</div>
</div>
</div>
</a-list-item>
<!-- 传输 item -->
<transfer-item v-show="filterItem(item)" :item="item" />
</template>
</a-list>
</a-spin>
@@ -112,16 +68,21 @@
</script>
<script lang="ts" setup>
import type { SftpTransferItem } from '../../types/terminal.type';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { useTerminalStore } from '@/store';
import { getFileSize } from '@/utils/file';
import { TransferStatus, TransferType } from '../../types/terminal.const';
import { useDictStore, useTerminalStore } from '@/store';
import { transferStatusKey } from '../../types/terminal.const';
import TransferItem from './transfer-item.vue';
const { transferManager } = useTerminalStore();
const { toOptions } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const filterStatus = ref<string>();
// 打开
const open = () => {
setVisible(true);
@@ -129,9 +90,24 @@
defineExpose({ open });
// 移除任务
const removeTask = (fileId: string) => {
transferManager.cancelTransfer(fileId);
// 选中过滤状态
const checkFilterStatus = (status: string) => {
// 相同则设置为空
if (status === filterStatus.value) {
filterStatus.value = undefined;
} else {
filterStatus.value = status;
}
};
// 过滤传输行
const filterItem = (item: SftpTransferItem) => {
return !filterStatus.value || item.status === filterStatus.value;
};
// 移除全部任务
const removeAllTask = () => {
transferManager.cancelAllTransfer();
};
// 关闭
@@ -146,14 +122,60 @@
</script>
<style lang="less" scoped>
@icon-size: 20px;
@item-left-width: 42px;
@item-right-width: 42px;
@item-center-width: 388px - @item-left-width - @item-right-width;
<style lang="less">
:deep(.transfer-item-wrapper) {
padding: 0 !important;
.transfer-drawer {
.arco-drawer-body {
overflow: hidden;
position: relative;
}
}
</style>
<style lang="less" scoped>
@header-height: 48px;
.transfer-container {
position: relative;
.transfer-header {
padding: 8px;
width: 100%;
height: @header-height;
position: absolute;
display: flex;
align-items: center;
justify-content: space-between;
&-icon {
width: 32px;
height: 32px;
font-size: 16px;
}
.status-count {
display: inline-block;
margin-left: 4px;
user-select: none;
}
}
.transfer-item-container {
width: 100%;
height: calc(100% - @header-height);
position: absolute;
top: @header-height;
overflow: auto;
:deep(.arco-list-item) {
padding: 0 !important;
}
:deep(.arco-scrollbar) {
height: 100%;
}
}
}
.list-empty {
@@ -161,86 +183,4 @@
margin-top: 32px;
}
.transfer-item {
min-height: 36px;
padding: 8px 0;
display: flex;
align-items: center;
&:hover {
.transfer-item-right-progress {
display: none;
}
.transfer-item-right-actions {
display: flex;
}
}
&-left {
width: @item-left-width;
display: flex;
justify-content: center;
.file-icon {
color: rgb(var(--arcoblue-6));
font-size: 18px;
}
}
&-center {
width: @item-center-width;
display: flex;
flex-direction: column;
.file-name {
color: var(--color-content-text-1);
overflow: hidden;
text-overflow: ellipsis;
width: fit-content;
max-width: 100%;
white-space: nowrap;
}
.transfer-progress, .target-path, .error-message {
padding-top: 4px;
font-size: 13px;
color: var(--color-neutral-8);
width: fit-content;
}
.error-message {
color: rgba(var(--red-6));
}
}
&-right {
width: @item-right-width;
&-progress {
display: flex;
justify-content: center;
}
&-actions {
display: none;
justify-content: center;
.close-icon {
width: @icon-size;
height: @icon-size;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
background: var(--color-fill-2);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,194 @@
<template>
<a-list-item class="transfer-item-wrapper">
<div class="transfer-item">
<!-- 左侧图标 -->
<div class="transfer-item-left">
<span class="file-icon">
<icon-upload v-if="item.type === TransferType.UPLOAD" />
<icon-download v-else-if="item.type === TransferType.DOWNLOAD" />
</span>
</div>
<!-- 中间信息 -->
<div class="transfer-item-center">
<!-- 文件名称 -->
<span class="file-name text-copy"
:title="item.name"
@click="copy(item.name)">
{{ item.name }}
</span>
<!-- 传输进度 -->
<span class="transfer-progress">
<!-- 当前大小 -->
<span v-if="item.status === TransferStatus.TRANSFERRING">{{ getFileSize(item.currentSize) }}</span>
<span class="mx4" v-if="item.status === TransferStatus.TRANSFERRING">/</span>
<!-- 总大小 -->
<span>{{ getFileSize(item.totalSize) }}</span>
<!-- 进度百分比 -->
<span class="ml8" v-if="item.status === TransferStatus.TRANSFERRING">
{{ item.progress }}%
</span>
</span>
<!-- 目标目录 -->
<span class="target-path text-copy"
:title="item.parentPath"
@click="copy(item.parentPath)">
{{ item.parentPath }}
</span>
<!-- 错误信息 -->
<a-tooltip v-if="item.errorMessage"
position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.errorMessage">
<span class="error-message">
{{ item.errorMessage }}
</span>
</a-tooltip>
</div>
<!-- 右侧状态/操作-->
<div class="transfer-item-right">
<!-- 传输状态 -->
<div class="transfer-item-right-progress">
<!-- 等待传输 -->
<icon-clock-circle v-if="item.status === TransferStatus.WAITING" />
<!-- 传输进度 -->
<a-progress v-else
type="circle"
size="mini"
:status="getDictValue(transferStatusKey, item.status, 'status')"
:percent="item.currentSize / item.totalSize" />
</div>
<!-- 传输操作 -->
<div class="transfer-item-right-actions">
<!-- 关闭 -->
<span class="close-icon" @click="removeTask(item.fileId)">
<icon-close />
</span>
</div>
</div>
</div>
</a-list-item>
</template>
<script lang="ts">
export default {
name: 'transferItem'
};
</script>
<script lang="ts" setup>
import type { SftpTransferItem } from '../../types/terminal.type';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { useDictStore, useTerminalStore } from '@/store';
import { copy } from '@/hooks/copy';
import { getFileSize } from '@/utils/file';
import { TransferStatus, TransferType, transferStatusKey } from '../../types/terminal.const';
const props = defineProps<{
item: SftpTransferItem;
}>();
const { transferManager } = useTerminalStore();
const { getDictValue } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
// 移除任务
const removeTask = (fileId: string) => {
transferManager.cancelTransfer(fileId);
};
</script>
<style lang="less" scoped>
@icon-size: 20px;
@item-left-width: 42px;
@item-right-width: 42px;
@item-center-width: 388px - @item-left-width - @item-right-width;
.transfer-item {
min-height: 36px;
padding: 8px 0;
display: flex;
align-items: center;
&:hover {
.transfer-item-right-progress {
display: none;
}
.transfer-item-right-actions {
display: flex;
}
}
&-left {
width: @item-left-width;
display: flex;
justify-content: center;
.file-icon {
color: rgb(var(--arcoblue-6));
font-size: 18px;
}
}
&-center {
width: @item-center-width;
display: flex;
flex-direction: column;
.file-name, .target-path {
color: var(--color-content-text-1);
overflow: hidden;
text-overflow: ellipsis;
width: fit-content;
max-width: 100%;
white-space: nowrap;
}
.transfer-progress, .target-path, .error-message {
padding-top: 4px;
font-size: 13px;
color: var(--color-neutral-8);
width: fit-content;
}
.error-message {
color: rgba(var(--red-6));
}
}
&-right {
width: @item-right-width;
&-progress {
display: flex;
justify-content: center;
}
&-actions {
display: none;
justify-content: center;
.close-icon {
width: @icon-size;
height: @icon-size;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
background: var(--color-fill-2);
}
}
}
}
}
</style>

View File

@@ -96,6 +96,16 @@ export default class SftpTransferManager implements ISftpTransferManager {
this.transferList.splice(index, 1);
}
// 取消全部传输
cancelAllTransfer(): void {
// 从列表中移除非传输中的元素
this.transferList.reduceRight((_, value: SftpTransferItem, index: number) => {
if (value.status !== TransferStatus.TRANSFERRING) {
this.transferList.splice(index, 1);
}
}, null as any);
}
// 打开会话
private async openClient() {
this.run = true;

View File

@@ -30,9 +30,9 @@
</div>
</main>
<!-- 命令片段列表抽屉 -->
<command-snippet-list-drawer ref="snippetRef" />
<command-snippet-drawer ref="snippetRef" />
<!-- 路径书签列表抽屉 -->
<path-bookmark-list-drawer ref="pathRef" />
<path-bookmark-drawer ref="pathRef" />
<!-- 传输列表 -->
<transfer-drawer ref="transferRef" />
</div>
@@ -57,8 +57,8 @@
import MainContent from './components/layout/main-content.vue';
import LoadingSkeleton from './components/layout/loading-skeleton.vue';
import TransferDrawer from '@/views/host/terminal/components/transfer/transfer-drawer.vue';
import CommandSnippetListDrawer from '@/views/host/command-snippet/components/command-snippet-list-drawer.vue';
import PathBookmarkListDrawer from '@/views/host/path-bookmark/components/path-bookmark-list-drawer.vue';
import CommandSnippetDrawer from '@/views/host/command-snippet/components/command-snippet-drawer.vue';
import PathBookmarkDrawer from '@/views/host/path-bookmark/components/path-bookmark-drawer.vue';
import '@/assets/style/host-terminal-layout.less';
import 'xterm/css/xterm.css';

View File

@@ -320,9 +320,9 @@ export const TerminalShortcutItems: Array<ShortcutKeyItem> = [
// 传输状态
export const TransferStatus = {
WAITING: 'waiting',
TRANSFERRING: 'normal',
TRANSFERRING: 'transferring',
SUCCESS: 'success',
ERROR: 'danger',
ERROR: 'error',
};
// 传输类型
@@ -390,16 +390,19 @@ export const extraSshAuthTypeKey = 'hostExtraSshAuthType';
export const connectStatusKey = 'terminalConnectStatus';
// 终端类型
export const terminalEmulationTypeKey = 'terminalEmulationType';
export const emulationTypeKey = 'terminalEmulationType';
// 终端标签颜色
export const terminalTabColorKey = 'terminalTabColor';
export const tabColorKey = 'terminalTabColor';
// SFTP 传输状态
export const transferStatusKey = 'sftpTransferStatus';
// 加载的字典值
export const dictKeys = [
fontFamilyKey, fontSizeKey,
fontWeightKey, cursorStyleKey,
newConnectionTypeKey, extraSshAuthTypeKey,
connectStatusKey, terminalEmulationTypeKey,
terminalTabColorKey
connectStatusKey, emulationTypeKey,
tabColorKey, transferStatusKey,
];

View File

@@ -402,6 +402,8 @@ export interface ISftpTransferManager {
addDownload: (hostId: number, currentPath: string, files: Array<SftpFile>) => void;
// 取消传输
cancelTransfer: (fileId: string) => void;
// 取消全部传输
cancelAllTransfer: () => void;
}
// sftp 上传器定义

View File

@@ -64,7 +64,7 @@
<!-- 快捷定义 -->
<a-tag v-for="definedExtraKey in definedExtraKeys"
color="arcoblue"
:title="`添加参数 ${definedExtraKey}`"
:title="`添加参数 ${definedExtraKey.name}`"
@click="addExtraParam(definedExtraKey.name, definedExtraKey.type)"
checkable
checked>
@@ -72,7 +72,7 @@
</a-tag>
<!-- 添加参数 -->
<a-button title="添加参数"
style="width: 180px;"
style="width: 140px;"
@click="addExtraParam(undefined,undefined)"
long>
<icon-plus />

View File

@@ -26,6 +26,9 @@ export const definedExtraKeys = [
}, {
name: 'color',
type: ValueType.COLOR
}, {
name: 'icon',
type: ValueType.STRING
}
];