Vue3 + TypeScript 实现文件拖拽上传

应用效果:

实例代码: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 };

关键改进点

  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 替换为适当的响应式引用,并根据组件结构调整代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95661.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025全国大学生数学建模C题保姆级思路模型(持续更新):NIPT 的时点选择与胎儿的异常判定

2025全国大学生数学建模C题保姆级思路模型&#xff08;持续更新&#xff09;&#xff1a;NIPT 的时点选择与胎儿的异常判定&#xff0c;完整持续更新内容见文末名片 胎儿遗传信息检测与临床决策数学建模分析讲义 问题一&#xff1a;Y染色体浓度的影响因素探索——线性回归的“侦…

【笔记】Software Engineering at Google

聚焦核心原则&#xff0c;挑取最让我眼前一亮的实践点&#xff0c;特别是那些能直接启发或解决我当前工作中痛点的部分。0. 序言 最近集中精力速读了关于 ​Google 软件工程实践​ 的诸多资料&#xff08;包括官方出版物、工程博客、技术演讲以及社区讨论&#xff09;。面对 G…

无人机防风技术难点解析

防风防御模块的技术难点主要体现在以下几个方面风场感知与精准建模1.复杂风场的实时感知&#xff1a;风&#xff0c;尤其是近地面的风&#xff0c;常常是无规则、瞬息万变的阵风、湍流或风切变。无人机需要通过各种传感器&#xff08;如空速计、惯性测量单元&#xff08;IMU&am…

HarmonyOS 应用开发深度解析:ArkTS 声明式 UI 与精细化状态管理

好的&#xff0c;请看这篇关于 HarmonyOS 应用开发中声明式 UI 状态管理的技术文章。 HarmonyOS 应用开发深度解析&#xff1a;ArkTS 声明式 UI 与精细化状态管理 引言 随着 HarmonyOS 4、5 的广泛应用和 HarmonyOS NEXT 的发布&#xff0c;基于 API 12 及以上的应用开发已成为…

机器学习入门,第一个MCP示例

前面我们已经搭建了属于自己的AI大模型&#xff1a;详情见 https://blog.csdn.net/hl_java/article/details/150591424?spm1001.2014.3001.5501 近期MCP概念这么火&#xff0c;那么它到底是什么呢&#xff0c;借一个例子为你讲解 第一步&#xff1a;理解MCP的核心价值 MCP (Mo…

flutter 中间组件自适应宽度

