🔨 批量上传.

This commit is contained in:
lijiahang
2024-05-10 18:58:48 +08:00
parent 564e40a31d
commit 0a43e5db45
18 changed files with 557 additions and 135 deletions

View File

@@ -40,7 +40,7 @@ import java.util.List;
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class UploadTaskController {
// TODO 测试空文件上传 0B 取消怎么那么慢 是不是删除也慢 异步cancel cancel 需要设置子元素为 cancel
// TODO 前端日志 测试删除慢吗
@Resource
private UploadTaskService uploadTaskService;

View File

@@ -7,7 +7,6 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 上传任务 视图响应对象
@@ -31,7 +30,4 @@ public class UploadTaskCreateVO implements Serializable {
@Schema(description = "上传 token")
private String token;
@Schema(description = "主机")
private List<HostBaseVO> hosts;
}

View File

@@ -43,6 +43,9 @@ public class UploadTaskFileVO implements Serializable {
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "额外信息")
private String extraInfo;
@Schema(description = "状态")
private String status;

View File

@@ -0,0 +1,43 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 上传任务主机 视图响应对象
*
* @author Jiahang Li
* @version 1.0.7
* @since 2024-5-8 10:31
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "UploadTaskHostVO", description = "上传任务主机 视图响应对象")
public class UploadTaskHostVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "主机名称")
private String name;
@Schema(description = "主机编码")
private String code;
@Schema(description = "主机地址")
private String address;
@Schema(description = "上传文件")
private List<UploadTaskFileVO> files;
}

View File

@@ -56,7 +56,7 @@ public class UploadTaskVO implements Serializable {
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "上传文件")
private List<UploadTaskFileVO> files;
@Schema(description = "上传主机及文件")
private List<UploadTaskHostVO> hosts;
}

View File

