✨ 批量上传优化.
This commit is contained in:
@@ -53,6 +53,8 @@ export interface UploadTaskQueryResponse extends TableData {
|
||||
description: string;
|
||||
status: string;
|
||||
extraInfo: string;
|
||||
fileCount: number;
|
||||
hostCount: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
createTime: number;
|
||||
@@ -86,6 +88,17 @@ export interface UploadTaskFile {
|
||||
current: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传任务状态响应
|
||||
*/
|
||||
export interface UploadTaskStatusResponse extends TableData {
|
||||
id: number;
|
||||
status: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
files: Array<UploadTaskFile>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建上传任务
|
||||
*/
|
||||
@@ -125,7 +138,7 @@ export function getUploadTaskPage(request: UploadTaskQueryRequest) {
|
||||
* 查询上传任务状态
|
||||
*/
|
||||
export function getUploadTaskStatus(idList: Array<number>, queryFiles: boolean) {
|
||||
return axios.get<Array<UploadTaskQueryResponse>>('/asset/upload-task/status', {
|
||||
return axios.get<Array<UploadTaskStatusResponse>>('/asset/upload-task/status', {
|
||||
params: { idList, queryFiles },
|
||||
paramsSerializer: params => {
|
||||
return qs.stringify(params, { arrayFormat: 'comma' });
|
||||
|
||||
@@ -41,14 +41,10 @@
|
||||
<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">
|
||||
<span class="file-name text-ellipsis"
|
||||
:title="fileItem.file.webkitRelativePath || fileItem.file.name">
|
||||
{{ fileItem.file.webkitRelativePath || fileItem.file.name }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 文件大小 -->
|
||||
<span class="file-size span-blue">
|
||||
{{ getFileSize(fileItem.file.size) }}
|
||||
@@ -144,7 +140,7 @@
|
||||
|
||||
:deep(.waiting-files-wrapper) {
|
||||
.arco-upload-list {
|
||||
padding: 0 6px 0 0 !important;
|
||||
padding: 0 12px 0 0 !important;
|
||||
}
|
||||
|
||||
.arco-upload-list-item-name {
|
||||
@@ -205,16 +201,18 @@
|
||||
justify-content: space-between;
|
||||
|
||||
.file-name {
|
||||
color: var(--color-text-1);
|
||||
display: inline-block;
|
||||
width: calc(100% - @file-size-width);
|
||||
padding: 2px 0;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
width: @file-size-width;
|
||||
text-align: end;
|
||||
display: inline-flex;
|
||||
font-size: 13px;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
<!-- 操作 -->
|
||||
<a-button-group size="mini">
|
||||
<!-- 重置 -->
|
||||
<a-button v-if="status.value === UploadTaskStatus.WAITING.value"
|
||||
<a-button v-if="status.value === UploadTaskStepStatus.WAITING.value"
|
||||
@click="emits('clear')">
|
||||
重置
|
||||
</a-button>
|
||||
<!-- 取消上传 -->
|
||||
<a-button v-if="status.value === UploadTaskStatus.REQUESTING.value"
|
||||
<a-button v-if="status.value === UploadTaskStepStatus.REQUESTING.value"
|
||||
type="primary"
|
||||
status="warning"
|
||||
@click="emits('abort')">
|
||||
取消上传
|
||||
</a-button>
|
||||
<!-- 开始上传 -->
|
||||
<a-button v-if="status.value === UploadTaskStatus.WAITING.value"
|
||||
<a-button v-if="status.value === UploadTaskStepStatus.WAITING.value"
|
||||
type="primary"
|
||||
@click="submit">
|
||||
开始上传
|
||||
@@ -39,7 +39,10 @@
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 上传路径 -->
|
||||
<a-form-item field="remotePath" label="上传路径">
|
||||
<a-form-item field="remotePath"
|
||||
style="margin-bottom: 4px;"
|
||||
label="上传路径"
|
||||
help="${username} 用户名 ${home} 家目录">
|
||||
<a-input v-model="formModel.remotePath"
|
||||
placeholder="请输入上传路径"
|
||||
allow-clear />
|
||||
@@ -71,7 +74,7 @@
|
||||
import type { UploadTaskStatusType } from '../types/const';
|
||||
import { ref } from 'vue';
|
||||
import formRules from '../types/form.rules';
|
||||
import { UploadTaskStatus } from '../types/const';
|
||||
import { UploadTaskStepStatus } from '../types/const';
|
||||
|
||||
const emits = defineEmits(['upload', 'openHost', 'abort', 'clear']);
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<!-- 返回 -->
|
||||
<a-button @click="emits('back')">返回</a-button>
|
||||
<!-- 取消上传 -->
|
||||
<a-button type="primary"
|
||||
<a-button v-if="status.value === UploadTaskStepStatus.UPLOADING.value"
|
||||
type="primary"
|
||||
status="warning"
|
||||
@click="emits('cancel')">
|
||||
取消上传
|
||||
@@ -37,14 +38,20 @@
|
||||
<!-- 主机状态 -->
|
||||
<a-space class="host-item-status" direction="vertical">
|
||||
<!-- 未完成 -->
|
||||
<a-tag class="host-item-status-tag" color="#52C41A">
|
||||
<a-tag class="host-item-status-tag"
|
||||
color="#73D13D"
|
||||
title="未完成数量"
|
||||
size="small">
|
||||
{{ 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">
|
||||
<a-tag class="host-item-status-tag"
|
||||
color="#40A9FF"
|
||||
title="已完成数量"
|
||||
size="small">
|
||||
{{ getFinishCount(host.files) }}
|
||||
<template #icon>
|
||||
<icon-check-circle class="host-item-status-icon" />
|
||||
@@ -66,10 +73,13 @@
|
||||
<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';
|
||||
import type { UploadTaskStatusType } from '../types/const';
|
||||
import { UploadTaskStepStatus } from '../types/const';
|
||||
import { UploadTaskFileStatus } from '@/views/exec/upload-task/types/const';
|
||||
|
||||
const emits = defineEmits(['update:selectedHost', 'back', 'cancel']);
|
||||
const props = defineProps<{
|
||||
status: UploadTaskStatusType;
|
||||
selectedHost: number;
|
||||
task: UploadTaskQueryResponse;
|
||||
}>();
|
||||
@@ -126,6 +136,7 @@
|
||||
|
||||
&-tag {
|
||||
max-width: 64px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
|
||||
@@ -16,12 +16,28 @@
|
||||
<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 class="file-item-status">
|
||||
<!-- 文件大小 -->
|
||||
<div class="file-item-size span-blue">
|
||||
<!-- 当前大小 -->
|
||||
<template v-if="file.status === UploadTaskFileStatus.WAITING || file.status === UploadTaskFileStatus.UPLOADING">
|
||||
{{ getFileSize(file.current || 0) }}
|
||||
</template>
|
||||
<!-- 总大小 -->
|
||||
<template v-else>
|
||||
{{ getFileSize(file.fileSize) }}
|
||||
</template>
|
||||
</div>
|
||||
<!-- 进度 -->
|
||||
<a-tooltip position="left"
|
||||
:content="((file.current || 0) / file.fileSize * 100).toFixed(2) + '%'"
|
||||
mini>
|
||||
<a-progress type="circle"
|
||||
size="mini"
|
||||
:status="getDictValue(fileStatusKey, file.status, 'status') as any"
|
||||
:percent="(file.current || 0) / file.fileSize" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</a-scrollbar>
|
||||
@@ -38,7 +54,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UploadTaskFile } from '@/api/exec/upload-task';
|
||||
import { fileStatusKey } from '../types/const';
|
||||
import { UploadTaskFileStatus } from '@/views/exec/upload-task/types/const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { getFileSize } from '@/utils/file';
|
||||
|
||||
const emits = defineEmits(['update:selectedHost']);
|
||||
const props = defineProps<{
|
||||
@@ -47,12 +65,11 @@
|
||||
|
||||
const { getDictValue } = useDictStore();
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@icon-width: 24px;
|
||||
@progress-width: 24px;
|
||||
@status-width: 102px;
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
@@ -83,15 +100,24 @@
|
||||
}
|
||||
|
||||
&-path {
|
||||
width: calc(100% - @icon-width - @progress-width);
|
||||
padding: 2px 0;
|
||||
width: calc(100% - @icon-width - @status-width);
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
&-progress {
|
||||
width: @progress-width;
|
||||
&-size {
|
||||
font-size: 12px;
|
||||
margin-right: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-status {
|
||||
width: @status-width;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<a-spin class="panel-container full" :loading="loading">
|
||||
<!-- 上传步骤 -->
|
||||
<batch-upload-step class="panel-item first-panel-container"
|
||||
:status="status" />
|
||||
:status="taskStatus" />
|
||||
<!-- 上传表单 -->
|
||||
<batch-upload-form v-if="status.formPanel"
|
||||
<batch-upload-form v-if="taskStatus.formPanel"
|
||||
class="panel-item center-panel-container"
|
||||
:form-model="formModel"
|
||||
:status="status"
|
||||
:status="taskStatus"
|
||||
@upload="doCreateUploadTask"
|
||||
@abort="abortUploadRequest"
|
||||
@open-host="openHostModal"
|
||||
@@ -16,11 +16,12 @@
|
||||
<batch-upload-hosts v-else
|
||||
class="panel-item center-panel-container"
|
||||
v-model:selected-host="selectedHost"
|
||||
:status="taskStatus"
|
||||
:task="task"
|
||||
@back="backFormPanel"
|
||||
@cancel="doCancelUploadTask" />
|
||||
<!-- 文件列表 -->
|
||||
<batch-upload-files v-if="status.formPanel"
|
||||
<batch-upload-files v-if="taskStatus.formPanel"
|
||||
v-model:file-list="fileList"
|
||||
class="panel-item last-panel-container"
|
||||
ref="filesRef"
|
||||
@@ -51,9 +52,10 @@
|
||||
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, getUploadTask } from '@/api/exec/upload-task';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { UploadTaskStepStatus } from '../types/const';
|
||||
import { UploadTaskStatus } from '@/views/exec/upload-task/types/const';
|
||||
import { cancelUploadTask, createUploadTask, startUploadTask, getUploadTask, getUploadTaskStatus } from '@/api/exec/upload-task';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import BatchUploadStep from './batch-upload-step.vue';
|
||||
@@ -66,24 +68,23 @@
|
||||
const defaultForm = (): UploadTaskCreateRequest => {
|
||||
return {
|
||||
description: '',
|
||||
remotePath: '/root/batch',
|
||||
hostIdList: [1],
|
||||
remotePath: '',
|
||||
hostIdList: [],
|
||||
files: []
|
||||
};
|
||||
};
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const pullStatusId = ref();
|
||||
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 taskStatus = ref<UploadTaskStatusType>(UploadTaskStepStatus.WAITING);
|
||||
const filesRef = ref();
|
||||
const hostModal = ref<any>();
|
||||
|
||||
// TODO pullstatus
|
||||
const hostModal = ref();
|
||||
|
||||
// 设置选中主机
|
||||
const setSelectedHost = (hosts: Array<number>) => {
|
||||
@@ -106,16 +107,16 @@
|
||||
}
|
||||
// 创建任务
|
||||
setLoading(true);
|
||||
status.value = UploadTaskStatus.WAITING;
|
||||
taskStatus.value = UploadTaskStepStatus.WAITING;
|
||||
try {
|
||||
formModel.value.files = files;
|
||||
const { data } = await createUploadTask(formModel.value);
|
||||
taskId.value = data.id;
|
||||
status.value = UploadTaskStatus.REQUESTING;
|
||||
taskStatus.value = UploadTaskStepStatus.REQUESTING;
|
||||
// 上传文件
|
||||
await filesRef.value.startUpload(data.token);
|
||||
} catch (e) {
|
||||
status.value = UploadTaskStatus.FAILED;
|
||||
taskStatus.value = UploadTaskStepStatus.FAILED;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -127,7 +128,7 @@
|
||||
try {
|
||||
// 取消上传
|
||||
await cancelUploadTask(taskId.value, false);
|
||||
status.value = UploadTaskStatus.WAITING;
|
||||
taskStatus.value = UploadTaskStepStatus.WAITING;
|
||||
Message.success('已取消');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
@@ -137,13 +138,13 @@
|
||||
|
||||
// 中断上传请求
|
||||
const abortUploadRequest = () => {
|
||||
status.value = UploadTaskStatus.WAITING;
|
||||
taskStatus.value = UploadTaskStepStatus.WAITING;
|
||||
filesRef.value?.close();
|
||||
};
|
||||
|
||||
// 上传请求结束
|
||||
const uploadRequestEnd = async () => {
|
||||
if (status.value.value === UploadTaskStatus.REQUESTING.value) {
|
||||
if (taskStatus.value.value === UploadTaskStepStatus.REQUESTING.value) {
|
||||
// 如果结束后还是请求中则代表请求完毕
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -153,7 +154,7 @@
|
||||
const { data } = await getUploadTask(taskId.value);
|
||||
task.value = data;
|
||||
selectedHost.value = data.hosts[0].id;
|
||||
status.value = UploadTaskStatus.UPLOADING;
|
||||
taskStatus.value = UploadTaskStepStatus.UPLOADING;
|
||||
} catch (e) {
|
||||
// 设置失败
|
||||
await uploadRequestError();
|
||||
@@ -172,13 +173,48 @@
|
||||
try {
|
||||
// 开始上传
|
||||
await cancelUploadTask(taskId.value, true);
|
||||
status.value = UploadTaskStatus.FAILED;
|
||||
taskStatus.value = UploadTaskStepStatus.FAILED;
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载轮询状态
|
||||
const pullTaskStatus = async () => {
|
||||
if (!taskId.value || !task.value) {
|
||||
return;
|
||||
}
|
||||
// 非上传中则不查询
|
||||
if (taskStatus.value.value !== UploadTaskStepStatus.UPLOADING.value) {
|
||||
return;
|
||||
}
|
||||
// 查询状态
|
||||
const { data } = await getUploadTaskStatus([taskId.value], true);
|
||||
if (!data.length) {
|
||||
return;
|
||||
}
|
||||
const taskStatusData = data[0];
|
||||
// 设置任务状态
|
||||
if (taskStatusData.status === UploadTaskStatus.FINISHED) {
|
||||
taskStatus.value = UploadTaskStepStatus.FINISHED;
|
||||
} else if (taskStatusData.status === UploadTaskStatus.CANCELED) {
|
||||
taskStatus.value = UploadTaskStepStatus.FINISHED;
|
||||
} else if (taskStatusData.status === UploadTaskStatus.FAILED) {
|
||||
taskStatus.value = UploadTaskStepStatus.FAILED;
|
||||
}
|
||||
// 设置文件进度
|
||||
for (let host of task.value.hosts) {
|
||||
for (let file of host.files) {
|
||||
const fileStatus = taskStatusData.files.find(s => s.id === file.id);
|
||||
if (fileStatus) {
|
||||
file.status = fileStatus.status;
|
||||
file.current = fileStatus.current;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 打开主机模态框
|
||||
const openHostModal = () => {
|
||||
hostModal.value.open(formModel.value.hostIdList);
|
||||
@@ -186,7 +222,7 @@
|
||||
|
||||
// 返回表单页面
|
||||
const backFormPanel = () => {
|
||||
status.value = UploadTaskStatus.WAITING;
|
||||
taskStatus.value = UploadTaskStepStatus.WAITING;
|
||||
taskId.value = undefined;
|
||||
task.value = undefined as any;
|
||||
selectedHost.value = undefined as any;
|
||||
@@ -202,6 +238,16 @@
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
// 设置轮询状态
|
||||
onMounted(() => {
|
||||
pullStatusId.value = setInterval(pullTaskStatus, 5000);
|
||||
});
|
||||
|
||||
// 卸载状态查询
|
||||
onUnmounted(() => {
|
||||
clearInterval(pullStatusId.value);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface UploadTaskStatusType {
|
||||
}
|
||||
|
||||
// 上传任务状态
|
||||
export const UploadTaskStatus = {
|
||||
export const UploadTaskStepStatus = {
|
||||
// 等待中
|
||||
WAITING: {
|
||||
value: 'WAITING',
|
||||
@@ -45,20 +45,6 @@ export const UploadTaskStatus = {
|
||||
},
|
||||
};
|
||||
|
||||
// 上传任务文件状态
|
||||
export const UploadTaskFileStatus = {
|
||||
// 等待中
|
||||
WAITING: 'WAITING',
|
||||
// 上传中
|
||||
UPLOADING: 'UPLOADING',
|
||||
// 已完成
|
||||
FINISHED: 'FINISHED',
|
||||
// 已完成
|
||||
FAILED: 'FAILED',
|
||||
// 已取消
|
||||
CANCELED: 'CANCELED',
|
||||
};
|
||||
|
||||
// 上传任务状态 字典项
|
||||
export const taskStatusKey = 'uploadTaskStatus';
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
|
||||
// 确定
|
||||
const handlerOk = async () => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<div>upload-task-log</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'index'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user