基础实现原理
1. 核心 HTML5 特性
// 关键属性:
<input type="file" webkitdirectory multiple>
webkitdirectory
: 启用文件夹选择模式(Chrome/Edge)multiple
: 允许选择多个文件(自动包含文件夹内所有文件)- 注意:Firefox 需使用
mozdirectory
,Safari 支持有限
2. **文件数据结构
File 对象包含关键属性:
- name: 文件名(不含路径)
- webkitRelativePath: 相对路径(如 "docs/2024/report.pdf")
- size: 文件大小(字节)
- type: MIME 类型
Vue 基础实现
1. 组件模板
<template>
<div>
<button @click="triggerUpload">上传文件夹</button>
<input
ref="fileInput"
type="file"
webkitdirectory
multiple
@change="handleUpload"
style="display: none"
>
</div>
</template>
2. 核心逻辑
export default {
methods: {
triggerUpload() {
this.$refs.fileInput.click();
},
async handleUpload(e) {
const files = Array.from(e.target.files);
// 创建 FormData
const formData = new FormData();
files.forEach(file => {
// 参数说明:
// 1. 'files[]' - 服务器接收的字段名(数组形式)
// 2. file - File 对象
// 3. file.webkitRelativePath - 保留目录结构
formData.append('files[]', file, file.webkitRelativePath);
});
try {
await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: progress => {
const percent = Math.round((progress.loaded * 100) / progress.total);
console.log(`进度: ${percent}%`);
}
});
alert('上传成功!');
} catch (error) {
console.error('上传失败:', error);
}
}
}
}
常见问题解决方案
1. 浏览器兼容性问题
// 统一处理目录属性
mounted() {
const input = this.$refs.realInput;
if ('directory' in input) {
input.setAttribute('directory', '');
} else if ('webkitdirectory' in input) {
input.setAttribute('webkitdirectory', '');
}
}
2. 文件类型过滤
handleRealChange(e) {
const files = Array.from(e.target.files).filter(file => {
const allowedTypes = ['image/jpeg', 'application/pdf'];
const maxSize = 10 * 1024 * 1024; // 10MB
return allowedTypes.includes(file.type) && file.size <= maxSize;
});
if (files.length < e.target.files.length) {
this.$message.warning('包含不支持的文件类型');
}
// ...后续处理
}
3. 大文件分片上传
async uploadLargeFile(file) {
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB/片
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const chunk = file.slice(i * CHUNK_SIZE, (i+1) * CHUNK_SIZE);
const formData = new FormData();
formData.append('chunk_index', i);
formData.append('total_chunks', totalChunks);
formData.append('file_path', file.webkitRelativePath);
formData.append('chunk', chunk);
await axios.post('/api/chunk-upload', formData);
}
// 合并请求
await axios.post('/api/merge-files', {
file_path: file.webkitRelativePath,
total_chunks: totalChunks
});
}
最佳实践建议
-
用户体验优化
- 添加拖拽上传支持
<div class="drop-zone" @dragover.prevent @drop.prevent="handleDrop" > 拖拽文件夹到此区域 </div> methods: { handleDrop(e) { const files = e.dataTransfer.files; this.processFiles(files); } }
-
安全防护
- 文件类型白名单验证
- 扫描文件内容(如防病毒)
- 设置合理的文件大小限制
-
进度显示增强
<el-progress
:percentage="uploadPercent"
:status="uploadStatus"
:stroke-width="16"
></el-progress>
// 数据项
data() {
return {
uploadPercent: 0,
uploadStatus: 'success' // 状态:success/exception
}
}
浏览器支持列表
浏览器 | 支持情况 | 备注 |
---|---|---|
Chrome 78+ | ✅ 完整支持 | 最佳实践浏览器 |
Edge 79+ | ✅ 完整支持 | |
Firefox 50+ | ⚠️ 需使用 mozdirectory | 需要额外兼容代码 |
Safari 11+ | ⚠️ 部分支持 | 可能丢失目录结构 |
移动端浏览器 | ❌ 不支持 | 需改用 ZIP 上传方案 |
本总结涵盖了从基础实现到企业级优化的完整知识体系,建议根据实际需求选择合适方案,开发时特别注意浏览器兼容性和服务器安全防护。