使用Flexible IntrinsicWidth Row(children: [Text(第一个text),IntrinsicWidth(child: ConstrainedBox(constraints: BoxConstraints(maxWidth: 200), // 最大宽度限制child: Text(中间的text可能很长也可能很短,overflow: TextOverflow.ellipsis,maxLines: 1,),),),Text(第三…

TDengine 时间函数 DAYOFWEEK 用户手册

DAYOFWEEK 函数使用手册 函数描述 DAYOFWEEK 函数用于返回指定日期是一周中的第几天。该函数遵循标准的星期编号约定&#xff0c;返回值范围为 1-7&#xff0c;其中&#xff1a; 1 星期日 (Sunday)2 星期一 (Monday)3 星期二 (Tuesday)4 星期三 (Wednesday)5 星期四 (T…

【STM32】贪吃蛇 [阶段 3] 增强模块结构(架构优化)

这篇博客是 承接&#xff1a;【项目思维】贪吃蛇&#xff08;嵌入式进阶方向&#xff09;中 聚焦于 &#x1f9f1; 阶段 3&#xff1a;增强模块结构&#xff08;架构优化&#xff09; 中的 菜单系统&#xff08;Menu System&#xff09;&#xff0c;这部分的结构优化可以学到的…

江协科技STM32学习笔记补充之004

STM32 ISP 一键下载电路&#xff08;按功能、逻辑与时序拆解&#xff09;

数据库小册(1)

1. 关系型数据库主要考点关系型数据库&#xff1a;架构索引锁语法理论规范2. 如何设计一个关系型数据库设计即模块划分。数据库最主要的功能是存储我们的数据&#xff0c;所以需要一个存储的文件系统。我们要把涉及到的物流数据提供逻辑的形式给组织和表示出来&#xff0c;这是…

记录收入最高的一次私活 选号网,需要大量卖号的人可能需要,比如游戏脚本批量跑的号

选号网管理后台(上传游戏信息、账号信息、 查看记录) http://124.223.214.5:180/admin1 选号网客户端(PC/H5页面 给客户筛选商品用) http://124.223.214.5:181/ 该在线地址仅供低频率测试&#xff0c;正式使用需要另外部署。 功能不满足可以联系开发者定制 一、项目的由来 …

热烈庆祝“中国抗战胜利80周年”,织信低代码助力国之重器砥砺前行!

“从保家卫国到科技强军&#xff0c;织信低代码平台为军工企业数字化转型注入新动能。”80年后的今天&#xff0c;国人记忆从未褪色。2025年9月3日&#xff0c;正值中国抗战胜利80周年阅兵之际&#xff0c;我国国防军工力量在经历长期的艰苦奋斗后&#xff0c;现今终于迎来了曙…

PostgreSQL与SQL Server:B树索引差异及去重的优势

PostgreSQL与SQL Server&#xff1a;B树索引差异及去重的优势 在优化查询性能方面&#xff0c;索引是数据库工程师可使用的最强大工具之一。PostgreSQL和Microsoft SQL Server&#xff08;或Azure SQL&#xff09;都将B树索引用作其默认索引结构&#xff0c;但每个系统实现、维…

【微实验】使用MATLAB制作一张赛博古琴?

当一个理工音乐人没钱去买古琴&#xff0c;我直接用代码画一个古琴&#xff01;目录 零、总脚本&#xff1a; 一、核心功能&#xff1a;交互模块拆解 二、核心价值 一、初始化脚本&#xff1a;参数配置与启动界面 ①废话不说&#xff0c;直接上代码 ②代码模块拆解与详细解…

毕业项目推荐:67-基于yolov8/yolov5/yolo11的大棚黄瓜检测识别系统(Python+卷积神经网络)

文章目录 项目介绍大全&#xff08;可点击查看&#xff0c;不定时更新中&#xff09;概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式…

无人机小尺寸RFSOC ZU47DR板卡

整板尺寸&#xff1a;120*120mmFPGA: XCZU47DR-2FFVE1156I;DDR&#xff1a;PS侧8GB 2400Mhz*64bit / PL侧 4GB 2400Mhz*32bit&#xff1b;2路(QSP0QSPI1)/单片512Mb、共计1Gb&#xff1b;千兆以太网&#xff1a;1路&#xff08;PS侧&#xff09;&#xff1b;主要接口资源如下&a…

LangGraph(一):入门从0到1(零基础)

文章目录LangGraph入门从0到10️⃣ 安装 & 确认环境1️⃣ 把 LangGraph 想象成「自动化的做菜流水线」2️⃣ 最小可运行例子&#xff1a;一句话复读机3️⃣ 加一个小节点&#xff1a;把用户输入变大写4️⃣ 条件边&#xff1a;如果用户说 quit 就结束&#xff0c;否则复读5…

学习数据结构(16)快速排序

快速排序的基本思想&#xff1a;快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;按照该基准值将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&am…

uni-app iOS 上架常见问题与解决方案,实战经验全解析

uni-app 让开发者能够“一套代码&#xff0c;多端运行”&#xff0c;极大降低了开发成本。 但当应用进入 iOS 上架阶段 时&#xff0c;不少团队发现流程并没有想象中那么顺利&#xff1a;证书问题、打包失败、上传出错、审核被拒……这些都可能让项目卡壳。 本文结合实际案例&a…

洗衣机的智能升级集成方案WT2606B屏幕驱动+AI语音控制

2025&#xff0c;洗衣机市场正从功能满足转向体验升级&#xff0c;企业正面临哪些转型难点?一文为您解读洗衣机行业智能化升级之路。传统洗衣机就像是一个"沉默的工人"&#xff0c;只能通过简单的LED指示灯告诉你它在工作&#xff0c;却无法让你真正了解它在干嘛。用…