🔨 批量上传.

This commit is contained in:
lijiahang
2024-05-09 15:43:35 +08:00
parent af00e71651
commit cf17cf93b0
10 changed files with 539 additions and 31 deletions

View File

@@ -7,14 +7,19 @@ import qs from 'query-string';
* 上传任务创建请求
*/
export interface UploadTaskCreateRequest {
userId?: number;
username?: string;
remotePath?: string;
description?: string;
status?: string;
extraInfo?: string;
startTime?: string;
endTime?: string;
hostIdList?: Array<number>;
files?: Array<UploadTaskFileCreateRequest>;
}
/**
* 上传任务文件创建请求
*/
export interface UploadTaskFileCreateRequest {
fileId?: string;
filePath?: string;
fileSize?: number;
}
/**

View File

@@ -0,0 +1,162 @@
<template>
<div class="container">
<!-- 表头 -->
<div class="panel-header">
<h3>文件列表</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button @click="clear">清空</a-button>
<!-- 选择文件 -->
<a-upload v-model:file-list="fileList"
:auto-upload="false"
:show-file-list="false"
:multiple="true">
<template #upload-button>
<a-button type="primary">选择文件</a-button>
</template>
</a-upload>
<!-- 选择文件夹 -->
<a-upload v-model:file-list="fileList"
:auto-upload="false"
:show-file-list="false"
:directory="true">
<template #upload-button>
<a-button type="primary">选择文件夹</a-button>
</template>
</a-upload>
</a-button-group>
</div>
<!-- 文件列表 -->
<div v-if="fileList.length" class="files-container">
<a-upload class="files-wrapper"
:class="['waiting-files-wrapper']"
v-model:file-list="fileList"
:auto-upload="false"
:show-cancel-button="false"
:show-remove-button="true"
:show-file-list="true">
<template #upload-button />
<template #file-name="{ fileItem }">
<div class="file-name-wrapper">
<!-- 文件名称 -->
<a-tooltip position="left"
:mini="true"
:content="fileItem.file.webkitRelativePath || fileItem.file.name">
<!-- 文件名称 -->
<span class="file-name text-ellipsis">
{{ fileItem.file.webkitRelativePath || fileItem.file.name }}
</span>
</a-tooltip>
<!-- 文件大小 -->
<span class="file-size span-blue">
{{ getFileSize(fileItem.file.size) }}
</span>
</div>
</template>
</a-upload>
</div>
<!-- 未选择文件 -->
<a-result v-else
class="usn"
status="404"
subtitle="请先点击上方按钮选择文件" />
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadFiles'
};
</script>
<script lang="ts" setup>
import type { FileItem } from '@arco-design/web-vue';
import { ref } from 'vue';
import { getFileSize } from '@/utils/file';
const fileList = ref<FileItem[]>([]);
// 清空
const clear = () => {
};
</script>
<style lang="less" scoped>
@file-size-width: 82px;
.files-container {
width: 100%;
height: calc(100% - 36px);
position: relative;
}
:deep(.waiting-files-wrapper) {
.arco-upload-list {
padding: 0 6px 0 0 !important;
}
.arco-upload-list-item-name {
margin-right: 0 !important;
}
.arco-upload-list-item .arco-upload-progress {
display: none;
}
}
:deep(.uploading-files-wrapper) {
.arco-upload-list {
padding: 0 !important;
}
.arco-upload-list-item-name {
margin-right: 10px !important;
}
}
.files-wrapper {
:deep(.arco-upload-wrapper) {
position: absolute;
height: 100%;
overflow-y: auto;
}
:deep(.arco-upload) {
display: none;
}
:deep(.arco-upload-list) {
max-height: 100%;
padding: 0;
overflow-y: auto;
}
:deep(.arco-upload-list-item-name-text) {
width: 100%;
}
:deep(.arco-upload-list-item:first-of-type) {
margin-top: 0 !important;
}
}
.file-name-wrapper {
display: flex;
justify-content: space-between;
.file-name {
color: var(--color-text-1);
display: inline-block;
width: calc(100% - @file-size-width);
}
.file-size {
font-size: 13px;
display: inline-block;
width: @file-size-width;
text-align: end;
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<div class="container">
<!-- 表头 -->
<div class="panel-header">
<h3>批量上传</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button>重置</a-button>
<a-button type="primary">上传</a-button>
</a-button-group>
</div>
<!-- 表单 -->
<a-form :model="formModel"
ref="formRef"
class="form-wrapper"
label-align="right"
:auto-label-width="true"
:rules="formRules">
<!-- 上传描述 -->
<a-form-item field="description" label="上传描述">
<a-input v-model="formModel.description"
placeholder="请输入上传描述"
allow-clear />
</a-form-item>
<!-- 上传路径 -->
<a-form-item field="remotePath" label="上传路径">
<a-input v-model="formModel.remotePath"
placeholder="请输入上传路径"
allow-clear />
</a-form-item>
<!-- 上传主机 -->
<a-form-item field="hostIdList" label="上传主机">
<div class="selected-host">
<!-- 已选择数量 -->
<span class="usn" v-if="formModel.hostIdList?.length">
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
</span>
<span class="usn pointer span-blue" @click="openSelectHost">
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
</span>
</div>
</a-form-item>
</a-form>
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadForm'
};
</script>
<script lang="ts" setup>
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
import { ref } from 'vue';
import formRules from '../types/form.rules';
import useLoading from '@/hooks/loading';
const defaultForm = (): UploadTaskCreateRequest => {
return {
description: '',
remotePath: '',
hostIdList: [],
files: []
};
};
const { loading, setLoading } = useLoading();
const formRef = ref<any>();
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
const hostModal = ref<any>();
// 打开选择主机
const openSelectHost = () => {
hostModal.value.open(formModel.value.hostIdList);
};
</script>
<style lang="less" scoped>
.selected-host {
width: 100%;
height: 32px;
padding: 0 12px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-text-2);
background: var(--color-fill-2);
transition: all 0.3s;
&-count {
font-size: 16px;
font-weight: 600;
display: inline-block;
margin: 0 6px;
}
&:hover {
background: var(--color-fill-3);
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div class="panel-container">
<!-- 上传步骤 -->
<batch-upload-step class="panel-item step-panel-container"
:step="step"
:status="stepStatus" />
<!-- 上传表单 -->
<batch-upload-form class="panel-item form-panel-container" />
<!-- 上传文件 -->
<batch-upload-files class="panel-item files-panel-container" />
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadPanel'
};
</script>
<script lang="ts" setup>
import { ref } from 'vue';
import { UploadStep, UploadStepStatus } from '../types/const';
import BatchUploadStep from './batch-upload-step.vue';
import BatchUploadForm from './batch-upload-form.vue';
import BatchUploadFiles from '@/views/exec/batch-upload/components/batch-upload-files.vue';
const step = ref(UploadStep.PREPARATION);
const stepStatus = ref(UploadStepStatus.PROCESS);
</script>
<style lang="less" scoped>
@step-width: 258px;
@center-width: 398px;
@files-width: calc(100% - @step-width - 16px - @center-width - 16px);
.panel-container {
height: 100%;
display: flex;
position: relative;
.panel-item {
height: 100%;
padding: 16px;
border-radius: 4px;
margin-right: 16px;
background: var(--color-bg-2);
}
.step-panel-container {
width: @step-width;
}
.form-panel-container {
width: @center-width;
}
.files-panel-container {
margin-right: 0;
width: @files-width;
}
}
:deep(.panel-header) {
width: 100%;
height: 28px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: flex-start;
h3, > span {
margin: 0;
overflow: hidden;
white-space: nowrap;
}
h3 {
color: var(--color-text-1);
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div class="container">
<a-steps :current="step"
:status="status as any"
direction="vertical">
<a-step description="创建上传任务">创建任务</a-step>
<a-step description="将文件上传到临时分区">上传文件</a-step>
<a-step description="将文件分发到目标服务器">分发文件</a-step>
<a-step description="上传完成并释放资源">上传完成</a-step>
</a-steps>
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadStep'
};
</script>
<script lang="ts" setup>
defineProps<{
step: number;
status: string;
}>();
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,17 +1,37 @@
<template>
<div>批量上传</div>
<div class="layout-container upload-container">
<!-- 上传面板 -->
<batch-upload-panel />
</div>
</template>
<script lang="ts">
export default {
name: 'index'
name: 'batchUpload'
};
</script>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from './types/const';
import BatchUploadPanel from './components/batch-upload-panel.vue';
// 加载字典值
onMounted(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
</script>
<style lang="less" scoped>
.upload-container {
width: 100%;
height: 100%;
position: relative;
display: flex;
}
</style>

View File

@@ -0,0 +1,42 @@
// 上传任务状态
export const UploadTaskStatus = {
// 准备中
PREPARATION: 'PREPARATION',
// 上传中
UPLOADING: 'UPLOADING',
// 已完成
FINISHED: 'FINISHED',
// 已取消
CANCELED: 'CANCELED',
};
// 上传步骤
export const UploadStep = {
// 准备中
PREPARATION: 1,
// 请求中
REQUESTING: 2,
// 分发中
UPLOADING: 3,
// 已完成
FINISHED: 4,
};
// 上传步骤状态
export const UploadStepStatus = {
// 处理中
PROCESS: 'process',
// 上传完成
FINISH: 'finish',
// 上传失败
ERROR: 'error',
};
// 上传任务状态 字典项
export const taskStatusKey = 'uploadTaskStatus';
// 上传任务文件状态 字典项
export const fileStatusKey = 'uploadTaskFileStatus';
// 加载的字典值
export const dictKeys = [taskStatusKey, fileStatusKey];

View File

@@ -0,0 +1,31 @@
import type { FieldRule } from '@arco-design/web-vue';
export const description = [{
maxLength: 128,
message: '描述长度不能大于128位'
}] as FieldRule[];
export const hostIdList = [{
required: true,
message: '请选择上传主机'
}] as FieldRule[];
export const remotePath = [{
required: true,
message: '请输入上传路径'
}, {
maxLength: 1024,
message: '上传路径长度不能大于1024位'
}] as FieldRule[];
export const files = [{
required: true,
message: '请选择文件'
}] as FieldRule[];
export default {
description,
hostIdList,
remotePath,
files,
} as Record<string, FieldRule | FieldRule[]>;

View File

@@ -21,11 +21,11 @@
<div class="selected-host">
<!-- 已选择数量 -->
<span class="usn" v-if="formModel.hostIdList?.length">
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
</span>
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
</span>
<span class="usn pointer span-blue" @click="openSelectHost">
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
</span>
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
</span>
</div>
</a-form-item>
</a-col>

View File

@@ -38,34 +38,31 @@
</a-upload>
</a-space>
<!-- 文件列表 -->
<a-upload v-if="fileList.length" class="file-list-uploader"
<a-upload v-if="fileList.length"
class="file-list-uploader"
v-model:file-list="fileList"
:auto-upload="false"
:show-file-list="true">
<template #upload-button />
<template #file-name="{ fileItem }">
<!-- 上传文件夹 -->
<template v-if="fileItem.file.webkitRelativePath">
<a-tooltip position="top"
<div class="file-name-wrapper">
<!-- 文件名称 -->
<a-tooltip position="left"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="fileItem.file.webkitRelativePath">
<span>{{ fileItem.file.webkitRelativePath }}</span>
:content="fileItem.file.webkitRelativePath || fileItem.file.name">
<!-- 文件名称 -->
<span class="file-name text-ellipsis">
{{ fileItem.file.webkitRelativePath || fileItem.file.name }}
</span>
</a-tooltip>
</template>
<!-- 上传文件 -->
<template v-else>
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="fileItem.file.name">
<span>{{ fileItem.file.name }}</span>
</a-tooltip>
</template>
<!-- 文件大小 -->
<span class="file-size span-blue">
{{ getFileSize(fileItem.file.size) }}
</span>
</div>
</template>
</a-upload>
</div>
@@ -79,17 +76,19 @@
</script>
<script lang="ts" setup>
import type { FileItem } from '@arco-design/web-vue';
import { ref } from 'vue';
import { useTerminalStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import useVisible from '@/hooks/visible';
import { getFileSize } from '@/utils/file';
const { visible, setVisible } = useVisible();
const { transferManager } = useTerminalStore();
const hostId = ref();
const parentPath = ref('');
const fileList = ref<any[]>([]);
const fileList = ref<FileItem[]>([]);
// 打开
const open = (host: number, parent: string) => {
@@ -131,6 +130,8 @@
</script>
<style lang="less" scoped>
@file-size-width: 82px;
.upload-container {
width: 100%;
}
@@ -162,6 +163,14 @@
padding: 0 12px 0 0;
}
:deep(.arco-upload-list-item-name) {
margin-right: 0 !important;
}
:deep(.arco-upload-list-item-name-text) {
width: 100%;
}
:deep(.arco-upload-list-item:first-of-type) {
margin-top: 0 !important;
}
@@ -170,4 +179,23 @@
display: none;
}
}
.file-name-wrapper {
display: flex;
justify-content: space-between;
.file-name {
color: var(--color-text-1);
display: inline-block;
width: calc(100% - @file-size-width);
}
.file-size {
font-size: 13px;
display: inline-block;
width: @file-size-width;
text-align: end;
}
}
</style>