Files
ccdi/doc/plans/2026-03-05-async-file-upload-part4-frontend.md
wkc 656453ea50 refactor: 移除WebSocket,改为页面轮询机制
- 移除WebSocket相关设计
- 添加页面轮询机制设计
- 轮询间隔:5秒
- 自动启动/停止策略
- 支持手动刷新
2026-03-05 10:39:35 +08:00

7.6 KiB
Raw Blame History

异步文件上传功能实施计划 - Part 4: 前端开发

文档信息

  • 创建日期: 2026-03-05
  • 版本: v1.1
  • 作者: Claude
  • 关联设计: 前端设计文档
  • 变更说明: 移除WebSocket改为页面轮询机制

任务概述

根据前端设计文档扩展UploadData.vue组件实现异步批量上传功能。

预计工时: 4.5个工作日

任务清单

任务 1: API接口封装0.5天)

文件: ruoyi-ui/src/api/ccdiProjectUpload.js

工作内容:

// 批量上传文件
export function batchUploadFiles(projectId, files) {
  const formData = new FormData()
  files.forEach(file => formData.append('files', file))
  formData.append('projectId', projectId)
  
  return request({
    url: '/ccdi/file-upload/batch',
    method: 'post',
    data: formData,
    timeout: 300000
  })
}

// 查询文件上传记录列表
export function getFileUploadList(params) {
  return request({
    url: '/ccdi/file-upload/list',
    method: 'get',
    params
  })
}

// 查询文件上传统计
export function getFileUploadStatistics(projectId) {
  return request({
    url: `/ccdi/file-upload/statistics/${projectId}`,
    method: 'get'
  })
}

任务 2: 批量上传弹窗1天

文件: ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue

主要修改:

  1. 添加批量上传弹窗状态
  2. 修改handleUploadClick方法
  3. 实现文件选择和校验逻辑
  4. 实现批量上传功能

关键代码:

// 批量上传
async handleBatchUpload() {
  if (this.selectedFiles.length === 0) {
    this.$message.warning('请选择要上传的文件')
    return
  }
  
  this.uploadLoading = true
  
  try {
    await batchUploadFiles(
      this.projectId,
      this.selectedFiles.map(f => f.raw)
    )
    
    this.uploadLoading = false
    this.batchUploadDialogVisible = false
    
    this.$message.success('上传任务已提交,请查看处理进度')
    
    // 刷新数据并启动轮询
    await Promise.all([
      this.loadStatistics(),
      this.loadFileList()
    ])
    
    this.startPolling()
    
  } catch (error) {
    this.uploadLoading = false
    this.$message.error('上传失败:' + (error.msg || '未知错误'))
  }
}

任务 3: 统计卡片0.5天)

工作内容:

  1. 添加统计数据状态
  2. 实现统计卡片组件
  3. 实现点击筛选功能

模板代码:

<div class="statistics-section">
  <div class="stat-card" @click="handleStatusFilter('uploading')">
    <div class="stat-icon uploading">
      <i class="el-icon-upload"></i>
    </div>
    <div class="stat-content">
      <div class="stat-label">上传中</div>
      <div class="stat-value">{{ statistics.uploading }}</div>
    </div>
  </div>
  <!-- 其他3个统计卡片 -->
</div>

任务 4: 文件列表1天

工作内容:

  1. 添加文件列表状态
  2. 实现文件列表组件
  3. 实现分页和筛选
  4. 实现操作按钮

关键方法:

// 加载文件列表
async loadFileList() {
  this.listLoading = true
  
  try {
    const res = await getFileUploadList({
      projectId: this.projectId,
      fileStatus: this.queryParams.fileStatus,
      pageNum: this.queryParams.pageNum,
      pageSize: this.queryParams.pageSize
    })
    
    this.fileList = res.rows || []
    this.total = res.total || 0
    
  } finally {
    this.listLoading = false
  }
}

任务 5: 轮询机制0.5天)

优先级: P0
依赖: 任务2、任务3、任务4完成

工作内容:

  1. 添加轮询状态:
