update:分片上传
This commit is contained in:
11
package.json
11
package.json
@@ -22,6 +22,7 @@
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g6": "^4.8.24",
|
||||
"@antv/x6": "^2.18.1",
|
||||
"@antv/x6-plugin-clipboard": "^2.1.6",
|
||||
"@antv/x6-plugin-dnd": "^2.1.1",
|
||||
@@ -34,12 +35,15 @@
|
||||
"@antv/x6-vue-shape": "^2.1.2",
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"axios": "^1.11.0",
|
||||
"chinese-workday": "^1.10.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"dhtmlx-gantt": "^8.0.6",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.11.7",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^3.0.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.54.0",
|
||||
"pinia": "^3.0.3",
|
||||
"sass-embedded": "^1.90.0",
|
||||
@@ -51,10 +55,7 @@
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-pc-ui": "^4.9.13",
|
||||
"vxe-table": "^4.16.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"@antv/g6": "^4.8.24",
|
||||
"dhtmlx-gantt": "^8.0.6",
|
||||
"chinese-workday": "^1.10.0"
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
@@ -74,4 +75,4 @@
|
||||
"vite-plugin-vue-devtools": "^8.0.0",
|
||||
"vue-tsc": "^3.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,3 +217,13 @@ export const getDirectorySizeByUserIdApi = (params: any) => {
|
||||
export const chunkUploadToMinioApi = (params: any) => {
|
||||
return upload(`${PREFIX}data/chunkUploadToMinio`, params);
|
||||
};
|
||||
|
||||
// 知识库上传
|
||||
export const batchAddFileInfoApi = (params: any) => {
|
||||
return post(`${PREFIX}data/batchAddFileInfo`, params);
|
||||
};
|
||||
|
||||
// 知识库上传后处理
|
||||
export const callBackknowledgeFileApi = (params: any) => {
|
||||
return post(`${PREFIX}data/callBackknowledgeFile`, params);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="comp-upload-list">
|
||||
<!-- <div class="btn" @click="listVisible = true"><el-icon :size="22"><Upload /></el-icon></div> -->
|
||||
<div class="btn" @click="listVisible = true"><el-icon :size="22"><Upload /></el-icon></div>
|
||||
<el-drawer
|
||||
title="上传列表"
|
||||
v-model="listVisible"
|
||||
@@ -10,11 +10,23 @@
|
||||
>
|
||||
<div class="content">
|
||||
<div v-if="listData.length > 0" class="list">
|
||||
<div v-for="(item) in listData" :key="item.file.name" class="item">
|
||||
<div v-for="(item, index) in listData" :key="item.file.name" class="item">
|
||||
<div class="main">
|
||||
<div class="name">{{ item.file.name }}</div>
|
||||
<div class="toper">
|
||||
<div class="name">
|
||||
{{ item.file.name }}
|
||||
</div>
|
||||
<div class="status">
|
||||
{{ UPLOAD_FILE_STATUS[item.data.status] }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<el-progress :show-text="false" :percentage="Number(item.data.current / item.data.total * 100) || 0" :stroke-width="5" />
|
||||
<el-progress
|
||||
:show-text="false"
|
||||
:percentage="Number(item.data.current / item.data.total * 100) || 0"
|
||||
:status="item.data.status === '-1' ? 'exception' : ''"
|
||||
:stroke-width="5"
|
||||
/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="speed">{{ item.data.speed || '--' }}/s</div>
|
||||
@@ -23,7 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="options"><el-icon :size="16"><Delete /></el-icon></div>
|
||||
<div class="options"><el-icon v-if="item.data.status !== '1'" :size="16" @click="removeFun(index)"><Delete /></el-icon></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-task">暂无上传任务</div>
|
||||
@@ -33,63 +45,122 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import {
|
||||
// Upload,
|
||||
Delete } from '@element-plus/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import { Upload, Delete } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { UploadStore } from '@/stores/upload';
|
||||
import { formatFileSize } from '@/utils/file';
|
||||
import { chunkUploadToMinioApi, callBackknowledgeFileApi } from '@/api/data/data';
|
||||
import emitter from '@/utils/eventBus';
|
||||
|
||||
const uploadStore = UploadStore();
|
||||
const taskStatusObj: any = {}; // 更具uploadTaskId和businessId记录所以任务文件的上传状态
|
||||
const listVisible = ref(false);
|
||||
const listData = ref<any>([]);
|
||||
const chunkSize = 1024 * 1024; // 每片1MB
|
||||
const chunkSize = 1024 * 1024 * 5; // 每片5MB
|
||||
const UPLOAD_FILE_STATUS: any = { // TODO
|
||||
'-1': '上传失败',
|
||||
'0': '待上传',
|
||||
'1': '上传中',
|
||||
'2': '上传完成',
|
||||
};
|
||||
|
||||
watch(() => uploadStore.addFile, (data: any) => {
|
||||
initFun(data);
|
||||
emitter.on('ADD_UPLOAD_FILE', (addData: any) => {
|
||||
const data = addData.data;
|
||||
data.status = '0'; // 默认状态
|
||||
if (!taskStatusObj[data.uploadTaskId]) {
|
||||
taskStatusObj[data.uploadTaskId] = {};
|
||||
}
|
||||
taskStatusObj[data.uploadTaskId][data.businessId] = data.status;
|
||||
initFun(addData);
|
||||
});
|
||||
|
||||
const initFun = (data: any) => {
|
||||
listData.value.push(data);
|
||||
if (listData.value.length === 1) {
|
||||
sliceFileFun(data);
|
||||
sliceFileFun(0);
|
||||
}
|
||||
};
|
||||
|
||||
const sliceFileFun = async(data: any) => {
|
||||
const file = data.file;
|
||||
// 分片并上传
|
||||
const sliceFileFun = async(fileIndex: number) => {
|
||||
const fileObj = listData.value[fileIndex];
|
||||
const file = fileObj.file;
|
||||
const fileData = fileObj.data;
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
for (let index = 0; index < totalChunks; index++) {
|
||||
const start = index * chunkSize;
|
||||
let fileTempPath = '';
|
||||
ElMessage.success(`${file.name} 开始上传`);
|
||||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
||||
const start = chunkIndex * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
const formData: any = new FormData();
|
||||
formData.append('chunk', chunk);
|
||||
formData.append('chunkIndex', index);
|
||||
formData.append('totalChunks', totalChunks);
|
||||
formData.append('filename', file.name);
|
||||
const res: any = await uploadFun({ total: totalChunks, current: index });
|
||||
data.data.total = res.total;
|
||||
data.data.current = res.current;
|
||||
data.data.speed = res.speed;
|
||||
const chunkFile = file.slice(start, end);
|
||||
const params: any = {
|
||||
uploadTaskId: fileData.uploadTaskId,
|
||||
businessId: fileData.businessId,
|
||||
objectKey: fileData.objectKey,
|
||||
sourceFileName: fileData.sourceFileName,
|
||||
chunk: chunkIndex + 1,
|
||||
chunkTotal: totalChunks,
|
||||
file: chunkFile,
|
||||
};
|
||||
if (fileTempPath) {
|
||||
params.fileTempPath = fileTempPath;
|
||||
}
|
||||
const res: any = await uploadFun(params);
|
||||
if (res.result) {
|
||||
fileTempPath = res.fileTempPath;
|
||||
fileObj.data.total = totalChunks;
|
||||
fileObj.data.current = chunkIndex + 1;
|
||||
fileObj.data.speed = res.speed;
|
||||
fileObj.data.status = (chunkIndex + 1 === totalChunks) ? '2' : '1';
|
||||
} else {
|
||||
fileObj.data.status = '-1';
|
||||
}
|
||||
taskStatusObj[fileObj.data.uploadTaskId][fileObj.data.businessId] = fileObj.data.status;
|
||||
if (fileObj.data.status === '2') {
|
||||
ElMessage.success(`${file.name} 上传成功`);
|
||||
}
|
||||
callBackFun(fileData);
|
||||
}
|
||||
listData.value.shift();
|
||||
ElMessage.success(`${file.name}上传成功`);
|
||||
if (listData.value.length >= 1) {
|
||||
sliceFileFun(listData.value[0]);
|
||||
setTimeout(() => {
|
||||
fileTempPath = '';
|
||||
const index = listData.value.findIndex((item: any) => item.data.status === '0');
|
||||
if (listData.value[index]) {
|
||||
sliceFileFun(index);
|
||||
}
|
||||
}, 2000); // 2s后自动开启下次上传,没有具体作用
|
||||
};
|
||||
|
||||
// 后处理回调校验
|
||||
const callBackFun = (data: any) => {
|
||||
const { uploadTaskId, isApprove, taskType } = data;
|
||||
const totalTaskNum = Object.keys(taskStatusObj[uploadTaskId]).length;
|
||||
const finishTaskNum = Object.values(taskStatusObj[uploadTaskId]).filter(val => val === '2' || val === '-1').length;
|
||||
const successData = Object.entries(taskStatusObj[uploadTaskId]).filter(([, value]) => value === '2').map(([key]) => key);
|
||||
const failData = Object.entries(taskStatusObj[uploadTaskId]).filter(([, value]) => value === '-1').map(([key]) => key);
|
||||
if (finishTaskNum === totalTaskNum) {
|
||||
const params = {
|
||||
uploadTaskId: uploadTaskId,
|
||||
succBusinessIds: successData,
|
||||
failBusinessIds: failData,
|
||||
isApprove,
|
||||
};
|
||||
const apiObj: any = {
|
||||
2: callBackknowledgeFileApi, // 知识库后处理
|
||||
};
|
||||
apiObj[taskType](params);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadFun = (data: any) => {
|
||||
const uploadFun = async(params: any) => {
|
||||
const starTime = new Date().getTime();
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const endTime = new Date().getTime();
|
||||
data.speed = formatFileSize((endTime - starTime) / 1000 * chunkSize);
|
||||
resolve(data);
|
||||
}, 1000);
|
||||
});
|
||||
const res: any = await chunkUploadToMinioApi(params);
|
||||
const data = res.data || {};
|
||||
const endTime = new Date().getTime();
|
||||
data.speed = formatFileSize(chunkSize / ((endTime - starTime) / 1000)); // 根据每次分片上传时间大致算一个速度
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeFun = (index: any) => {
|
||||
listData.value.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -124,10 +195,20 @@ const uploadFun = (data: any) => {
|
||||
.main {
|
||||
padding: 0 10px;
|
||||
flex: 1;
|
||||
.name {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
.toper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 4px;
|
||||
line-height: 14px;
|
||||
font-size: 12px;
|
||||
.name {
|
||||
flex: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.status {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
height: 5px;
|
||||
@@ -142,6 +223,7 @@ const uploadFun = (data: any) => {
|
||||
}
|
||||
}
|
||||
.options {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const UploadStore = defineStore('upload', {
|
||||
state: () => {
|
||||
return {
|
||||
addFile: {} as any,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
uploadFile(file: any, data: any) {
|
||||
this.addFile = {
|
||||
file,
|
||||
data,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
4
src/utils/eventBus.ts
Normal file
4
src/utils/eventBus.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import mitt from 'mitt';
|
||||
|
||||
const emitter = mitt();
|
||||
export default emitter;
|
||||
@@ -2934,7 +2934,7 @@ minimatch@^9.0.4:
|
||||
|
||||
mitt@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
|
||||
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
||||
|
||||
ml-array-max@^1.2.4:
|
||||
|
||||
Reference in New Issue
Block a user