需要和后端对接好:我的是拿到数据之后判断是否大于分片大小(不展示进度条),大于就进入分片模式(并展示进度条),小于直接上传。并且我的是只负责分片,提交数据给后端,后端自己合并就行。
1、template部分
<!-- 上传学员视频文件 -->
<el-dialog
v-model="dialogVisible"
title="上传学员视频文件"
width="30%"
:before-close="closeDialog"
>
<el-form
ref="ruleFormRef"
:inline="true"
:rules="rules"
:model="formData"
>
<el-form-item label="视频文件:" prop="subjectId">
<el-upload
ref="upload"
:key="uploadKey"
:limit="1"
action="#"
:http-request="uploadChange"
accept=".mp4"
>
<!-- :http-request="uploadChange"上传前的回调,显示文件类型
action="#"上传的地址 -->
<template #trigger>
<el-button type="primary">上传视频文件</el-button>
</template>
</el-upload>
</el-form-item>
<div
class="demo-progress"
v-show="formData.file.size > formData.chunkSize"
>
<el-progress :stroke-width="10" :percentage="uploadProgress" />
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="syncMsg()">提交</el-button>
</span>
</template>
</el-dialog>
2、script部分
流程:必须有的【分片大小、当前上传的分片、上传的文件、总片数】、我的代码又后端要求每次需要唯一的文件名,这样它可以根据是否是同一个文件名对视频文件进行合并
const upUrl = settings.REST + "/api/stu-photos-mgt/xxx/"+route.query.id;
// 格式化进度,使用百分比进行展示
const uploadProgress = ref(0); // 进度条进度
const formData = reactive({
file: "", // 保存上传的文件
chunkSize: 10 * 1024 * 1024, // 分片大小,10MB
filename: "", // 文件名
});
const uploadChange = async (options) => {
console.log(options);
// 保存上传的文件
formData.file = options.file;
if(formData.file.size>=1024*1024*200){
ElMessage({message: "请上传小于200M的文件",type: "error"});
return options.onError("请上传小于200M的文件")
}
}
const ruleFormRef = ref();
const uploadKey = ref(Date.now());
const clearFormData = () => {
uploadKey.value = Date.now(); // 强制重新渲染 ElUpload
formData.file = {}; // 清空文件对象
formData.filename = ""; // 清空文件名
uploadProgress.value = 0; // 重置进度条
};
const closeDialog = () => {
dialogVisible.value = false;
clearFormData(); // 调用清空数据的方法
};
const generateUniqueFileName = () => {
// 生成唯一文件名,使用当前时间戳加上随机数
const timestamp = Date.now(); // 获取当前时间戳
const randomNum = Math.floor(Math.random() * 10000); // 生成一个随机数
return `file_${timestamp}_${randomNum}`; // 拼接生成文件名
};
// 先上传、再分片、再提交
const syncMsg = async () => {
if (!formData.file) {
ElMessage.error("请先上传文件");
return;
}
// 如果文件在分片大小以内,就直接上传
if (formData.file.size <= formData.chunkSize) {
try {
const formDataChunk = new FormData();
formDataChunk.append("uploadFile", formData.file);
let res = await axios({
url: `/api/stu-photos-mgt/xxxxxxx/${route.query.id}`,
method: "POST",
data: formDataChunk,
});
res.code == 200
? (ElMessage.success("上传成功"), (dialogVisible.value = false))
: ElMessage.error(res.message);
getData();
setTimeout(closeDialog, 500); // 延时清空数据,确保进度条到达 100%
} catch (error) {
ElMessage.error(error);
}
return;
} else {
// 如果文件超过分片大小,就分片上传
const file = formData.file; // 上传的文件
const totalChunks = Math.ceil(file.size / formData.chunkSize); // 计算总分片数
let currentChunk = 0; // 当前上传的分片
const uniqueFileName = generateUniqueFileName(); // 生成唯一文件名
// 分片上传逻辑
while (currentChunk < totalChunks) {
const start = currentChunk * formData.chunkSize;
const end = Math.min(file.size, start + formData.chunkSize);
const chunk = file.slice(start, end); // 切割文件
const formDataChunk = new FormData();
formDataChunk.append("uploadFile", chunk);
formDataChunk.append("filename", uniqueFileName); // 使用生成的唯一文件名--文件名
formDataChunk.append("index", currentChunk); // 分片序号,从 0 开始
formDataChunk.append("chunkSize", totalChunks); // 总分片数--分割文件数
try {
// 上传分片接口
let res = await axios({
url: `/api/stu-photos-mgt/xxxxxxx/${route.query.id}`,
method: "POST",
data: formDataChunk,
});
currentChunk++;
// 更新进度条
uploadProgress.value = Math.round((currentChunk / totalChunks) * 100);
if (uploadProgress.value < 100) {
// 稍微延迟增加进度条的数值,模拟平滑过渡
await new Promise(resolve => setTimeout(resolve, 50));
}
if (
(res.code == 200 || res.data.code == 1) &&
currentChunk === totalChunks - 1
) {
// 最后的进度条平滑到 100%
uploadProgress.value = 100;
await new Promise(resolve => setTimeout(resolve, 200)); // 延时显示100%的效果
ElMessage.success("上传成功");
dialogVisible.value = false;
getData();
closeDialog(); // 调用清空数据的方法
}
} catch (error) {
ElMessage.error("上传分片失败,请重试");
return;
}
}
}
};
3、css代码
.demo-progress .el-progress--line {
margin-bottom: 15px;
max-width: 600px;
}