data() {
  return {
    // 轮询相关
    pollingTimer: null,
    pollingEnabled: false,
    pollingInterval: 5000  // 5秒轮询间隔
  }
}
  1. 生命周期钩子:
mounted() {
  this.loadStatistics()
  this.loadFileList()
  
  // 检查是否需要启动轮询
  this.$nextTick(() => {
    if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
      this.startPolling()
    }
  })
},

beforeDestroy() {
  this.stopPolling()
}
  1. 轮询方法:
methods: {
  /**
   * 启动轮询
   */
  startPolling() {
    if (this.pollingEnabled) {
      return  // 已经在轮询中
    }
    
    this.pollingEnabled = true
    console.log('启动轮询')
    
    const poll = () => {
      if (!this.pollingEnabled) {
        return
      }
      
      // 刷新统计数据和列表
      Promise.all([
        this.loadStatistics(),
        this.loadFileList()
      ]).then(() => {
        // 检查是否需要继续轮询
        if (this.statistics.uploading === 0 && 
            this.statistics.parsing === 0) {
          this.stopPolling()
          console.log('所有任务已完成,停止轮询')
          return
        }
        
        // 继续下一次轮询
        this.pollingTimer = setTimeout(poll, this.pollingInterval)
      }).catch(error => {
        console.error('轮询失败:', error)
        // 发生错误时继续轮询
        this.pollingTimer = setTimeout(poll, this.pollingInterval)
      })
    }
    
    // 立即执行一次
    poll()
  },
  
  /**
   * 停止轮询
   */
  stopPolling() {
    this.pollingEnabled = false
    
    if (this.pollingTimer) {
      clearTimeout(this.pollingTimer)
      this.pollingTimer = null
    }
    
    console.log('停止轮询')
  },
  
  /**
   * 手动刷新
   */
  async handleManualRefresh() {
    await Promise.all([
      this.loadStatistics(),
      this.loadFileList()
    ])
    
    this.$message.success('刷新成功')
    
    // 如果有进行中的任务,启动轮询
    if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
      this.startPolling()
    }
  },
  
  /**
   * 状态筛选
   */
  handleStatusFilter(status) {
    this.queryParams.fileStatus = status
    this.queryParams.pageNum = 1
    this.loadFileList()
  }
}
  1. 在模板中添加刷新按钮:
<el-button 
  icon="el-icon-refresh" 
  @click="handleManualRefresh"
>
  刷新
</el-button>

5.2 验证方式

  1. 启动轮询测试

    • 上传文件后,检查控制台输出"启动轮询"
    • 观察5秒后数据是否自动刷新
  2. 停止轮询测试

    • 等待所有文件处理完成
    • 检查控制台输出"停止轮询"
  3. 手动刷新测试

    • 点击刷新按钮
    • 验证数据立即更新
    • 验证提示消息显示
  4. 页面销毁测试

    • 切换到其他页面
    • 检查控制台输出"停止轮询"
    • 确认定时器被清除

任务 6: 联调测试1天

测试项:

  1. 批量上传功能
  2. 统计卡片展示和筛选
  3. 文件列表展示和分页
  4. 轮询机制(启动、停止、手动刷新)
  5. 操作按钮(查看流水、查看错误)

验收标准

  • 所有API接口正常调用
  • 批量上传弹窗正常工作
  • 统计卡片正常显示和筛选
  • 文件列表正常展示和操作
  • 轮询机制正常(自动启动/停止/手动刷新)
  • 所有测试项通过

轮询优化建议(可选)

智能轮询间隔

// 根据活跃任务数动态调整轮询间隔
getPollingInterval() {
  const { uploading, parsing } = this.statistics
  const activeCount = uploading + parsing
  
  if (activeCount > 50) {
    return 3000  // 大量任务时3秒轮询
  } else if (activeCount > 10) {
    return 5000  // 正常情况5秒轮询
  } else {
    return 10000  // 少量任务时10秒轮询
  }
}

用户体验优化

  • 在页面顶部显示"自动刷新中..."状态提示
  • 支持用户手动开关轮询开关

文档结束