找回密码
 立即注册
首页 业界区 业界 使用vue3+ts构建企业级文件传输管理系统:状态管理、性 ...

使用vue3+ts构建企业级文件传输管理系统:状态管理、性能优化与用户体验的深度实践

万俟谷雪 16 小时前
vue3+ts构建企业级文件传输管理系统:状态管理、性能优化与用户体验的深度实践

在现代企业应用中,文件传输是核心功能之一。一个高效的传输管理系统不仅需要处理大量文件,还需提供直观的状态反馈、灵活的操作选项和流畅的用户体验。今天,我将分享一个基于Vue 3和TypeScript构建的企业级文件传输记录组件的实现细节,它已在多个大型项目中成功应用,支持每日超过10万次文件传输操作。
一、需求分析与设计挑战

在开始编码前,我们需要明确核心需求:

  • 多维度数据展示:区分上传/下载记录,区分进行中/已完成状态
  • 高性能数据处理:支持数千条记录的快速筛选、排序
  • 实时状态更新:上传/下载进度实时显示
  • 复杂操作支持:全选、批量删除、搜索、排序
  • 特殊场景处理:压缩包下载的特殊状态管理
  • 用户体验优化:平滑过渡、加载状态、空数据提示
  • 跨平台兼容:支持桌面和移动设备
这些需求带来了几大技术挑战:

  • 如何高效管理大量动态数据
  • 如何实现上传/下载队列与UI的同步
  • 如何处理特殊场景(如压缩包下载)的不同状态
  • 如何确保大量数据下的渲染性能
二、架构设计:模块化与状态管理

1. 核心数据结构设计
  1. interface TransferItem {
  2.   id: string;
  3.   fileName: string;
  4.   fileSize: string;
  5.   uploadTime?: string;
  6.   downloadTime?: string;
  7.   progress: number;
  8.   status: 'uploading' | 'downloading' | 'completed' | 'failed';
  9.   uploadedFileId?: string; // 上传成功后的文件ID
  10.   flId?: number; // API记录ID,用于删除操作
  11.   flFolderId?: number; // 文件所属文件夹ID
  12.   // 压缩包相关字段
  13.   fctIsComplete?: number; // 压缩包是否完成,0未完成,1已完成,-1失败
  14.   flFileId?: string; // 文件存储编号
  15.   fctCompressName?: string; // 压缩包名称
  16.   flDownloadUrl?: string; // 下载URL
  17. }
复制代码
2. 状态管理分层设计

组件采用了分层状态管理策略:
  1. // 本地队列数据(实时性高)
  2. uploadingList: TransferItem[] = []; // 上传中
  3. uploadCompletedList: TransferItem[] = []; // 已完成(缓存)
  4. downloadingList: TransferItem[] = []; // 下载中
  5. downloadCompletedList: TransferItem[] = []; // 已完成(缓存)
  6. // API数据(持久化)
  7. apiUploadCompletedList: TransferItem[] = []; // API返回的已完成上传
  8. apiDownloadCompletedList: TransferItem[] = []; // API返回的已完成下载
  9. apiUploadTotal = 0; // 上传总数
  10. apiDownloadTotal = 0; // 下载总数
复制代码
这种设计解决了数据一致性实时性的矛盾:本地队列保证操作实时响应,API数据确保记录持久化。
三、核心功能实现:从队列管理到UI同步

1. 队列管理器集成

组件使用了一个自定义的uploadQueueManager处理文件传输队列:
  1. // 初始化队列监听
  2. mounted() {
  3.   // 监听队列更新
  4.   this.queueUpdateListener = () => {
  5.     this.updateDataFromQueue();
  6.     // 当有任务完成时,重新加载API数据
  7.     this.checkAndReloadApiData();
  8.   };
  9.   uploadQueueManager.on('queueUpdate', this.queueUpdateListener);
  10.   // 初始化数据
  11.   this.updateDataFromQueue();
  12.   // 首次加载API数据
  13.   this.loadApiData();
  14. }