@@ -128,7 +128,6 @@ public class SftpSession extends TerminalSession implements ISftpSession {
} catch (Exception e) {
throw Exceptions.ioRuntime(e);
} finally {
// TODO Test
// 关闭 inputStream 可能会被阻塞 ???...??? 只能关闭 executor
Streams.close(this.executor);
this.connect();

View File

@@ -141,6 +141,9 @@ public class FileUploadTask implements IFileUploadTask {
.current(0L)
.build())
.collect(Collectors.toList());
if (files.isEmpty()) {
return;
}
// 添加到上传器
uploaderList.add(new FileUploader(id, k, files));
});
@@ -150,6 +153,10 @@ public class FileUploadTask implements IFileUploadTask {
* 执行上传
*/
private void runUpload() throws Exception {
if (uploaderList.isEmpty()) {
return;
}
// 执行
if (uploaderList.size() == 1) {
// 单个主机直接执行
IFileUploader handler = uploaderList.get(0);

View File

@@ -141,7 +141,6 @@ public class FileUploader implements IFileUploader {
int read;
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
// todo test
file.setCurrent(file.getCurrent() + read);
}
outputStream.flush();

View File

@@ -32,10 +32,7 @@ import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskFileRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest;
import com.orion.ops.module.asset.entity.vo.HostBaseVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskFileVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
import com.orion.ops.module.asset.entity.vo.*;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum;
import com.orion.ops.module.asset.enums.UploadTaskStatusEnum;
@@ -147,7 +144,6 @@ public class UploadTaskServiceImpl implements UploadTaskService {
return UploadTaskCreateVO.builder()
.id(id)
.token(token)
.hosts(hosts)
.build();
}
@@ -158,11 +154,12 @@ public class UploadTaskServiceImpl implements UploadTaskService {
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 查询任务文件
List<UploadTaskFileVO> files = uploadTaskFileService.getFileByTaskId(id);
// 计算传输进度
this.computeUploadProgress(id, files);
// 返回
UploadTaskVO uploadTask = UploadTaskConvert.MAPPER.to(record);
uploadTask.setFiles(files);
// 计算传输进度
this.computeUploadProgress(id, files);
// 设置任务文件
this.setTaskFiles(uploadTask, files);
return uploadTask;
}
@@ -208,8 +205,9 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} else {
// 计算进度
this.computeUploadProgress(id, files);
// 设置任务文件
}
task.setFiles(files);
this.setTaskFiles(task, files);
}
return tasks;
}
@@ -299,8 +297,6 @@ public class UploadTaskServiceImpl implements UploadTaskService {
.map(localFileClient::getReturnPath)
.map(localFileClient::getAbsolutePath)
.collect(Collectors.toList());
// TODO test
paths.forEach(System.out::println);
// 删除文件
paths.forEach(Files1::delete);
}
@@ -393,7 +389,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
uploadFile.setStatus(UploadTaskFileStatusEnum.CANCELED.name());
uploadFile.setEndTime(new Date());
LambdaQueryWrapper<UploadTaskFileDO> updateFileQuery = uploadTaskFileDAO.wrapper()
.in(UploadTaskFileDO::getId, updateIdList)
.in(UploadTaskFileDO::getTaskId, updateIdList)
.in(UploadTaskFileDO::getStatus,
UploadTaskFileStatusEnum.WAITING.name(),
UploadTaskFileStatusEnum.UPLOADING.name());
@@ -446,11 +442,34 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} else if (UploadTaskFileStatusEnum.FINISHED.name().equals(status)) {
file.setCurrent(file.getFileSize());
} else if (UploadTaskFileStatusEnum.FAILED.name().equals(status)) {
file.setCurrent(0L);
file.setCurrent(file.getFileSize());
} else if (UploadTaskFileStatusEnum.CANCELED.name().equals(status)) {
file.setCurrent(0L);
file.setCurrent(file.getFileSize());
}
}
}
/**
* 设置任务文件
*
* @param task task
* @param files files
*/
private void setTaskFiles(UploadTaskVO task, List<UploadTaskFileVO> files) {
Map<Long, List<UploadTaskFileVO>> hostFiles = files.stream()
.collect(Collectors.groupingBy(UploadTaskFileVO::getHostId));
List<UploadTaskHostVO> hosts = JSON.parseObject(task.getExtraInfo(), UploadTaskExtraDTO.class)
.getHosts()
.stream()
.map(s -> UploadTaskHostVO.builder()
.id(s.getId())
.code(s.getCode())
.name(s.getName())
.address(s.getAddress())
.files(hostFiles.get(s.getId()))
.build())
.collect(Collectors.toList());
task.setHosts(hosts);
}
}

View File

