應用效果:
實例代碼: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
?替換為適當的響應式引用,并根據組件結構調整代碼。