复制代码
队列更新时的处理逻辑非常关键,确保UI与数据同步:
  1. updateDataFromQueue() {
  2.   const uploadingTasks = uploadQueueManager.getUploadingTasks();
  3.   const completedUploadTasks = uploadQueueManager.getCompletedUploadTasks();
  4.   const downloadingTasks = uploadQueueManager.getDownloadingTasks();
  5.   const completedDownloadTasks = uploadQueueManager.getCompletedDownloadTasks();
  6.   // 转换上传中任务
  7.   this.uploadingList = uploadingTasks.map(task =>
  8.     this.convertUploadTaskToTransferItem(task)
  9.   );
  10.   
  11.   // 转换已完成上传任务
  12.   this.uploadCompletedList = completedUploadTasks.map(task =>
  13.     this.convertUploadTaskToTransferItem(task)
  14.   );
  15.   // 下载任务处理
  16.   this.downloadingList = downloadingTasks.map(task =>
  17.     this.convertDownloadTaskToTransferItem(task)
  18.   );
  19.   
  20.   // 检查是否需要启动下载刷新定时器
  21.   const currentDownloadingCount = uploadQueueManager.getDownloadingTasks().length;
  22.   if (currentDownloadingCount > 0 && !this.downloadRefreshTimer) {
  23.     this.startDownloadRefreshTimer();
  24.   }
  25. }
复制代码
2. 平滑数据更新:避免UI闪烁

当从API获取数据时,组件采用平滑更新策略避免UI闪烁:
  1. /**
  2. * 平滑更新下载中列表,避免闪烁
  3. * 优化:结合本地队列和API数据,严格去重确保计数一致
  4. */
  5. updateDownloadingListSmooth(apiList: TransferItem[]) {
  6.   const previousCount = this.downloadingList.length;
  7.   
  8.   // 第一步:对API数据进行去重
  9.   const deduplicatedApiList = this.deduplicateApiList(apiList);
  10.   
  11.   // 第二步:创建映射表
  12.   const apiMap = new Map<number, TransferItem>();
  13.   const apiByFileIdMap = new Map<string, TransferItem>();
  14.   deduplicatedApiList.forEach(apiItem => {
  15.     if (apiItem.flId) apiMap.set(apiItem.flId, apiItem);
  16.     if (apiItem.flFileId) apiByFileIdMap.set(apiItem.flFileId, apiItem);
  17.   });
  18.   // 第三步:合并更新,保持进度连续性
  19.   const updatedList: TransferItem[] = [];
  20.   const existingMap = new Map(this.downloadingList.map(item => {
  21.     const key = item.flId ? `flId_${item.flId}` : item.id;
  22.     return [key, item];
  23.   }));
  24.   // 合并API数据和现有数据
  25.   deduplicatedApiList.forEach(apiItem => {
  26.     const uniqueKey = apiItem.flId ? `flId_${apiItem.flId}` : apiItem.id;
  27.     const existingItem = existingMap.get(uniqueKey);
  28.     if (existingItem) {
  29.       // 更新现有项,保持进度平滑
  30.       updatedList.push({
  31.         ...existingItem,
  32.         ...apiItem,
  33.         progress: Math.max(existingItem.progress || 0, apiItem.progress || 0)
  34.       });
  35.     } else {
  36.       // 新增项
  37.       updatedList.push(apiItem);
  38.     }
  39.   });
  40.   this.downloadingList = updatedList;
  41. }
复制代码
3. 压缩包下载状态处理

压缩包下载是复杂场景,需要特殊处理:
  1.   
  2.    
  3.     下载失败,请重试
  4.   
  5.   
  6.    
  7.    
  8.       
  9.         
  10.            下载
  11.         </a-button>
  12.         压缩包已准备完成
  13.       
  14.       
  15.         
  16.         压缩失败
  17.       
  18.       
  19.         
  20.         正在压缩中,请稍候...
  21.       
  22.    
  23.    
  24.    
  25.       
  26.         
  27.         下载中 {{ item.progress }}%
  28.       
  29.       
  30.    
  31.   
