Vue3前端发送多个文件的方法详解
作者:BillKu
这篇文章主要介绍了基于Vue3 Composition API实现多文件上传组件,支持拖放和文件选择,包含进度条、状态显示、删除、重试等功能,响应式设计与模拟上传效果提升用户体验,需要的朋友可以参考下
我将为你创建一个美观且功能完整的多文件上传组件,支持拖放上传、文件列表显示和进度条展示。
设计思路
- 使用Vue3的Composition API
- 实现拖放上传和传统文件选择两种方式
- 显示上传进度和文件信息
- 提供删除和重新上传功能
- 使用模拟上传效果展示进度
下面是完整的实现代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 多文件上传</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="external nofollow" >
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 800px;
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
header {
background: #4a6bdf;
color: white;
padding: 20px;
text-align: center;
}
h1 {
font-size: 1.8rem;
margin-bottom: 8px;
}
.subtitle {
font-size: 1rem;
opacity: 0.9;
}
.upload-container {
padding: 30px;
}
.upload-areas {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
@media (max-width: 650px) {
.upload-areas {
flex-direction: column;
}
}
.drag-drop-area {
flex: 1;
border: 2px dashed #4a6bdf;
border-radius: 12px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.drag-drop-area:hover, .drag-drop-area.dragover {
background: #f0f4ff;
transform: translateY(-2px);
}
.drag-icon {
font-size: 48px;
color: #4a6bdf;
margin-bottom: 15px;
}
.file-input-area {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 15px;
}
.select-button {
background: #4a6bdf;
color: white;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s;
display: inline-block;
}
.select-button:hover {
background: #3a5bc7;
}
input[type="file"] {
display: none;
}
.file-list {
margin-top: 30px;
}
.file-list-title {
font-size: 1.2rem;
margin-bottom: 15px;
color: #333;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.file-item {
display: flex;
align-items: center;
padding: 12px 15px;
border-radius: 8px;
margin-bottom: 10px;
background: #f9fafc;
transition: transform 0.2s;
}
.file-item:hover {
transform: translateX(5px);
background: #f0f4ff;
}
.file-icon {
color: #4a6bdf;
font-size: 24px;
margin-right: 15px;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 500;
margin-bottom: 5px;
word-break: break-word;
}
.file-size {
font-size: 0.8rem;
color: #777;
}
.file-progress {
width: 100%;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
margin-top: 8px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #4a6bdf, #6f42c1);
border-radius: 3px;
transition: width 0.3s ease;
}
.file-actions {
display: flex;
gap: 10px;
margin-left: 15px;
}
.action-btn {
background: none;
border: none;
color: #777;
cursor: pointer;
font-size: 16px;
transition: color 0.3s;
}
.action-btn:hover {
color: #4a6bdf;
}
.delete-btn:hover {
color: #e74c3c;
}
.upload-status {
font-size: 0.8rem;
margin-top: 5px;
color: #4a6bdf;
}
.success {
color: #2ecc71;
}
.error {
color: #e74c3c;
}
.empty-state {
text-align: center;
padding: 30px;
color: #777;
}
.empty-icon {
font-size: 48px;
margin-bottom: 15px;
color: #ccc;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<header>
<h1>Vue3 多文件上传</h1>
<div class="subtitle">支持拖放和选择文件,实时显示上传进度</div>
</header>
<div class="upload-container">
<div class="upload-areas">
<div
class="drag-drop-area"
@click="triggerFileInput"
@drop.prevent="onDrop"
@dragover.prevent="dragover = true"
@dragleave="dragover = false"
:class="{ 'dragover': dragover }"
>
<div class="drag-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<h3>拖放文件到此处</h3>
<p>或者点击选择文件</p>
</div>
<div class="file-input-area">
<p>选择多个文件进行上传</p>
<label class="select-button">
<i class="fas fa-folder-open"></i> 选择文件
<input
type="file"
multiple
@change="onFileSelected"
ref="fileInput"
>
</label>
<p>最大支持10个文件同时上传</p>
</div>
</div>
<div class="file-list">
<h3 class="file-list-title">文件列表</h3>
<div v-if="files.length === 0" class="empty-state">
<div class="empty-icon">
<i class="fas fa-file-import"></i>
</div>
<p>尚未选择任何文件</p>
</div>
<div v-else>
<div v-for="(file, index) in files" :key="index" class="file-item">
<div class="file-icon">
<i class="fas fa-file"></i>
</div>
<div class="file-info">
<div class="file-name">{{ file.name }}</div>
<div class="file-size">{{ formatFileSize(file.size) }}</div>
<div class="file-progress">
<div class="progress-bar" :style="{ width: file.progress + '%' }"></div>
</div>
<div class="upload-status" :class="file.status">
{{ getStatusText(file) }}
</div>
</div>
<div class="file-actions">
<button
v-if="file.status === 'error'"
class="action-btn"
@click="retryUpload(file)"
title="重新上传"
>
<i class="fas fa-redo"></i>
</button>
<button
class="action-btn delete-btn"
@click="removeFile(index)"
title="删除文件"
>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const files = ref([]);
const dragover = ref(false);
const fileInput = ref(null);
const triggerFileInput = () => {
fileInput.value.click();
};
const onFileSelected = (event) => {
const selectedFiles = Array.from(event.target.files);
addFiles(selectedFiles);
// 清空input,允许重复选择相同文件
event.target.value = '';
};
const onDrop = (event) => {
dragover.value = false;
const droppedFiles = Array.from(event.dataTransfer.files);
addFiles(droppedFiles);
};
const addFiles = (newFiles) => {
// 限制最多10个文件
if (files.value.length + newFiles.length > 10) {
alert('最多只能上传10个文件');
newFiles = newFiles.slice(0, 10 - files.value.length);
}
newFiles.forEach(file => {
files.value.push({
file: file,
name: file.name,
size: file.size,
progress: 0,
status: 'pending' // pending, uploading, success, error
});
});
// 开始上传所有新添加的文件
newFiles.forEach((_, index) => {
const actualIndex = files.value.length - newFiles.length + index;
simulateUpload(actualIndex);
});
};
const simulateUpload = (index) => {
files.value[index].status = 'uploading';
// 模拟上传进度
const interval = setInterval(() => {
if (files.value[index].progress < 95) {
files.value[index].progress += Math.floor(Math.random() * 10) + 5;
if (files.value[index].progress > 95) {
files.value[index].progress = 95;
}
}
}, 300);
// 模拟上传完成
setTimeout(() => {
clearInterval(interval);
// 随机决定上传成功或失败(实际应用中应根据真实上传结果)
const isSuccess = Math.random() > 0.2;
if (isSuccess) {
files.value[index].progress = 100;
files.value[index].status = 'success';
} else {
files.value[index].status = 'error';
}
}, 3000);
};
const removeFile = (index) => {
files.value.splice(index, 1);
};
const retryUpload = (file) => {
const index = files.value.indexOf(file);
if (index !== -1) {
files.value[index].progress = 0;
files.value[index].status = 'pending';
simulateUpload(index);
}
};
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const getStatusText = (file) => {
switch (file.status) {
case 'pending': return '等待上传';
case 'uploading': return `上传中... ${file.progress}%`;
case 'success': return '上传成功';
case 'error': return '上传失败';
default: return '';
}
};
return {
files,
dragover,
fileInput,
triggerFileInput,
onFileSelected,
onDrop,
removeFile,
retryUpload,
formatFileSize,
getStatusText
};
}
}).mount('#app');
</script>
</body>
</html>功能说明
文件选择方式:
- 拖放文件到指定区域上传
- 点击"选择文件"按钮选择多个文件
文件列表显示:
- 显示文件名和大小
- 实时显示上传进度条
- 显示上传状态(等待、上传中、成功、失败)
操作功能:
- 删除已选择的文件
- 重新上传失败的文件
响应式设计:
- 在移动设备上自动调整布局
实际应用提示
在实际项目中,你需要将simulateUpload函数替换为真实的上传逻辑,通常使用fetch或axios库来实现文件上传。真实上传代码可能类似于:
const realUpload = (fileItem) => {
const formData = new FormData();
formData.append('file', fileItem.file);
axios.post('/upload', formData, {
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
fileItem.progress = percentCompleted;
}
})
.then(response => {
fileItem.status = 'success';
})
.catch(error => {
fileItem.status = 'error';
});
};这个实现提供了良好的用户体验和直观的界面,你可以根据需要进一步扩展功能。
Vue3 多文件上传技术点解析
下面我将详细梳理Vue3中实现多文件上传的各项技术点,并提供一个完整的示例。
核心实现技术点
1. Composition API 使用
Vue3的Composition API提供了更好的代码组织和复用能力
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 响应式数据
const files = ref([])
const uploadProgress = reactive({})
const isUploading = ref(false)
// 计算属性
const totalProgress = computed(() => {
// 计算总上传进度
})
// 方法
const handleFileSelect = (event) => {
// 处理文件选择
}
return {
files,
uploadProgress,
isUploading,
totalProgress,
handleFileSelect
}
}
}2. 文件选择与处理
使用input元素和File API处理文件选择
const handleFileSelect = (event) => {
const selectedFiles = Array.from(event.target.files)
// 验证文件类型和大小
const validFiles = selectedFiles.filter(file => {
const maxSize = 10 * 1024 * 1024 // 10MB
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
return file.size <= maxSize && allowedTypes.includes(file.type)
})
// 添加到文件列表
files.value = [...files.value, ...validFiles]
}3. 拖放功能实现
使用HTML5拖放API
const handleDrop = (event) => {
event.preventDefault()
isDragging.value = false
const droppedFiles = Array.from(event.dataTransfer.files)
// 处理拖放的文件
}
const handleDragOver = (event) => {
event.preventDefault()
isDragging.value = true
}
const handleDragLeave = (event) => {
event.preventDefault()
isDragging.value = false
}4. 分块上传与进度跟踪
对于大文件,使用分块上传可以提高可靠性和用户体验
const uploadFile = async (file) => {
const chunkSize = 1024 * 1024 // 1MB
const totalChunks = Math.ceil(file.size / chunkSize)
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)
try {
const formData = new FormData()
formData.append('file', chunk)
formData.append('chunkIndex', chunkIndex)
formData.append('totalChunks', totalChunks)
formData.append('fileId', file.id) // 唯一文件标识
await axios.post('/upload-chunk', formData, {
onUploadProgress: (progressEvent) => {
// 更新上传进度
const chunkProgress = (progressEvent.loaded / progressEvent.total) * 100
updateFileProgress(file.id, chunkIndex, chunkProgress, totalChunks)
}
})
} catch (error) {
console.error('上传失败:', error)
// 处理错误和重试逻辑
}
}
// 所有分块上传完成后,通知服务器合并文件
await axios.post('/merge-chunks', {
fileId: file.id,
fileName: file.name,
totalChunks
})
}5. 并发控制
限制同时上传的文件数量
const MAX_CONCURRENT_UPLOADS = 3
const uploadAllFiles = async () => {
isUploading.value = true
// 创建上传队列
const uploadQueue = [...files.value]
const activeUploads = []
while (uploadQueue.length > 0 || activeUploads.length > 0) {
// 填充活动上传队列
while (activeUploads.length < MAX_CONCURRENT_UPLOADS && uploadQueue.length > 0) {
const file = uploadQueue.shift()
const uploadPromise = uploadFile(file).finally(() => {
// 上传完成后从活动队列中移除
activeUploads.splice(activeUploads.indexOf(uploadPromise), 1)
})
activeUploads.push(uploadPromise)
}
// 等待至少一个上传完成
if (activeUploads.length > 0) {
await Promise.race(activeUploads)
}
}
isUploading.value = false
}6. 取消上传与暂停/恢复
实现上传控制功能
// 为每个文件创建取消令牌
const cancelTokens = {}
const uploadFile = async (file) => {
const cancelToken = axios.CancelToken.source()
cancelTokens[file.id] = cancelToken
try {
await axios.post('/upload', formData, {
cancelToken: cancelToken.token,
onUploadProgress: (progressEvent) => {
// 更新进度
}
})
} catch (error) {
if (axios.isCancel(error)) {
console.log('上传已取消:', error.message)
} else {
console.error('上传错误:', error)
}
}
}
// 取消上传
const cancelUpload = (fileId) => {
if (cancelTokens[fileId]) {
cancelTokens[fileId].cancel('用户取消上传')
}
}
// 暂停和恢复需要更复杂的实现,通常需要记录已上传的部分7. 错误处理与重试机制
增强上传的可靠性
const uploadWithRetry = async (file, maxRetries = 3) => {
let retries = 0
while (retries <= maxRetries) {
try {
await uploadFile(file)
return // 上传成功,退出函数
} catch (error) {
retries++
if (retries > maxRetries) {
throw new Error(`上传失败: ${error.message}`)
}
// 等待一段时间后重试(指数退避)
const delay = Math.pow(2, retries) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}8. 文件预览与元数据展示
在上传前提供文件预览
const generateThumbnail = (file) => {
return new Promise((resolve) => {
if (!file.type.startsWith('image/')) {
resolve(null) // 非图片文件不生成预览
return
}
const reader = new FileReader()
reader.onload = (e) => {
resolve(e.target.result)
}
reader.readAsDataURL(file)
})
}
// 处理文件选择时生成预览
const handleFileSelect = async (event) => {
const selectedFiles = Array.from(event.target.files)
for (const file of selectedFiles) {
const thumbnail = await generateThumbnail(file)
files.value.push({
id: generateUniqueId(),
file,
name: file.name,
size: file.size,
type: file.type,
thumbnail,
progress: 0,
status: 'pending'
})
}
}9. 响应式UI与用户体验优化
根据上传状态更新UI
<template>
<div :class="['file-item', `status-${file.status}`]">
<div class="file-preview" v-if="file.thumbnail">
<img :src="file.thumbnail" :alt="file.name">
</div>
<div class="file-info">
<div class="file-name">{{ file.name }}</div>
<div class="file-size">{{ formatFileSize(file.size) }}</div>
<div class="file-progress">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${file.progress}%` }"
></div>
</div>
<div class="progress-text">{{ file.progress }}%</div>
</div>
</div>
<div class="file-actions">
<button
v-if="file.status === 'uploading'"
@click="cancelUpload(file.id)"
class="btn-cancel"
>
取消
</button>
<button
v-if="file.status === 'error'"
@click="retryUpload(file.id)"
class="btn-retry"
>
重试
</button>
<button
v-if="file.status !== 'uploading'"
@click="removeFile(file.id)"
class="btn-remove"
>
删除
</button>
</div>
</div>
</template>完整示例
下面是一个简化的完整示例,展示了Vue3多文件上传的实现:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 多文件上传示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.upload-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.drop-area {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 40px;
text-align: center;
margin-bottom: 20px;
transition: all 0.3s;
}
.drop-area.dragover {
border-color: #42b883;
background-color: rgba(66, 184, 131, 0.1);
}
.file-list {
margin-top: 20px;
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
border: 1px solid #eee;
border-radius: 6px;
margin-bottom: 10px;
}
.file-preview {
width: 50px;
height: 50px;
margin-right: 15px;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
overflow: hidden;
}
.file-preview img {
max-width: 100%;
max-height: 100%;
}
.file-info {
flex-grow: 1;
}
.file-name {
font-weight: 500;
margin-bottom: 5px;
}
.file-progress {
margin-top: 8px;
}
.progress-bar {
height: 6px;
background: #e9ecef;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #42b883;
transition: width 0.3s;
}
.file-actions {
margin-left: 15px;
}
button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 5px;
}
.btn-primary {
background: #42b883;
color: white;
}
.btn-cancel {
background: #ff4757;
color: white;
}
.btn-retry {
background: #ffa502;
color: white;
}
.btn-remove {
background: #a4b0be;
color: white;
}
.status-error {
border-color: #ff4757;
}
.status-success {
border-color: #42b883;
}
</style>
</head>
<body>
<div id="app">
<div class="upload-container">
<h1>Vue3 多文件上传</h1>
<div
class="drop-area"
:class="{ 'dragover': isDragging }"
@drop="onDrop"
@dragover.prevent="onDragOver"
@dragleave="onDragLeave"
>
<p>拖放文件到此处或</p>
<input
type="file"
multiple
@change="onFileSelect"
ref="fileInput"
style="display: none;"
>
<button class="btn-primary" @click="openFileDialog">选择文件</button>
</div>
<div v-if="files.length > 0">
<div class="file-list">
<div
v-for="file in files"
:key="file.id"
class="file-item"
:class="'status-' + file.status"
>
<div class="file-preview">
<img v-if="file.thumbnail" :src="file.thumbnail" :alt="file.name">
<span v-else>📄</span>
</div>
<div class="file-info">
<div class="file-name">{{ file.name }}</div>
<div class="file-size">{{ formatFileSize(file.size) }}</div>
<div class="file-progress" v-if="file.status !== 'success'">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: file.progress + '%' }"
></div>
</div>
<div>{{ file.progress }}%</div>
</div>
<div v-else>上传成功</div>
</div>
<div class="file-actions">
<button
v-if="file.status === 'uploading'"
@click="cancelUpload(file.id)"
class="btn-cancel"
>
取消
</button>
<button
v-if="file.status === 'error'"
@click="retryUpload(file.id)"
class="btn-retry"
>
重试
</button>
<button
@click="removeFile(file.id)"
class="btn-remove"
>
删除
</button>
</div>
</div>
</div>
<div style="margin-top: 20px;">
<button
class="btn-primary"
@click="uploadFiles"
:disabled="isUploading"
>
{{ isUploading ? '上传中...' : '开始上传' }}
</button>
<span style="margin-left: 15px;">
总进度: {{ totalProgress }}%
</span>
</div>
</div>
</div>
</div>
<script>
const { createApp, ref, reactive, computed } = Vue;
createApp({
setup() {
const files = ref([]);
const isUploading = ref(false);
const isDragging = ref(false);
const fileInput = ref(null);
// 模拟上传函数(实际项目中替换为真实上传逻辑)
const simulateUpload = (fileId) => {
return new Promise((resolve, reject) => {
const file = files.value.find(f => f.id === fileId);
if (!file) return;
file.status = 'uploading';
const interval = setInterval(() => {
if (file.progress < 95) {
file.progress += Math.random() * 15;
} else {
clearInterval(interval);
// 模拟成功或失败
setTimeout(() => {
if (Math.random() > 0.2) {
file.progress = 100;
file.status = 'success';
resolve();
} else {
file.status = 'error';
reject(new Error('上传失败'));
}
}, 500);
}
}, 300);
});
};
const openFileDialog = () => {
fileInput.value.click();
};
const onFileSelect = async (event) => {
const selectedFiles = Array.from(event.target.files);
await processFiles(selectedFiles);
event.target.value = ''; // 重置input
};
const onDrop = async (event) => {
event.preventDefault();
isDragging.value = false;
const droppedFiles = Array.from(event.dataTransfer.files);
await processFiles(droppedFiles);
};
const onDragOver = (event) => {
event.preventDefault();
isDragging.value = true;
};
const onDragLeave = (event) => {
event.preventDefault();
isDragging.value = false;
};
const processFiles = async (fileList) => {
for (const file of fileList) {
// 生成缩略图(如果是图片)
const thumbnail = await generateThumbnail(file);
files.value.push({
id: Date.now() + Math.random().toString(36).substr(2, 9),
file,
name: file.name,
size: file.size,
type: file.type,
thumbnail,
progress: 0,
status: 'pending'
});
}
};
const generateThumbnail = (file) => {
return new Promise((resolve) => {
if (!file.type.startsWith('image/')) {
resolve(null);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result);
};
reader.readAsDataURL(file);
});
};
const uploadFiles = async () => {
isUploading.value = true;
// 仅上传状态为pending的文件
const filesToUpload = files.value.filter(f => f.status === 'pending');
// 使用Promise.allSettled同时上传多个文件
const uploadPromises = filesToUpload.map(file => simulateUpload(file.id));
try {
await Promise.allSettled(uploadPromises);
console.log('所有文件上传完成');
} catch (error) {
console.error('上传过程中出错:', error);
} finally {
isUploading.value = false;
}
};
const cancelUpload = (fileId) => {
// 在实际项目中,这里应该取消正在进行的上传请求
const file = files.value.find(f => f.id === fileId);
if (file) {
file.status = 'cancelled';
}
};
const retryUpload = (fileId) => {
const file = files.value.find(f => f.id === fileId);
if (file) {
file.progress = 0;
file.status = 'pending';
simulateUpload(fileId);
}
};
const removeFile = (fileId) => {
files.value = files.value.filter(f => f.id !== fileId);
};
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const totalProgress = computed(() => {
if (files.value.length === 0) return 0;
const total = files.value.reduce((sum, file) => sum + file.progress, 0);
return Math.round(total / files.value.length);
});
return {
files,
isUploading,
isDragging,
fileInput,
openFileDialog,
onFileSelect,
onDrop,
onDragOver,
onDragLeave,
uploadFiles,
cancelUpload,
retryUpload,
removeFile,
formatFileSize,
totalProgress
};
}
}).mount('#app');
</script>
</body>
</html>关键技术点总结
- Composition API:使用Vue3的setup函数和ref/reactive管理状态
- 文件处理:通过File API处理用户选择的文件
- 拖放功能:利用HTML5拖放API实现拖放上传
- 预览生成:使用FileReader生成图片文件的缩略图预览
- 进度跟踪:模拟上传进度展示(实际项目中使用真实的上传进度事件)
- 并发控制:使用Promise.allSettled处理多个文件同时上传
- 响应式UI:根据文件状态动态更新界面
- 用户体验:提供取消、重试、删除等操作,增强交互性
这个实现涵盖了多文件上传的核心技术点,您可以根据实际需求进一步扩展和完善功能。
以上就是Vue3前端发送多个文件的方法详解的详细内容,更多关于Vue3发送多个文件的资料请关注脚本之家其它相关文章!