@@ -1,5 +1,4 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { HostQueryResponse } from '@/api/asset/host';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import axios from 'axios';
import qs from 'query-string';
@@ -29,7 +28,6 @@ export interface UploadTaskFileCreateRequest {
export interface UploadTaskCreateResponse {
id: number;
token: string;
hosts: Array<HostQueryResponse>;
}
/**
@@ -58,13 +56,24 @@ export interface UploadTaskQueryResponse extends TableData {
startTime: number;
endTime: number;
createTime: number;
files: Array<UploadTaskFileQueryResponse>;
hosts: Array<UploadTaskHost>;
}
/**
* 上传任务文件查询响应
* 上传任务主机响应
*/
export interface UploadTaskFileQueryResponse {
export interface UploadTaskHost {
id: number;
code: string;
name: string;
address: string;
files: Array<UploadTaskFile>;
}
/**
* 上传任务文件响应
*/
export interface UploadTaskFile {
id: number;
taskId: number;
hostId: number;

View File

@@ -4,10 +4,10 @@
<div class="panel-header">
<h3>文件列表</h3>
<!-- 操作 -->
<a-button-group size="small" :disabled="startStatus">
<a-button-group size="mini" :disabled="startStatus">
<a-button @click="clear">清空</a-button>
<!-- 选择文件 -->
<a-upload v-model:file-list="fileList"
<a-upload v-model:file-list="files"
:auto-upload="false"
:show-file-list="false"
:multiple="true">
@@ -16,7 +16,7 @@
</template>
</a-upload>
<!-- 选择文件夹 -->
<a-upload v-model:file-list="fileList"
<a-upload v-model:file-list="files"
:auto-upload="false"
:show-file-list="false"
:directory="true">
@@ -27,34 +27,36 @@
</a-button-group>
</div>
<!-- 文件列表 -->
<div v-if="fileList.length" class="files-container">
<a-upload class="files-wrapper"
:class="[ startStatus ? 'uploading-files-wrapper' : 'waiting-files-wrapper' ]"
v-model:file-list="fileList"
:auto-upload="false"
:show-cancel-button="false"
:show-retry-button="false"
:show-remove-button="!startStatus"
: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">
<div v-if="files.length" class="files-container">
<a-scrollbar style="overflow-y: auto; height: 100%;">
<a-upload class="files-wrapper"
:class="[ startStatus ? 'uploading-files-wrapper' : 'waiting-files-wrapper' ]"
v-model:file-list="files"
:auto-upload="false"
:show-cancel-button="false"
:show-retry-button="false"
:show-remove-button="!startStatus"
:show-file-list="true">
<template #upload-button />
<template #file-name="{ fileItem }">
<div class="file-name-wrapper">
<!-- 文件名称 -->
<span class="file-name text-ellipsis">
<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">
</a-tooltip>
<!-- 文件大小 -->
<span class="file-size span-blue">
{{ getFileSize(fileItem.file.size) }}
</span>
</div>
</template>
</a-upload>
</div>
</template>
</a-upload>
</a-scrollbar>
</div>
<!-- 未选择文件 -->
<a-result v-else
@@ -72,38 +74,35 @@
<script lang="ts" setup>
import type { FileItem } from '@arco-design/web-vue';
import type { UploadTaskFileCreateRequest } from '@/api/exec/upload-task';
import type { IFileUploader } from '@/components/system/uploader/const';
import { onUnmounted, ref } from 'vue';
import { computed, onUnmounted, ref } from 'vue';
import { getFileSize } from '@/utils/file';
import FileUploader from '@/components/system/uploader/file-uploader';
const emits = defineEmits(['end', 'error']);
const emits = defineEmits(['update:fileList', 'end', 'error', 'clearFile']);
const props = defineProps<{
fileList: Array<FileItem>;
}>();
const startStatus = ref(false);
const fileList = ref<FileItem[]>([]);
const uploader = ref<IFileUploader>();
// 获取上传的文件
const getFiles = (): Array<UploadTaskFileCreateRequest> => {
return fileList.value
.map(s => {
return {
fileId: s.uid,
filePath: s.file?.webkitRelativePath || s.file?.name,
fileSize: s.file?.size,
};
});
};
const files = computed<Array<FileItem>>({
get() {
return props.fileList;
},
set(e) {
emits('update:fileList', e);
}
});
// 开始上传
const startUpload = async (token: string) => {
// 修改状态
startStatus.value = true;
fileList.value.forEach(s => s.status = 'uploading');
props.fileList.forEach(s => s.status = 'uploading');
// 开始上传
try {
uploader.value = new FileUploader(token, fileList.value);
uploader.value = new FileUploader(token, props.fileList);
uploader.value?.setHook(() => {
emits('end');
});
@@ -115,8 +114,8 @@
// 清空
const clear = () => {
fileList.value = [];
startStatus.value = false;
emits('clearFile');
};
// 关闭
@@ -125,7 +124,7 @@
uploader.value?.close();
};
defineExpose({ getFiles, startUpload, close });
defineExpose({ startUpload, close });
// 卸载时关闭
onUnmounted(() => {
@@ -171,7 +170,6 @@
:deep(.arco-upload-wrapper) {
position: absolute;
height: 100%;
overflow-y: auto;
}
:deep(.arco-upload) {
@@ -181,8 +179,6 @@
:deep(.arco-upload-list) {
padding: 0;
max-height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
:deep(.arco-upload-list-item-error) {
@@ -222,4 +218,10 @@
}
}
:deep(.arco-scrollbar) {
position: absolute;
height: 100%;
width: 100%;
}
</style>

View File

@@ -4,21 +4,21 @@
<div class="panel-header">
<h3>批量上传</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button-group size="mini">
<!-- 重置 -->
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.value"
<a-button v-if="status.value === UploadTaskStatus.WAITING.value"
@click="emits('clear')">
重置
</a-button>
<!-- 取消上传 -->
<a-button v-if="status.value === UploadTaskStatus.REQUESTING.value
|| status.value === UploadTaskStatus.UPLOADING.value"
@click="emits('cancel')">
<a-button v-if="status.value === UploadTaskStatus.REQUESTING.value"
type="primary"
status="warning"
@click="emits('abort')">
取消上传
</a-button>
<!-- 开始上传 -->
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.value
&& status.value !== UploadTaskStatus.UPLOADING.value"
<a-button v-if="status.value === UploadTaskStatus.WAITING.value"
type="primary"
@click="submit">
开始上传
@@ -73,7 +73,7 @@
import formRules from '../types/form.rules';
import { UploadTaskStatus } from '../types/const';
const emits = defineEmits(['upload', 'openHost', 'cancel', 'clear']);
const emits = defineEmits(['upload', 'openHost', 'abort', 'clear']);
const props = defineProps<{
status: UploadTaskStatusType;
formModel: UploadTaskCreateRequest;

View File

@@ -0,0 +1,174 @@
<template>
<div class="container">
<!-- 表头 -->
<div class="panel-header">
<h3>上传主机</h3>
<!-- 操作 -->
<a-button-group size="mini">
<!-- 返回 -->
<a-button @click="emits('back')">返回</a-button>
<!-- 取消上传 -->
<a-button type="primary"
status="warning"
@click="emits('cancel')">
取消上传
</a-button>
</a-button-group>
</div>
<!-- 主机列表 -->
<div class="wrapper">
<a-scrollbar style="overflow-y: auto; height: 100%;">
<!-- 主机 -->
<div v-for="host in task.hosts"
class="host-item"
:class="[ selectedHost === host.id ? 'host-item-active' : '']"
@click="changeSelectedHost(host.id)">
<!-- 主机信息 -->
<div class="host-item-host">
<!-- 主机名称 -->
<div class="host-item-name text-ellipsis" :title="host.name">
{{ host.name }}
</div>
<!-- 主机地址 -->
<div class="host-item-address text-ellipsis" :title="host.address">
{{ host.address }}
</div>
</div>
<!-- 主机状态 -->
<a-space class="host-item-status" direction="vertical">
<!-- 未完成 -->
<a-tag class="host-item-status-tag" color="#52C41A">
{{ host.files.length - getFinishCount(host.files) }}
<template #icon>
<icon-clock-circle class="host-item-status-icon" />
</template>
</a-tag>
<!-- 已完成 -->
<a-tag class="host-item-status-tag" color="#1890FF">
{{ getFinishCount(host.files) }}
<template #icon>
<icon-check-circle class="host-item-status-icon" />
</template>
</a-tag>
</a-space>
</div>
</a-scrollbar>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadHosts'
};
</script>
<script lang="ts" setup>
import type { UploadTaskQueryResponse } from '@/api/exec/upload-task';
import type { UploadTaskFile } from '@/api/exec/upload-task';
import { UploadTaskFileStatus } from '../types/const';
const emits = defineEmits(['update:selectedHost', 'back', 'cancel']);
const props = defineProps<{
selectedHost: number;
task: UploadTaskQueryResponse;
}>();
// 修改选中的主机
const changeSelectedHost = (id: number) => {
emits('update:selectedHost', id);
};
// 获取已完成数量
const getFinishCount = (files: Array<UploadTaskFile>) => {
return files.filter(s => s.status === UploadTaskFileStatus.FINISHED
|| s.status === UploadTaskFileStatus.CANCELED
|| s.status === UploadTaskFileStatus.FAILED).length;
};
</script>
<style lang="less" scoped>
.wrapper {
width: 100%;
height: calc(100% - 36px);
position: relative;
.host-item {
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
position: relative;
background: var(--color-fill-1);
cursor: pointer;
&:last-child {
margin-bottom: 0;
}
&:hover {
background: var(--color-fill-2);
}
&-host {
width: calc(100% - 64px);
display: flex;
align-items: flex-start;
flex-direction: column;
justify-content: center;
}
&-status {
user-select: none;
&-tag {
max-width: 64px;
}
&-icon {
color: #FFFFFF;
}
}
&-name {
width: 100%;
margin-bottom: 12px;
font-size: 14px;
color: var(--color-text-1);
}
&-address {
width: 100%;
font-size: 12px;
color: var(--color-text-3);
}
}
.host-item-active {
background: var(--color-fill-2) !important;
&::after {
width: 3px;
height: 100%;
border-radius: 4px 6px 6px 4px;
display: block;
position: absolute;
top: 0;
right: 1px;
background: rgb(var(--arcoblue-6));
content: '';
}
}
}
:deep(.arco-scrollbar) {
position: absolute;
height: 100%;
width: 100%;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="container">
<!-- 表头 -->
<div class="panel-header">
<h3>传输列表</h3>
</div>
<div class="wrapper">
<a-scrollbar style="overflow-y: auto; height: 100%;">
<!-- 主机 -->
<div v-for="file in files" class="file-item">
<!-- 图标 -->
<div class="file-item-icon span-blue">
<icon-file />
</div>
<!-- 文件路径 -->
<div class="file-item-path text-ellipsis" :title="file.filePath">
{{ file.filePath }}
</div>
<!-- 进度 -->
<div class="file-item-progress">
<a-progress type="circle"
size="mini"
:status="getDictValue(fileStatusKey, file.status, 'status') as any"
:percent="file.current / file.fileSize" />
</div>
</div>
</a-scrollbar>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'batchUploadProgress'
};
</script>
<script lang="ts" setup>
import type { UploadTaskFile } from '@/api/exec/upload-task';
import { fileStatusKey } from '../types/const';
import { useDictStore } from '@/store';
const emits = defineEmits(['update:selectedHost']);
const props = defineProps<{
files: Array<UploadTaskFile>;
}>();
const { getDictValue } = useDictStore();
</script>
<style lang="less" scoped>
@icon-width: 24px;
@progress-width: 24px;
.wrapper {
width: 100%;
height: calc(100% - 36px);
position: relative;
.file-item {
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
background: var(--color-fill-1);
&:last-child {
margin-bottom: 0;
}
&:hover {
background: var(--color-fill-2);
}
&-icon {
width: @icon-width;
font-size: 18px;
}
&-path {
width: calc(100% - @icon-width - @progress-width);
font-size: 14px;
color: var(--color-text-1);
}
&-progress {
width: @progress-width;
display: flex;
justify-content: flex-end;
}
}
}
:deep(.arco-scrollbar) {
position: absolute;
height: 100%;
width: 100%;
}
</style>

View File

@@ -1,21 +1,40 @@
<template>
<a-spin class="panel-container full" :loading="loading">
<!-- 上传步骤 -->
<batch-upload-step class="panel-item step-panel-container"
<batch-upload-step class="panel-item first-panel-container"
:status="status" />
<!-- 上传表单 -->
<batch-upload-form class="panel-item form-panel-container"
<batch-upload-form v-if="status.formPanel"
class="panel-item center-panel-container"
:form-model="formModel"
:status="status"
@upload="doCreateUploadTask"
@cancel="doCancelUploadTask"
@abort="abortUploadRequest"
@open-host="openHostModal"
@clear="clear" />
<!-- 上传文件 -->
<batch-upload-files class="panel-item files-panel-container"
@clear="clearForm" />
<!-- 上传主机 -->
<batch-upload-hosts v-else
class="panel-item center-panel-container"
v-model:selected-host="selectedHost"
:task="task"
@back="backFormPanel"
@cancel="doCancelUploadTask" />
<!-- 文件列表 -->
<batch-upload-files v-if="status.formPanel"
v-model:file-list="fileList"
class="panel-item last-panel-container"
ref="filesRef"
@end="uploadRequestEnd"
@error="uploadRequestError" />
@error="uploadRequestError"
@clear-file="clearFile" />
<!-- 传输进度 -->
<template v-else>
<template v-for="host in task.hosts">
<batch-upload-progress v-if="host.id === selectedHost"
class="panel-item last-panel-container"
:files="host.files" />
</template>
</template>
<!-- 主机模态框 -->
<authorized-host-modal ref="hostModal"
@selected="setSelectedHost" />
@@ -24,21 +43,24 @@
<script lang="ts">
export default {
name: 'batchUploadPanel'
name: 'uploadPanel'
};
</script>
<script lang="ts" setup>
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
import type { FileItem } from '@arco-design/web-vue';
import type { UploadTaskCreateRequest, UploadTaskQueryResponse } from '@/api/exec/upload-task';
import type { UploadTaskStatusType } from '../types/const';
import { ref } from 'vue';
import { UploadTaskStatus } from '../types/const';
import { cancelUploadTask, createUploadTask, startUploadTask } from '@/api/exec/upload-task';
import { cancelUploadTask, createUploadTask, startUploadTask, getUploadTask } from '@/api/exec/upload-task';
import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue';
import BatchUploadStep from './batch-upload-step.vue';
import BatchUploadForm from './batch-upload-form.vue';
import BatchUploadFiles from './batch-upload-files.vue';
import BatchUploadHosts from './batch-upload-hosts.vue';
import BatchUploadProgress from './batch-upload-progress.vue';
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
const defaultForm = (): UploadTaskCreateRequest => {
@@ -53,14 +75,15 @@
const { loading, setLoading } = useLoading();
const taskId = ref();
const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse);
const selectedHost = ref();
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
const fileList = ref<Array<FileItem>>([]);
const status = ref<UploadTaskStatusType>(UploadTaskStatus.WAITING);
const filesRef = ref();
const hostModal = ref<any>();
// TODO pullstatus
// host tab
// status tab
// TODO pullstatus
//
const setSelectedHost = (hosts: Array<number>) => {
@@ -70,7 +93,13 @@
//
const doCreateUploadTask = async () => {
//
const files = filesRef.value?.getFiles();
const files = fileList.value.map(s => {
return {
fileId: s.uid,
filePath: s.file?.webkitRelativePath || s.file?.name,
fileSize: s.file?.size,
};
});
if (!files || !files.length) {
Message.error('请先选择需要上传的文件');
return;
@@ -95,34 +124,45 @@
//
const doCancelUploadTask = async () => {
setLoading(true);
filesRef.value?.close();
try {
//
await cancelUploadTask(taskId.value, false);
status.value = UploadTaskStatus.CANCELED;
status.value = UploadTaskStatus.WAITING;
Message.success('已取消');
} catch (e) {
} finally {
setLoading(false);
}
};
//
const abortUploadRequest = () => {
status.value = UploadTaskStatus.WAITING;
filesRef.value?.close();
};
//
const uploadRequestEnd = async () => {
if (status.value.value !== UploadTaskStatus.REQUESTING.value) {
//
return;
}
//
setLoading(true);
try {
//
await startUploadTask(taskId.value);
status.value = UploadTaskStatus.UPLOADING;
} catch (e) {
//
await uploadRequestError();
} finally {
setLoading(false);
if (status.value.value === UploadTaskStatus.REQUESTING.value) {
//
setLoading(true);
try {
//
await startUploadTask(taskId.value);
//
const { data } = await getUploadTask(taskId.value);
task.value = data;
selectedHost.value = data.hosts[0].id;
status.value = UploadTaskStatus.UPLOADING;
} catch (e) {
//
await uploadRequestError();
} finally {
setLoading(false);
}
} else {
//
await doCancelUploadTask();
}
};
@@ -144,11 +184,22 @@
hostModal.value.open(formModel.value.hostIdList);
};
//
const clear = () => {
//
const backFormPanel = () => {
status.value = UploadTaskStatus.WAITING;
taskId.value = undefined;
task.value = undefined as any;
selectedHost.value = undefined as any;
};
//
const clearForm = () => {
formModel.value = { ...defaultForm() };
filesRef.value?.close();
};
//
const clearFile = () => {
fileList.value = [];
};
</script>
@@ -156,7 +207,7 @@
<style lang="less" scoped>
@step-width: 258px;
@center-width: 398px;
@files-width: calc(100% - @step-width - 16px - @center-width - 16px);
@last-width: calc(100% - @step-width - 16px - @center-width - 16px);
.panel-container {
height: 100%;
@@ -168,20 +219,21 @@
padding: 16px;
border-radius: 4px;
margin-right: 16px;
position: relative;
background: var(--color-bg-2);
}
.step-panel-container {
.first-panel-container {
width: @step-width;
}
.form-panel-container {
.center-panel-container {
width: @center-width;
}
.files-panel-container {
.last-panel-container {
margin-right: 0;
width: @files-width;
width: @last-width;
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="layout-container upload-container">
<!-- 上传面板 -->
<batch-upload-panel />
<upload-panel />
</div>
</template>
@@ -15,7 +15,7 @@
import { onMounted } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from './types/const';
import BatchUploadPanel from './components/batch-upload-panel.vue';
import UploadPanel from './components/upload-panel.vue';
// 加载字典值
onMounted(async () => {

View File

@@ -1,8 +1,9 @@
// 上传任务状态定义
export interface UploadTaskStatusType {
value: string,
step: number,
status: string,
value: string;
step: number;
status: string;
formPanel: boolean;
}
// 上传任务状态
@@ -12,37 +13,50 @@ export const UploadTaskStatus = {
value: 'WAITING',
step: 1,
status: 'process',
formPanel: true,
},
// 请求中
REQUESTING: {
value: 'REQUESTING',
step: 2,
status: 'process',
formPanel: true,
},
// 上传中
UPLOADING: {
value: 'UPLOADING',
step: 3,
status: 'process',
formPanel: false,
},
// 已完成
FINISHED: {
value: 'FINISHED',
step: 4,
status: 'finish',
formPanel: false,
},
// 已失败
FAILED: {
value: 'FAILED',
step: 4,
status: 'error',
formPanel: false,
},
};
// 上传任务文件状态
export const UploadTaskFileStatus = {
// 等待中
WAITING: 'WAITING',
// 上传中
UPLOADING: 'UPLOADING',
// 已完成
FINISHED: 'FINISHED',
// 已完成
FAILED: 'FAILED',
// 已取消
CANCELED: {
value: 'CANCELED',
step: 4,
status: 'error',
},
CANCELED: 'CANCELED',
};
// 上传任务状态 字典项

View File

@@ -4,7 +4,7 @@
<div class="panel-header">
<h3>执行参数</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button-group size="mini">
<a-button @click="emits('reset')">重置</a-button>
<a-button type="primary" @click="emits('exec')">执行</a-button>
</a-button-group>