复制代码
对应的处理逻辑:
  1. /**
  2. * 处理压缩包下载
  3. */
  4. async downloadCompressedFile(item: TransferItem): Promise<void> {
  5.   if (item.fctIsComplete !== 1) {
  6.     this.$message.warning('压缩包还未准备完成,请稍候');
  7.     return;
  8.   }
  9.   
  10.   try {
  11.     // 构造下载URL
  12.     const downloadUrl = item.flDownloadUrl ? `files${item.flDownloadUrl}` : '';
  13.     if (!downloadUrl) {
  14.       this.$message.warning('下载地址不存在,无法下载');
  15.       return;
  16.     }
  17.     // 使用a标签触发下载
  18.     const link = document.createElement('a');
  19.     link.href = downloadUrl;
  20.     link.download = item.fileName || '';
  21.     link.style.display = 'none';
  22.     document.body.appendChild(link);
  23.     link.click();
  24.     document.body.removeChild(link);
  25.    
  26.     this.$message.success(`开始下载: ${item.fileName}`);
  27.    
  28.     // 调用下载任务完成记录API
  29.     if (item.flId) {
  30.       const formData = new FormData();
  31.       formData.append('flId', item.flId.toString());
  32.       await DownloadTaskRecordCompleted(formData);
  33.     }
  34.   } catch (error) {
  35.     console.error(`下载压缩包失败: ${item.fileName}`, error);
  36.     this.$message.error('下载失败,请稍后重试');
  37.   }
  38. }
复制代码
4. 定时刷新机制

为了保持下载状态实时更新,组件实现了智能定时器:
  1. /**
  2. * 启动下载状态刷新定时器
  3. */
  4. startDownloadRefreshTimer(): void {
  5.   // 先清除现有定时器
  6.   this.stopDownloadRefreshTimer();
  7.   
  8.   console.log('[传输记录] 启动下载状态刷新定时器');
  9.   
  10.   this.downloadRefreshTimer = setInterval(async () => {
  11.     try {
  12.       await this.refreshDownloadingStatus();
  13.     } catch (error) {
  14.       console.error('[传输记录] 刷新下载状态失败:', error);
  15.     }
  16.   }, this.REFRESH_INTERVAL); // 3秒刷新一次
  17.   
  18.   // 立即执行一次
  19.   this.refreshDownloadingStatus();
  20. }
  21. /**
  22. * 停止下载状态刷新定时器
  23. */
  24. stopDownloadRefreshTimer(): void {
  25.   if (this.downloadRefreshTimer) {
  26.     clearInterval(this.downloadRefreshTimer);
  27.     this.downloadRefreshTimer = null;
  28.   }
  29. }
  30. /**
  31. * 刷新下载中的任务状态
  32. */
  33. async refreshDownloadingStatus(): Promise<void> {
  34.   try {
  35.     // 重新加载下载中的数据
  36.     await this.loadDownloadInProgressData(false);
  37.    
  38.     // 检查缓存中是否有下载中任务
  39.     const cacheDownloadingTasks = uploadQueueManager.getDownloadingTasks();
  40.     const hasApiTasks = this.downloadingList.length > 0;
  41.     const hasCacheTasks = cacheDownloadingTasks.length > 0;
  42.    
  43.     // 只有当API和缓存都没有下载中的任务时,才停止定时器
  44.     if (!hasApiTasks && !hasCacheTasks) {
  45.       this.stopDownloadRefreshTimer();
  46.       return;
  47.     }
  48.    
  49.     // 检查是否有满足下载条件的任务(压缩完成的文件夹)
  50.     const downloadingTasks = this.downloadingList.filter(item =>
  51.       this.isCompressedDownload(item) && item.fctIsComplete === 1
  52.     );
  53.    
  54.     // 处理满足条件的下载任务
  55.     for (const task of downloadingTasks) {
  56.       await this.processDownloadingTask(task as any);
  57.     }
  58.   } catch (error) {
  59.     console.error('[传输记录] 刷新下载状态失败:', error);
  60.   }
  61. }
复制代码
四、性能优化:大规模数据处理

1. 虚拟滚动与分页

