🔨 批量上传.

This commit is contained in:
lijiahang
2024-05-10 11:23:22 +08:00
parent cf17cf93b0
commit cd312ef5c8
28 changed files with 658 additions and 213 deletions

View File

@@ -4,7 +4,7 @@
<div class="panel-header">
<h3>文件列表</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button-group size="small" :disabled="startStatus">
<a-button @click="clear">清空</a-button>
<!-- 选择文件 -->
<a-upload v-model:file-list="fileList"
@@ -29,11 +29,12 @@
<!-- 文件列表 -->
<div v-if="fileList.length" class="files-container">
<a-upload class="files-wrapper"
:class="['waiting-files-wrapper']"
:class="[ startStatus ? 'uploading-files-wrapper' : 'waiting-files-wrapper' ]"
v-model:file-list="fileList"
:auto-upload="false"
:show-cancel-button="false"
:show-remove-button="true"
:show-retry-button="false"
:show-remove-button="!startStatus"
:show-file-list="true">
<template #upload-button />
<template #file-name="{ fileItem }">
@@ -71,15 +72,61 @@
<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 { ref } from 'vue';
import { getFileSize } from '@/utils/file';
import FileUploader from '@/components/system/uploader/file-uploader';
const emits = defineEmits(['end', 'error']);
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 startUpload = async (token: string) => {
// 修改状态
startStatus.value = true;
fileList.value.forEach(s => s.status = 'uploading');
// 开始上传
try {
uploader.value = new FileUploader(token, fileList.value);
uploader.value?.setHook(() => {
emits('end');
});
await uploader.value?.start();
} catch (e) {
emits('error');
}
};
// 清空
const clear = () => {
fileList.value = [];
startStatus.value = false;
};
// 关闭
const close = () => {
startStatus.value = false;
uploader.value?.close();
};
defineExpose({ getFiles, startUpload, close });
</script>
<style lang="less" scoped>
@@ -127,11 +174,22 @@
}
:deep(.arco-upload-list) {
max-height: 100%;
padding: 0;
max-height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
:deep(.arco-upload-list-item-error) {
.arco-upload-list-item-name {
margin-right: 0 !important;
}
.arco-upload-progress {
display: none;
}
}
:deep(.arco-upload-list-item-name-text) {
width: 100%;
}

View File

@@ -5,8 +5,24 @@
<h3>批量上传</h3>
<!-- 操作 -->
<a-button-group size="small">
<a-button>重置</a-button>
<a-button type="primary">上传</a-button>
<!-- 重置 -->
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.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>
<!-- 开始上传 -->
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.value
&& status.value !== UploadTaskStatus.UPLOADING.value"
type="primary"
@click="submit">
开始上传
</a-button>
</a-button-group>
</div>
<!-- 表单 -->
@@ -35,7 +51,7 @@
<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">
<span class="usn pointer span-blue" @click="emits('openHost')">
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
</span>
</div>
@@ -52,28 +68,27 @@
<script lang="ts" setup>
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
import type { UploadTaskStatusType } from '../types/const';
import { ref } from 'vue';
import formRules from '../types/form.rules';
import useLoading from '@/hooks/loading';
import { UploadTaskStatus } from '../types/const';
const defaultForm = (): UploadTaskCreateRequest => {
return {
description: '',
remotePath: '',
hostIdList: [],
files: []
};
};
const { loading, setLoading } = useLoading();
const emits = defineEmits(['upload', 'openHost', 'cancel', 'clear']);
const props = defineProps<{
status: UploadTaskStatusType;
formModel: UploadTaskCreateRequest;
}>();
const formRef = ref<any>();
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
const hostModal = ref<any>();
// 打开选择主机
const openSelectHost = () => {
hostModal.value.open(formModel.value.hostIdList);
// 提交表单
const submit = async () => {
// 验证参数
let error = await formRef.value.validate();
if (error) {
return false;
}
emits('upload');
};
</script>

View File

@@ -1,14 +1,25 @@
<template>
<div class="panel-container">
<a-spin class="panel-container full" :loading="loading">
<!-- 上传步骤 -->
<batch-upload-step class="panel-item step-panel-container"
:step="step"
:status="stepStatus" />
:status="status" />
<!-- 上传表单 -->
<batch-upload-form class="panel-item form-panel-container" />
<batch-upload-form class="panel-item form-panel-container"
:form-model="formModel"
:status="status"
@upload="doCreateUploadTask"
@cancel="doCancelUploadTask"
@open-host="openHostModal"
@clear="clear" />
<!-- 上传文件 -->
<batch-upload-files class="panel-item files-panel-container" />
</div>
<batch-upload-files class="panel-item files-panel-container"
ref="filesRef"
@end="uploadRequestEnd"
@error="uploadRequestError" />
<!-- 主机模态框 -->
<authorized-host-modal ref="hostModal"
@selected="setSelectedHost" />
</a-spin>
</template>
<script lang="ts">
@@ -18,14 +29,125 @@
</script>
<script lang="ts" setup>
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
import type { UploadTaskStatusType } from '../types/const';
import { ref } from 'vue';
import { UploadStep, UploadStepStatus } from '../types/const';
import { UploadTaskStatus } from '../types/const';
import { cancelUploadTask, createUploadTask, startUploadTask } 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 '@/views/exec/batch-upload/components/batch-upload-files.vue';
import BatchUploadFiles from './batch-upload-files.vue';
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
const step = ref(UploadStep.PREPARATION);
const stepStatus = ref(UploadStepStatus.PROCESS);
const defaultForm = (): UploadTaskCreateRequest => {
return {
description: '',
remotePath: '/root/batch',
hostIdList: [1],
files: []
};
};
const { loading, setLoading } = useLoading();
const taskId = ref();
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
const status = ref<UploadTaskStatusType>(UploadTaskStatus.WAITING);
const filesRef = ref();
const hostModal = ref<any>();
// TODO pullstatus
// TODO 测试 error 情况
// 设置选中主机
const setSelectedHost = (hosts: Array<number>) => {
formModel.value.hostIdList = hosts;
};
// 创建上传任务
const doCreateUploadTask = async () => {
// 获取文件
const files = filesRef.value?.getFiles();
if (!files || !files.length) {
Message.error('请先选择需要上传的文件');
return;
}
// 创建任务
setLoading(true);
status.value = UploadTaskStatus.WAITING;
try {
formModel.value.files = files;
const { data } = await createUploadTask(formModel.value);
taskId.value = data.id;
status.value = UploadTaskStatus.REQUESTING;
// 上传文件
await filesRef.value.startUpload(data.token);
} catch (e) {
status.value = UploadTaskStatus.FAILED;
} finally {
setLoading(false);
}
};
// 取消上传任务
const doCancelUploadTask = async () => {
setLoading(true);
filesRef.value?.close();
try {
// 取消上传
await cancelUploadTask(taskId.value, false);
status.value = UploadTaskStatus.CANCELED;
} catch (e) {
} finally {
setLoading(false);
}
};
// 上传请求结束
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);
}
};
// 上传请求失败
const uploadRequestError = async () => {
setLoading(true);
try {
// 开始上传
await cancelUploadTask(taskId.value, true);
status.value = UploadTaskStatus.FAILED;
} catch (e) {
} finally {
setLoading(false);
}
};
// 打开主机模态框
const openHostModal = () => {
hostModal.value.open(formModel.value.hostIdList);
};
// 清空
const clear = () => {
status.value = UploadTaskStatus.WAITING;
formModel.value = { ...defaultForm() };
};
</script>

