应用效果:
实例代码:CommonApplyBasicInfoForm.vue
<script setup lang="ts" name="CommonApplyBasicInfoForm">
......
// 选择文件列表
const selectedFiles = ref<FileList | null>(null);
// 通过 FormData 对象实现文件上传
let formData = new FormData();
// 受理文件列表
const acceptFiles = ref<ApplyBasicFileVO[]>([]);// 拖放事件处理函数,拖放文件,轻开鼠标左键触发
const onDrop = async (e: DragEvent) => {// 获取文件对象列表(拖放的文件列表)if (e.dataTransfer) selectedFiles.value = e.dataTransfer.files;// 判断文件列表是否为空if (!selectedFiles.value?.length) {ElMessage.warning("请选择至少一个文件");return;}try {// 清空 FormData 表单数据的内容:重新赋值,创建新实例,旧数据被丢弃(完全清空),需要使用 let 声明对象,不能使用 const 声明对象formData = new FormData();// 添加多个文件到 FormDatafor (const file of selectedFiles.value) {formData.append("uploadFiles", file);}// 添加受理编号到 FormDataformData.append("outerApplyId", applyBasicInfo.value.outerApplyId);// 发送请求,上传多个文件到后端服务器await applyBasicInfoUploadFilesService(formData);ElMessage.success("文件上传成功!");// 获取已上传的文件列表await fetchUploadedFiles(applyBasicInfo.value.outerApplyId);} catch (error) {ElMessage.error("文件上传失败!");}
};// 获取已上传的文件列表
const fetchUploadedFiles = async (outerApplyId: string) => {const result = await applyBasicInfoQueryFilesService(outerApplyId);acceptFiles.value = result.data;
};
......
</script><template>
......<!-- 拖拽上传区域 --><!-- @drop.prevent="onDrop":监听拖拽放置事件,阻止默认行为并调用onDrop方法 --><!-- @dragover.prevent:监听拖拽悬停事件并阻止默认行为(某些浏览器默认打开拖放的文件),也可以写为 @dragover.prevent="" 或 @dragover.prevent = "dragover = true" 或 @dragover.prevent = "method" --><!-- @dragleave.prevent:监听拖拽离开事件并阻止默认行为,也可以写为 @dragleave.prevent="" 或 @dragleave.prevent = "dragover = false" 或 @dragleave.prevent = "method" --><!-- @dragleave="":监听拖拽离开事件,也可以写成 @dragleave="false" 或 @dragleave="dragover = false" 或 @dragleave="method"--><div class="drag-drop-area" @drop.prevent="onDrop" @dragover.prevent><div class="drag-icon"><!-- <i class="fas fa-cloud-upload-alt"></i> --><el-icon color="#4a6bdf" size="60"><MostlyCloudy /></el-icon></div><h3>拖放文件到此处</h3><p>或者点击选择文件</p></div>
......
</template><style scoped lang="scss">
......
.drag-drop-area {flex: 1;border: 2px dashed #4a6bdf;border-radius: 12px;padding: 10px;text-align: center;cursor: pointer;transition: all 0.3s ease;
}.drag-drop-area:hover{// transform 对元素进行2D或3D变换,常用于微调元素位置或解决某些浏览器渲染问题,属于CSS3变换属性,不会影响文档流和其他元素布局// translateX(1px) 表示在X轴方向上平移1像素,正值表示向下移动,负值表示向上移动// translateY(1px) 表示在Y轴方向上平移1像素,正值表示向右移动,负值表示向左移动transform: translateY(-1px);background: #f0f4ff;
}.drag-icon {font-size: 48px;color: #4a6bdf;margin-bottom: 15px;
}
......
</style>
TypeScript :拖放事件处理函数
typescript
// 定义事件处理函数类型 type DropEventHandler = (event: DragEvent) => void;// 假设这是在Vue 3 Composition API环境中的代码 // 定义响应式变量类型 interface ReactiveState {dragover: { value: boolean }; }// 定义文件处理函数类型 type AddFilesFunction = (files: File[]) => void;// 完整的onDrop函数实现 const onDrop: DropEventHandler = (event: DragEvent): void => {// 确保事件对象存在if (!event) {console.error('Drop event is undefined or null');return;}// 确保dataTransfer对象存在if (!event.dataTransfer) {console.error('Data transfer is not available in this event');return;}// 防止默认行为(某些浏览器可能会尝试打开拖放的文件)event.preventDefault();// 更新响应式状态(假设这是在Vue组件中)// 这里假设dragover是一个ref对象,包含value属性dragover.value = false;try {// 获取拖放的文件列表并转换为数组const droppedFiles: File[] = Array.from(event.dataTransfer.files);// 验证是否确实有文件if (droppedFiles.length === 0) {console.warn('No files were dropped');return;}// 调用处理函数addFiles(droppedFiles);} catch (error) {console.error('Error processing dropped files:', error);} };// 辅助函数:验证文件类型 const isValidFileType = (file: File, allowedTypes?: string[]): boolean => {if (!allowedTypes || allowedTypes.length === 0) {return true; // 如果没有限制,所有文件类型都有效}return allowedTypes.some(type => {// 支持通配符,如 "image/*"if (type.endsWith('/*')) {const category = type.split('/')[0];return file.type.startsWith(`${category}/`);}return file.type === type;}); };// 辅助函数:验证文件大小 const isValidFileSize = (file: File, maxSize?: number): boolean => {if (!maxSize) {return true; // 如果没有大小限制,所有文件都有效}return file.size <= maxSize; };// 增强版的addFiles函数(带验证) const addFiles: AddFilesFunction = (files: File[]): void => {// 定义允许的文件类型和最大文件大小const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'text/plain'];const maxFileSize = 10 * 1024 * 1024; // 10MB// 过滤有效文件const validFiles = files.filter(file => isValidFileType(file, allowedTypes) && isValidFileSize(file, maxFileSize));// 处理无效文件const invalidFiles = files.filter(file => !isValidFileType(file, allowedTypes) || !isValidFileSize(file, maxFileSize));// 记录或处理无效文件if (invalidFiles.length > 0) {console.warn(`${invalidFiles.length} files were rejected due to type or size restrictions`);// 可以在这里显示用户通知invalidFiles.forEach(file => {if (!isValidFileType(file, allowedTypes)) {console.warn(`File ${file.name} has an invalid type: ${file.type}`);} else if (!isValidFileSize(file, maxFileSize)) {console.warn(`File ${file.name} exceeds the size limit: ${formatFileSize(file.size)}`);}});}// 如果有有效文件,继续处理if (validFiles.length > 0) {// 实际的文件处理逻辑console.log(`Processing ${validFiles.length} valid files`);// 这里可以调用上传函数或其他处理逻辑// uploadFiles(validFiles);} };// 格式化文件大小的辅助函数 const formatFileSize = (bytes: number): string => {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 setupDropZone = (element: HTMLElement): void => {// 防止默认的拖放行为const preventDefault = (e: DragEvent) => {e.preventDefault();e.stopPropagation();};// 设置事件监听器element.addEventListener('dragenter', preventDefault);element.addEventListener('dragover', preventDefault);element.addEventListener('dragleave', preventDefault);element.addEventListener('drop', onDrop);// 添加视觉反馈element.addEventListener('dragenter', () => {dragover.value = true;element.classList.add('drag-over');});element.addEventListener('dragleave', () => {dragover.value = false;element.classList.remove('drag-over');});element.addEventListener('drop', () => {dragover.value = false;element.classList.remove('drag-over');}); };// 假设的Vue 3组件中使用示例 /* import { ref } from 'vue';export default {setup() {const dragover = ref(false);// 在模板渲染后设置拖放区域onMounted(() => {const dropZone = document.getElementById('drop-zone');if (dropZone) {setupDropZone(dropZone);}});return {dragover};} }; */// 导出函数和类型(如果使用模块系统) export type { DropEventHandler, AddFilesFunction }; export { onDrop, addFiles, setupDropZone, isValidFileType, isValidFileSize, formatFileSize };
关键改进点
类型注解:
为事件参数
event
添加了DragEvent
类型为函数添加了明确的返回类型 (
void
)定义了自定义类型
DropEventHandler
和AddFilesFunction
错误处理:
添加了空值检查,确保事件对象和
dataTransfer
存在使用 try-catch 块处理可能的异常
添加了文件验证逻辑
文件验证:
添加了文件类型验证函数
isValidFileType
添加了文件大小验证函数
isValidFileSize
在
addFiles
函数中过滤无效文件
辅助功能:
添加了文件大小格式化函数
formatFileSize
添加了拖放区域设置函数
setupDropZone
提供了完整的视觉反馈处理
代码健壮性:
添加了详细的警告和错误日志
提供了更完整的用户体验(如视觉反馈)
考虑了边界情况(如空文件列表)
使用说明
这个 TypeScript 实现不仅转换了原始的 JavaScript 代码,还增加了类型安全性和错误处理。在实际项目中,您可能需要根据具体框架(如 Vue、React 或 Angular)调整实现方式。
如果您是在 Vue 3 中使用这个函数,可能需要将 dragover.value
替换为适当的响应式引用,并根据组件结构调整代码。