组件实现了智能分页,只渲染可见数据:
  1. // 显示的列表(用于加载更多功能)
  2. get displayUploadList(): TransferItem[] {
  3.   let filteredList = this.currentUploadList;
  4.   // 如果是已完成状态且有搜索关键词,进行过滤
  5.   if (this.uploadStatus === 'completed' && this.uploadSearchKeyword.trim()) {
  6.     filteredList = this.currentUploadList.filter(item =>
  7.       item.fileName.toLowerCase().includes(this.uploadSearchKeyword.toLowerCase().trim())
  8.     );
  9.   }
  10.   // 只返回当前页需要显示的数据
  11.   return filteredList?.slice(0, this.uploadCurrentPage * this.uploadPageSize);
  12. }
复制代码
2. 计算属性优化

使用计算属性替代方法调用,提高渲染性能:
  1. // 当前已完成上传任务的实际显示数量
  2. get currentUploadCompletedCount(): number {
  3.   // 上传已完成状态的计数:
  4.   // - 优先使用缓存数据(实时)
  5.   // - 无缓存时使用API数据(稳定)
  6.   const cacheCompletedCount = this.uploadCompletedList?.length || 0;
  7.   const apiCompletedCount = this.apiUploadTotal || 0;
  8.   // 缓存数据优先:优先使用缓存,无缓存时使用API数据
  9.   return cacheCompletedCount > 0 ? cacheCompletedCount : apiCompletedCount;
  10. }
复制代码
3. 防抖与节流

对频繁触发的操作(如搜索)使用防抖:
  1. /**
  2. * 加载下载记录已完成数据(带防抖)
  3. */
  4. private loadDownloadCompletedDataTimer: any = null;
  5. async loadDownloadCompletedData(immediate = false) {
  6.   // 如果不是立即执行,使用防抖
  7.   if (!immediate && this.loadDownloadCompletedDataTimer) {
  8.     return;
  9.   }
  10.   
  11.   // 清除之前的定时器
  12.   if (this.loadDownloadCompletedDataTimer) {
  13.     clearTimeout(this.loadDownloadCompletedDataTimer);
  14.     this.loadDownloadCompletedDataTimer = null;
  15.   }
  16.   
  17.   try {
  18.     // ...数据加载逻辑
  19.   } catch (error) {
  20.     console.error('[TransferRecord] 加载下载已完成数据失败:', error);
  21.   }
  22. }
复制代码
五、用户体验细节

1. 自定义复选框设计

组件实现了美观的自定义复选框:
  1.   
  2.    
  3.    
  4.   
复制代码
  1. .custom-checkbox {
  2.   cursor: pointer;
  3.   .checkbox-circle {
  4.     width: 16px;
  5.     height: 16px;
  6.     border: 1px solid #d9d9d9;
  7.     border-radius: 50%;
  8.     display: flex;
  9.     align-items: center;
  10.     justify-content: center;
  11.     transition: all 0.3s;
  12.     &.checked {
  13.       background-color: #2877ED;
  14.       border-color: #2877ED;
  15.       color: #fff;
  16.     }
  17.     &.indeterminate {
  18.       background-color: #2877ED;
  19.       border-color: #2877ED;
  20.       .indeterminate-line {
  21.         width: 8px;
  22.         height: 2px;
  23.         background-color: #fff;
  24.         border-radius: 1px;
  25.       }
  26.     }
  27.     .anticon {
  28.       font-size: 10px;
  29.     }
  30.   }
  31. }
复制代码
2. 优雅的加载状态

组件在不同场景下展示合适的加载状态:
  1. 0"   :data-source="displayUploadList"      :loading="uploadLoading">  
  2.    
  3.    
  4.             加载更多  
  5.    
  6.    
  7.     [align=center] 1.jpg [/align]  暂无数据
复制代码
3. 操作反馈与确认

重要操作(如删除)有明确的确认提示:
  1. clearAllUploadRecords(): void {
  2.   if (this.selectedUploadItems.length > 0) {
  3.     // 删除选中的记录
  4.     this.deleteSelectedItems();
  5.   } else {
  6.     // 删除功能,弹出自定义确认弹框
  7.     Modal.confirm({
  8.       title: '清除记录提示',
  9.       content: '确定要清除所有上传记录吗?一旦删除,数据将无法恢复!',
  10.       okText: '确定',
  11.       okType: 'danger',
  12.       cancelText: '取消',
  13.       centered: true,
  14.       onOk: () => this.executeClearAllUploadRecords(),
  15.     });
  16.   }
  17. }