View File

@@ -1,11 +1,23 @@
<template>
<div class="container">
<a-steps :current="step"
:status="status as any"
<a-steps :current="status.step"
:status="status.status as any"
direction="vertical">
<a-step description="创建上传任务">创建任务</a-step>
<a-step description="将文件上传到临时分区">上传文件</a-step>
<a-step description="将文件分发到目标服务器">分发文件</a-step>
<a-step>
上传文件
<template #description>
<span>将文件上传到临时分区</span><br>
<span class="span-red">在此期间请不要关闭页面</span>
</template>
</a-step>
<a-step>
分发文件
<template #description>
<span>将文件分发到目标服务器</span><br>
<span>可以关闭页面</span>
</template>
</a-step>
<a-step description="上传完成并释放资源">上传完成</a-step>
</a-steps>
</div>
@@ -18,13 +30,16 @@
</script>
<script lang="ts" setup>
import type { UploadTaskStatusType } from '../types/const';
defineProps<{
step: number;
status: string;
status: UploadTaskStatusType;
}>();
</script>
<style lang="less" scoped>
.container {
user-select: none;
}
</style>

View File

@@ -1,35 +1,48 @@
// 上传任务状态定义
export interface UploadTaskStatusType {
value: string,
step: number,
status: string,
}
// 上传任务状态
export const UploadTaskStatus = {
// 准备
PREPARATION: 'PREPARATION',
// 上传中
UPLOADING: 'UPLOADING',
// 已完成
FINISHED: 'FINISHED',
// 已取消
CANCELED: 'CANCELED',
};
// 上传步骤
export const UploadStep = {
// 准备中
PREPARATION: 1,
// 等待
WAITING: {
value: 'WAITING',
step: 1,
status: 'process',
},
// 请求中
REQUESTING: 2,
// 分发中
UPLOADING: 3,
REQUESTING: {
value: 'REQUESTING',
step: 2,
status: 'process',
},
// 上传中
UPLOADING: {
value: 'UPLOADING',
step: 3,
status: 'process',
},
// 已完成
FINISHED: 4,
};
// 上传步骤状态
export const UploadStepStatus = {
// 处理中
PROCESS: 'process',
// 上传完成
FINISH: 'finish',
// 上传失败
ERROR: 'error',
FINISHED: {
value: 'FINISHED',
step: 4,
status: 'finish',
},
// 已失败
FAILED: {
value: 'FAILED',
step: 4,
status: 'error',
},
// 已取消
CANCELED: {
value: 'CANCELED',
step: 4,
status: 'error',
},
};
// 上传任务状态 字典项

View File

@@ -18,14 +18,8 @@ export const remotePath = [{
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

@@ -109,7 +109,7 @@
return true;
}
// 添加到上传列表
const files = fileList.value.map(s => s.file);
const files = fileList.value.map(s => s.file as File);
transferManager.addUpload(hostId.value, parentPath.value, files);
Message.success('已开始上传, 点击右侧传输列表查看进度');
// 清空
@@ -158,9 +158,10 @@
}
:deep(.arco-upload-list) {
max-height: calc(100vh - 386px);
overflow-y: auto;
padding: 0 12px 0 0;
max-height: calc(100vh - 386px);
overflow-x: hidden;
overflow-y: auto;
}
:deep(.arco-upload-list-item-name) {