Vue3前端發(fā)送多個(gè)文件的方法詳解
我將為你創(chuàng)建一個(gè)美觀且功能完整的多文件上傳組件,支持拖放上傳、文件列表顯示和進(jìn)度條展示。
設(shè)計(jì)思路
- 使用Vue3的Composition API
- 實(shí)現(xiàn)拖放上傳和傳統(tǒng)文件選擇兩種方式
- 顯示上傳進(jìn)度和文件信息
- 提供刪除和重新上傳功能
- 使用模擬上傳效果展示進(jìn)度
下面是完整的實(shí)現(xiàn)代碼:
<!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" 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">支持拖放和選擇文件,實(shí)時(shí)顯示上傳進(jìn)度</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>或者點(diǎn)擊選擇文件</p>
</div>
<div class="file-input-area">
<p>選擇多個(gè)文件進(jìn)行上傳</p>
<label class="select-button">
<i class="fas fa-folder-open"></i> 選擇文件
<input
type="file"
multiple
@change="onFileSelected"
ref="fileInput"
>
</label>
<p>最大支持10個(gè)文件同時(shí)上傳</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,允許重復(fù)選擇相同文件
event.target.value = '';
};
const onDrop = (event) => {
dragover.value = false;
const droppedFiles = Array.from(event.dataTransfer.files);
addFiles(droppedFiles);
};
const addFiles = (newFiles) => {
// 限制最多10個(gè)文件
if (files.value.length + newFiles.length > 10) {
alert('最多只能上傳10個(gè)文件');
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';
// 模擬上傳進(jìn)度
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);
// 隨機(jī)決定上傳成功或失?。▽?shí)際應(yīng)用中應(yīng)根據(jù)真實(shí)上傳結(jié)果)
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>功能說(shuō)明
文件選擇方式:
- 拖放文件到指定區(qū)域上傳
- 點(diǎn)擊"選擇文件"按鈕選擇多個(gè)文件
文件列表顯示:
- 顯示文件名和大小
- 實(shí)時(shí)顯示上傳進(jìn)度條
- 顯示上傳狀態(tài)(等待、上傳中、成功、失敗)
操作功能:
- 刪除已選擇的文件
- 重新上傳失敗的文件
響應(yīng)式設(shè)計(jì):
- 在移動(dòng)設(shè)備上自動(dòng)調(diào)整布局
實(shí)際應(yīng)用提示
在實(shí)際項(xiàng)目中,你需要將simulateUpload函數(shù)替換為真實(shí)的上傳邏輯,通常使用fetch或axios庫(kù)來(lái)實(shí)現(xiàn)文件上傳。真實(shí)上傳代碼可能類似于:
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';
});
};這個(gè)實(shí)現(xiàn)提供了良好的用戶體驗(yàn)和直觀的界面,你可以根據(jù)需要進(jìn)一步擴(kuò)展功能。
Vue3 多文件上傳技術(shù)點(diǎn)解析
下面我將詳細(xì)梳理Vue3中實(shí)現(xiàn)多文件上傳的各項(xiàng)技術(shù)點(diǎn),并提供一個(gè)完整的示例。
核心實(shí)現(xiàn)技術(shù)點(diǎn)
1. Composition API 使用
Vue3的Composition API提供了更好的代碼組織和復(fù)用能力
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 響應(yīng)式數(shù)據(jù)
const files = ref([])
const uploadProgress = reactive({})
const isUploading = ref(false)
// 計(jì)算屬性
const totalProgress = computed(() => {
// 計(jì)算總上傳進(jìn)度
})
// 方法
const handleFileSelect = (event) => {
// 處理文件選擇
}
return {
files,
uploadProgress,
isUploading,
totalProgress,
handleFileSelect
}
}
}2. 文件選擇與處理
使用input元素和File API處理文件選擇
const handleFileSelect = (event) => {
const selectedFiles = Array.from(event.target.files)
// 驗(yàn)證文件類型和大小
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. 拖放功能實(shí)現(xiàn)
使用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. 分塊上傳與進(jìn)度跟蹤
對(duì)于大文件,使用分塊上傳可以提高可靠性和用戶體驗(yàn)
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) // 唯一文件標(biāo)識(shí)
await axios.post('/upload-chunk', formData, {
onUploadProgress: (progressEvent) => {
// 更新上傳進(jìn)度
const chunkProgress = (progressEvent.loaded / progressEvent.total) * 100
updateFileProgress(file.id, chunkIndex, chunkProgress, totalChunks)
}
})
} catch (error) {
console.error('上傳失敗:', error)
// 處理錯(cuò)誤和重試邏輯
}
}
// 所有分塊上傳完成后,通知服務(wù)器合并文件
await axios.post('/merge-chunks', {
fileId: file.id,
fileName: file.name,
totalChunks
})
}5. 并發(fā)控制
限制同時(shí)上傳的文件數(shù)量
const MAX_CONCURRENT_UPLOADS = 3
const uploadAllFiles = async () => {
isUploading.value = true
// 創(chuàng)建上傳隊(duì)列
const uploadQueue = [...files.value]
const activeUploads = []
while (uploadQueue.length > 0 || activeUploads.length > 0) {
// 填充活動(dòng)上傳隊(duì)列
while (activeUploads.length < MAX_CONCURRENT_UPLOADS && uploadQueue.length > 0) {
const file = uploadQueue.shift()
const uploadPromise = uploadFile(file).finally(() => {
// 上傳完成后從活動(dòng)隊(duì)列中移除
activeUploads.splice(activeUploads.indexOf(uploadPromise), 1)
})
activeUploads.push(uploadPromise)
}
// 等待至少一個(gè)上傳完成
if (activeUploads.length > 0) {
await Promise.race(activeUploads)
}
}
isUploading.value = false
}6. 取消上傳與暫停/恢復(fù)
實(shí)現(xiàn)上傳控制功能
// 為每個(gè)文件創(chuàng)建取消令牌
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) => {
// 更新進(jìn)度
}
})
} catch (error) {
if (axios.isCancel(error)) {
console.log('上傳已取消:', error.message)
} else {
console.error('上傳錯(cuò)誤:', error)
}
}
}
// 取消上傳
const cancelUpload = (fileId) => {
if (cancelTokens[fileId]) {
cancelTokens[fileId].cancel('用戶取消上傳')
}
}
// 暫停和恢復(fù)需要更復(fù)雜的實(shí)現(xiàn),通常需要記錄已上傳的部分7. 錯(cuò)誤處理與重試機(jī)制
增強(qiáng)上傳的可靠性
const uploadWithRetry = async (file, maxRetries = 3) => {
let retries = 0
while (retries <= maxRetries) {
try {
await uploadFile(file)
return // 上傳成功,退出函數(shù)
} catch (error) {
retries++
if (retries > maxRetries) {
throw new Error(`上傳失敗: ${error.message}`)
}
// 等待一段時(shí)間后重試(指數(shù)退避)
const delay = Math.pow(2, retries) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}8. 文件預(yù)覽與元數(shù)據(jù)展示
在上傳前提供文件預(yù)覽
const generateThumbnail = (file) => {
return new Promise((resolve) => {
if (!file.type.startsWith('image/')) {
resolve(null) // 非圖片文件不生成預(yù)覽
return
}
const reader = new FileReader()
reader.onload = (e) => {
resolve(e.target.result)
}
reader.readAsDataURL(file)
})
}
// 處理文件選擇時(shí)生成預(yù)覽
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. 響應(yīng)式UI與用戶體驗(yàn)優(yōu)化
根據(jù)上傳狀態(tài)更新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>完整示例
下面是一個(gè)簡(jiǎn)化的完整示例,展示了Vue3多文件上傳的實(shí)現(xiàn):
<!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;">
總進(jìn)度: {{ 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);
// 模擬上傳函數(shù)(實(shí)際項(xiàng)目中替換為真實(shí)上傳邏輯)
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;
// 僅上傳狀態(tài)為pending的文件
const filesToUpload = files.value.filter(f => f.status === 'pending');
// 使用Promise.allSettled同時(shí)上傳多個(gè)文件
const uploadPromises = filesToUpload.map(file => simulateUpload(file.id));
try {
await Promise.allSettled(uploadPromises);
console.log('所有文件上傳完成');
} catch (error) {
console.error('上傳過(guò)程中出錯(cuò):', error);
} finally {
isUploading.value = false;
}
};
const cancelUpload = (fileId) => {
// 在實(shí)際項(xiàng)目中,這里應(yīng)該取消正在進(jìn)行的上傳請(qǐng)求
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>關(guān)鍵技術(shù)點(diǎn)總結(jié)
- Composition API:使用Vue3的setup函數(shù)和ref/reactive管理狀態(tài)
- 文件處理:通過(guò)File API處理用戶選擇的文件
- 拖放功能:利用HTML5拖放API實(shí)現(xiàn)拖放上傳
- 預(yù)覽生成:使用FileReader生成圖片文件的縮略圖預(yù)覽
- 進(jìn)度跟蹤:模擬上傳進(jìn)度展示(實(shí)際項(xiàng)目中使用真實(shí)的上傳進(jìn)度事件)
- 并發(fā)控制:使用Promise.allSettled處理多個(gè)文件同時(shí)上傳
- 響應(yīng)式UI:根據(jù)文件狀態(tài)動(dòng)態(tài)更新界面
- 用戶體驗(yàn):提供取消、重試、刪除等操作,增強(qiáng)交互性
這個(gè)實(shí)現(xiàn)涵蓋了多文件上傳的核心技術(shù)點(diǎn),您可以根據(jù)實(shí)際需求進(jìn)一步擴(kuò)展和完善功能。
以上就是Vue3前端發(fā)送多個(gè)文件的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3發(fā)送多個(gè)文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 項(xiàng)目中實(shí)現(xiàn)按鈕防抖方法
這篇文章主要介紹了vue 項(xiàng)目中實(shí)現(xiàn)按鈕防抖方法,首先需要新建 .js文件存放防抖方法,引入防抖文件,methods中添加方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
vue兩組件間值傳遞 $router.push實(shí)現(xiàn)方法
兩組件間傳值,可能包含多種情況,這篇文章主要介紹了vue兩組件間值傳遞 $router.push實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
vue實(shí)現(xiàn)設(shè)置載入動(dòng)畫和初始化頁(yè)面動(dòng)畫效果
今天小編就為大家分享一篇vue實(shí)現(xiàn)設(shè)置載入動(dòng)畫和初始化頁(yè)面動(dòng)畫效果,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10
解決Vue 通過(guò)下表修改數(shù)組,頁(yè)面不渲染的問(wèn)題
下面小編就為大家分享一篇解決Vue 通過(guò)下表修改數(shù)組,頁(yè)面不渲染的問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Vue引入Stylus知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給各位整理的是一篇關(guān)于Vue引入Stylus知識(shí)點(diǎn)總結(jié)內(nèi)容,有需要的朋友們可以學(xué)習(xí)參考下。2020-01-01