复制代码
六、错误处理与数据一致性

1. 事务性操作

关键操作实现事务性处理,确保数据一致性:
  1. /**
  2. * 删除指定的下载记录(API + 缓存)
  3. */
  4. async deleteDownloadRecords(taskIds: string[]): Promise<void> {
  5.   console.log(`[删除下载记录] 开始删除${taskIds.length}个下载记录`);
  6.   
  7.   // 1. 先删除API记录
  8.   for (const taskId of taskIds) {
  9.     try {
  10.       const item = this.currentDownloadList.find(item => item.id === taskId);
  11.       if (item && item.flId) {
  12.         const formData = new FormData();
  13.         formData.append('flId', item.flId.toString());
  14.         await DeleteTaskRecord(formData);
  15.         console.log(`[删除下载记录] API删除成功: ${item.fileName}`);
  16.         // 清除已处理标记
  17.         this.processedDownloadTasks.delete(item.flId);
  18.       }
  19.     } catch (error) {
  20.       console.warn(`[删除下载记录] API删除失败 ${taskId}:`, error);
  21.       // 继续删除其他记录
  22.     }
  23.   }
  24.   
  25.   // 2. 再删除缓存记录
  26.   taskIds.forEach(id => {
  27.     uploadQueueManager.removeTask(id, false); // false = 下载
  28.     console.log(`[删除下载记录] 缓存删除: ${id}`);
  29.   });
  30.   
  31.   // 3. 重新加载数据
  32.   await this.loadDownloadApiData();
  33. }
复制代码
2. 异常处理机制

组件对可能出现的异常有全面的处理:
  1. async handleMenuAction(action: string, record: any): Promise<void> {
  2.   try {
  3.     // ...操作逻辑
  4.   } catch (error) {
  5.     console.error('操作失败:', error);
  6.     this.$message.error('操作失败,请重试');
  7.   }
  8. }
  9. async executeDeleteSelectedItems(): Promise<void> {
  10.   // 防止重复执行
  11.   if (this.isDeleting) {
  12.     console.warn('删除操作正在进行中,忽略重复调用');
  13.     return;
  14.   }
  15.   this.isDeleting = true;
  16.   
  17.   try {
  18.     // ...删除逻辑
  19.   } catch (error) {
  20.     console.error('删除记录失败:', error);
  21.     this.$message.error('删除失败,请重试');
  22.   } finally {
  23.     this.isDeleting = false;
  24.   }
  25. }
复制代码
七、总结与最佳实践

通过这个企业级文件传输记录组件,我们总结了以下最佳实践:

  • 数据分层管理:区分本地缓存数据和API持久化数据,根据场景选择使用
  • 状态同步策略:使用事件驱动模式保持UI与数据同步
  • 平滑更新机制:避免UI闪烁,提供流畅用户体验
  • 特殊场景处理:针对压缩包下载等复杂场景设计专门的状态机
  • 性能优化:虚拟滚动、防抖节流、计算属性优化
  • 错误处理:全面的异常捕获和用户反馈
  • 事务性操作:确保关键操作的数据一致性
  • 用户体验细节:自定义控件、加载状态、空数据提示
这个组件不仅解决了文件传输记录的展示问题,更通过精心设计的架构和细节处理,为用户提供了流畅、可靠的体验。在实际项目中,它支撑了每天超过10万次的文件操作,证明了其可靠性和性能。
完整代码实现远比本文展示的更为复杂,涉及诸多边缘情况处理和性能优化细节。 如果你正在构建类似的系统,建议结合自身业务需求,借鉴其中的设计思想而非直接复制代码。前端工程化不仅是功能实现,更是对用户体验、性能和可维护性的综合考量。
希望这篇文章能为你的企业级应用开发带来启发!如果你有任何问题或建议,欢迎在评论区留言讨论。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册