效果预览

image.png
用到的事件
@drop.prevent
- 事件类型:drop(当用户释放被拖拽元素到目标元素上时触发)
- 修饰符:.prevent(阻止浏览器默认行为,如打开文件链接)
@dragover.prevent
- 事件类型:dragover(当被拖拽元素在目标元素上移动时持续触发)
@dragleave.prevent
- 事件类型:dragleave(当被拖拽元素离开目标元素时触发)
具体代码
<template>
<div class="upload-content w-full h-full flex-1 flex-center"
:class="{ 'border-blue-500! bg-blue-50!': isDragging }"
@drop.prevent="handleDrop" @dragover.prevent="isDragging = true"
@dragleave.prevent="isDragging = false">
<div class="flex items-center flex-col w-full h-full gap-24px">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
<path
d="M33.729 4.16669H16.2707C8.68734 4.16669 4.1665 8.68752 4.1665 16.2709V33.7084C4.1665 41.3125 8.68734 45.8334 16.2707 45.8334H33.7082C41.2915 45.8334 45.8123 41.3125 45.8123 33.7292V16.2709C45.8332 8.68752 41.3123 4.16669 33.729 4.16669ZM17.6457 18.7084L23.8957 12.4584C24.0415 12.3125 24.2082 12.2084 24.3957 12.125C24.7707 11.9584 25.2082 11.9584 25.5832 12.125C25.7707 12.2084 25.9373 12.3125 26.0832 12.4584L32.3332 18.7084C32.9373 19.3125 32.9373 20.3125 32.3332 20.9167C32.0207 21.2292 31.6248 21.375 31.229 21.375C30.8332 21.375 30.4373 21.2292 30.1248 20.9167L26.5415 17.3334V30.2292C26.5415 31.0834 25.8332 31.7917 24.979 31.7917C24.1248 31.7917 23.4165 31.0834 23.4165 30.2292V17.3334L19.8332 20.9167C19.229 21.5209 18.229 21.5209 17.6248 20.9167C17.0207 20.3125 17.0415 19.3334 17.6457 18.7084ZM37.9998 35.875C33.8123 37.2709 29.4165 37.9792 24.9998 37.9792C20.5832 37.9792 16.1873 37.2709 11.9998 35.875C11.1873 35.6042 10.7498 34.7084 11.0207 33.8959C11.2915 33.0834 12.1873 32.625 12.9998 32.9167C20.7498 35.5 29.2707 35.5 37.0207 32.9167C37.8332 32.6459 38.729 33.0834 38.9998 33.8959C39.2498 34.7292 38.8123 35.6042 37.9998 35.875Z"
fill="#4E5969" />
</svg>
<div v-if="!uploading">
<p class="upload-text">
将文件拖拽到此区域,或
<label class="upload-click-text cursor-pointer">
点击添加
<input type="file" class="hidden" multiple
accept=".pptx,.ppt,.avi,.m4v,.mov,.mp4,.wmv,.jpeg,.jpg,.gif,.png,.mp3,.wav,.wma,.docx,.doc,.pdf,.xlsx,.xls,.txt"
@change="handleFileSelect" />
</label>
</p>
<p class="upload-text-tip">限制1G以内</p>
</div>
<div v-if="uploading">
<p class="upload-text flex items-center">
上传中,请稍侯 。。。
</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { commonUploadFile } from "@/fetch/courseWare.js";
const isDragging = ref(false);
const uploading = ref(false)
const selectedFile = ref([]) // 多文件上传
const uploadProgress = ref(0) // 进度百分比
const validateFile = (file) => {
if (!file.length) return false
const validTypes = [
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
'application/vnd.ms-powerpoint', // .ppt
'image/jpeg',
'image/jpg',
'image/gif',
'image/png',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'text/plain',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'video/avi',
'video/x-m4v',
'video/quicktime',
'video/mp4',
'video/x-ms-wmv',
'video/mpeg',
'audio/wav',
'audio/x-ms-wma',
'audio/mpeg'
]
for (let i = 0; i < file.length; i++) {
if (!validTypes.includes(file[i].type)) {
console.log('不支持这种文件类型,或大小超限。请检查类型和大小后再上传。')
return false
}
}
const maxSize = 1024 * 1024 * 1024 // 1GB
for (let i = 0; i < file.length; i++) {
if (file[i].size > maxSize) {
console.log('不支持这种文件类型,或大小超限。请检查类型和大小后再上传。')
return false
}
}
return true
}
const handleDrop = (event) => {
isDragging.value = false
const file = event.dataTransfer.files
if (validateFile(file)) {
for (let i = 0; i < file.length; i++) {
selectedFile.value.push({
file: file[i],
temporaryData: {}
})
}
handleUpload()
}
}
const handleFileSelect = (event) => {
const file = event.target.files
if (validateFile(file)) {
for (let i = 0; i < file.length; i++) {
selectedFile.value.push({
file: file[i],
temporaryData: {}
})
}
handleUpload()
}
}
const handleUpload = async () => {
uploading.value = true
uploadProgress.value = 0
const uploadPromises = [] // 存储所有上传任务的Promise
for (let i = 0; i < selectedFile.value.length; i++) {
const file = selectedFile.value[i].file
// 将每个上传操作封装为Promise
const uploadPromise = new Promise(async (resolve, reject) => {
const formData = new FormData()
formData.append('file', file)
formData.append("appName", "kg-smart-course-operating-pro");
formData.append("moduleName", "courseSpace");
const res = await commonUploadFile(formData, (percent) => {
// uploadProgress.value = percent
})
/**
* { "code": 200, "message": "success", "currentTime": 1742956627595, "data": { "fileName": "PPT 验证稿.ppt", "type": "doc", "fileId": "9648725", "url": "https://file.***.com/kg-smart-course-operating-pro/courseSpace/202503/efe43798432443c39b51f720e4ccffaa.ppt", } }
*/
if (res.status == 200 || res.code == '200') {
selectedFile.value[i].temporaryData = {
...res.data,
size: file.size,
suffix: res.data?.fileName?.split('.')[res.data?.fileName?.split('.').length - 1],
}
resolve() // 单个文件上传成功
} else {
ElMessage.error({ message: res.message })
reject() // 单个文件上传失败
}
})
uploadPromises.push(uploadPromise)
}
// 等待所有上传任务完成(无论成功/失败)
await Promise.allSettled(uploadPromises)
uploading.value = false
console.log('所有文件上传完成', selectedFile.value)
}
</script>
<style lang="scss" scoped>
.upload-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 32px;
height: 260px;
padding: 73px 16px 50px 16px;
border-radius: 16px;
border: 1px dashed #C9CDD4;
background: #F2F3F5;
.upload-text {
color: #4E5969;
text-align: center;
font-family: "Alibaba PuHuiTi 3.0";
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 28px;
}
.upload-click-text {
color: #23C343;
font-family: "Alibaba PuHuiTi 3.0";
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 28px;
}
.upload-text-tip {
color: #86909C;
text-align: center;
font-family: "Alibaba PuHuiTi 3.0";
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 27px;
margin-top: 8px;
}
}
</style>
上传大文件的一种解决方案
在前端开发中,接口超时时间(如5秒默认值)的调整是常见需求,尤其在处理大文件上传时。以下是技术实现要点及最佳实践:
- 超时机制原理
前端层面:通过HTTP客户端(如Axios、Fetch)设置timeout参数控制请求超时。例如Axios默认超时为0(无限制),但框架可能封装默认值(如5秒)。
后端层面:服务器(如Nginx、Express)也有超时配置(如proxy_read_timeout),需前后端协同调整。 - 前端代码实现
import axios from "axios";
const zidingyiFormDataInstance = axios.create({
headers: {
"Content-Type": "multipart/form-data;charset=UTF-8",
"Authorization": 'token' // 身份信息
},
withCredentials: false,
timeout: 1800000 // 毫秒 前端过期时间为 30分钟
});
export function postFormData(url, params, onProgress) {
return new Promise((resolve, reject) => {
zidingyiFormDataInstance.post(url, params, {
onUploadProgress: (progressEvent) => {
if (onProgress) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percentCompleted);
}
}
}).then((response) => {
resolve(response.data);
},
(err) => {
reject(err);
}
).catch((error) => {
reject(error);
});
});
}
// 上传
export const commonUploadFile = (params, onProgress) => {
return postFormData(`https://base-upload.com/upload/commonUploadFile`, params, onProgress)
}
- 动态超时策略
按文件大小计算:根据公式超时时间 = 基础时间 + 文件大小 * 单位系数动态设置。例如:
const baseTimeout = 60000; // 基础60秒
const perMBTimeout = 1000; // 每MB增加1秒
const timeout = baseTimeout + (file.size / (1024 * 1024)) * perMBTimeout;