应用效果:

实例代码: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();
    // 添加多个文件到 FormData
    for (const file of selectedFiles.value) {
      formData.append("uploadFiles", file);
    }
    // 添加受理编号到 FormData
    formData.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 };

关键改进点

  1. 类型注解

    • 为事件参数 event 添加了 DragEvent 类型

    • 为函数添加了明确的返回类型 (void)

    • 定义了自定义类型 DropEventHandler 和 AddFilesFunction

  2. 错误处理

    • 添加了空值检查,确保事件对象和 dataTransfer 存在

    • 使用 try-catch 块处理可能的异常

    • 添加了文件验证逻辑

  3. 文件验证

    • 添加了文件类型验证函数 isValidFileType

    • 添加了文件大小验证函数 isValidFileSize

    • 在 addFiles 函数中过滤无效文件

  4. 辅助功能

    • 添加了文件大小格式化函数 formatFileSize

    • 添加了拖放区域设置函数 setupDropZone

    • 提供了完整的视觉反馈处理

  5. 代码健壮性

    • 添加了详细的警告和错误日志

    • 提供了更完整的用户体验(如视觉反馈)

    • 考虑了边界情况(如空文件列表)

使用说明

这个 TypeScript 实现不仅转换了原始的 JavaScript 代码,还增加了类型安全性和错误处理。在实际项目中,您可能需要根据具体框架(如 Vue、React 或 Angular)调整实现方式。

如果您是在 Vue 3 中使用这个函数,可能需要将 dragover.value 替换为适当的响应式引用,并根据组件结构调整代码。

更多推荐