效果如下:
视频组件上传效果图
1.视频上传组件全部代码(包括删除,拖拽,中断功能),不过由于文件没有进行切片上传,下篇文章会更新文件切片上传
<template>
<div class="content">
<div
class="n-upload"
v-for="(item, index) in imgs"
:key="item"
>
<video
class="video"
controls
disablepictureinpicture
:src="item"
></video>
<div
class="delete"
@click="deleteImg(index)"
>
<el-icon color="#fff">
<Delete />
</el-icon>
</div>
</div>
<div
class="progress"
v-if="isUplaoding"
>
<el-progress
type="circle"
:percentage="progress"
/>
<div
class="cancel"
@click="cancel"
>
<el-icon
:size="20"
color="gray"
>
<Close />
</el-icon>
</div>
</div>
<div
ref="loadFile"
class="upload-file"
v-show="!isDisableUploadFile"
@dragover.prevent
@drop.prevent="dragUploadFile"
@click="clickUploadFile"
>
<el-icon
:size="20"
color="gray"
>
<Plus />
</el-icon>
</div>
<input
type="file"
ref="file"
style="display: none"
accept=".mp4,.avi"
@change="realUploadFile"
/>
</div>
</template>
<script setup lang="ts">
import axios from "axios";
import { ElMessage } from "element-plus";
import { computed, ref } from "vue";
let file = ref<HTMLInputElement>();
let isUplaoding = ref<Boolean>(false);
let imgs = ref<Array<string>>([]);
let progress = ref<number | undefined>(0);
// 取消请求
let controller: AbortController;
const cancel = () => {
controller.abort();
progress.value = 0;
isUplaoding.value = false;
};
// 点击上传
const clickUploadFile = () => {
file.value?.click();
};
const realUploadFile = (e: any) => {
uploadFile(e.target.files[0]);
};
// 拖拽上传
const dragUploadFile = (e: DragEvent) => {
uploadFile(e.dataTransfer?.files[0]);
};
// 删除图片
const deleteImg = (index: number) => {
imgs.value.splice(index, 1);
};
const uploadFile = async (fileBlob: Blob | undefined) => {
controller = new AbortController();
if (typeof fileBlob == "undefined") {
return;
}
isUplaoding.value = true;
let formData = new FormData();
formData.append("file", fileBlob);
try {
let res = await axios.post("/api/file/uploadSingleFile", formData, {
onUploadProgress: (processEvent) => {
progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
},
signal: controller.signal,
});
progress.value = 100;
imgs.value.push(res.data.data);
ElMessage({
type: "success",
message: "上传成功",
});
} catch {
} finally {
isUplaoding.value = false;
progress.value = 0;
}
};
// 是否禁止上传
const isDisableUploadFile = computed(() => {
return imgs.value.length >= 2 || isUplaoding.value;
});
</script>
<style scoped lang="scss">
*:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.upload-file,
.video {
width: 150px;
height: 100px;
border: 2px dotted gray;
border-radius: 2px;
background-color: rgb(240, 243, 245);
cursor: pointer;
}
.cancel,
.delete {
position: absolute;
top: -10px;
right: -10px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
text-align: center;
line-height: 20px;
background-color: #000;
}
.content {
display: flex;
column-gap: 10px;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
.n-upload {
display: flex;
column-gap: 10px;
position: relative;
height: 100px;
}
.progress {
width: 100px;
height: 100px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
:deep(.el-progress-circle) {
width: 80px !important;
height: 80px !important;
}
}
.upload-file {
width: 100px;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
2.项目主要功能讲解
2.1 点击上传,其实主要通过input标签来实现,但是要控制样式隐藏,然后设置一个change事件就可以获取用户选中的文件啦
<input
type="file"
ref="file"
style="display: none"
accept=".mp4,.avi"
@change="realUploadFile"
/>
<div
ref="loadFile"
class="upload-file"
v-show="!isDisableUploadFile"
@dragover.prevent
@drop.prevent="dragUploadFile"
@click="clickUploadFile"
>
<el-icon
:size="20"
color="gray"
>
<Plus />
</el-icon>
</div>
这个才是展示给用户的标签,并绑定一个点击事件,但是其实触发的是input标签的点击事件,用户选择文件后,会触发input事件的change事件
let file = ref<HTMLInputElement>();
// 点击上传
const clickUploadFile = () => {
file.value?.click();
};
const realUploadFile = (e: any) => {
uploadFile(e.target.files[0]);
};
const uploadFile = async (fileBlob: Blob | undefined) => {
controller = new AbortController();
if (typeof fileBlob == "undefined") {
return;
}
isUplaoding.value = true;
let formData = new FormData();
formData.append("file", fileBlob);
try {
let res = await axios.post("/api/file/uploadSingleFile", formData, {
onUploadProgress: (processEvent) => {
progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
},
signal: controller.signal,
});
progress.value = 100;
imgs.value.push(res.data.data);
ElMessage({
type: "success",
message: "上传成功",
});
} catch {
} finally {
isUplaoding.value = false;
progress.value = 0;
}
};
然后将上传文件封装为一个方法,当然这里的提示信息给的并不完全,没有给出错误提示,可以自行完善
除此以外其实这里的进度条的精度还存在一些问题
2.2 拖拽功能,其实完成了点击上传以后,拖拽就比较简单了,绑定dragover 和drop事件就可以了,但是要阻止默认事件,否则拖拽后,页面就会发生跳转
// 拖拽上传
const dragUploadFile = (e: DragEvent) => {
uploadFile(e.dataTransfer?.files[0]);
};
2.3 取消上传,可以利用axios的AbortController的实例对象来取消,注意:controller对象不能全局定义,而是在不同的请求中,不断的创建对象,并且signal等于请求的标识,取消请求也是基于标识。
// 取消请求
let controller: AbortController;
const cancel = () => {
controller.abort();
progress.value = 0;
isUplaoding.value = false;
};
controller = new AbortController();
signal: controller.signal,
2.4 删除视频,这个比较简单,根据下标来删除imgs的路径就可以了
2.5 还有一个视频上传进度, 在axios中配置一个处理原生的进度事件, 现存bug: 进度的精度存在问题
{
onUploadProgress: (processEvent) => {
progress.value = Number(processEvent.progress?.toFixed(2)) * 100;
},
signal: controller.signal,
});
这里主要参考的知识来源于b站:【面试至少多加3k】基于原生js和node实现文件上传和大文件切片上传_哔哩哔哩_bilibili