356 lines
7.6 KiB
Markdown
356 lines
7.6 KiB
Markdown
# 异步文件上传功能实施计划 - Part 4: 前端开发
|
||
|
||
## 文档信息
|
||
- **创建日期**: 2026-03-05
|
||
- **版本**: v1.1
|
||
- **作者**: Claude
|
||
- **关联设计**: [前端设计文档](../design/2026-03-05-async-file-upload-frontend-design.md)
|
||
- **变更说明**: 移除WebSocket,改为页面轮询机制
|
||
|
||
## 任务概述
|
||
|
||
根据前端设计文档,扩展UploadData.vue组件实现异步批量上传功能。
|
||
|
||
**预计工时**: 4.5个工作日
|
||
|
||
## 任务清单
|
||
|
||
### 任务 1: API接口封装(0.5天)
|
||
|
||
**文件**: `ruoyi-ui/src/api/ccdiProjectUpload.js`
|
||
|
||
**工作内容**:
|
||
```javascript
|
||
// 批量上传文件
|
||
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. 实现批量上传功能
|
||
|
||
**关键代码**:
|
||
```javascript
|
||
// 批量上传
|
||
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. 实现点击筛选功能
|
||
|
||
**模板代码**:
|
||
```vue
|
||
<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. 实现操作按钮
|
||
|
||
**关键方法**:
|
||
```javascript
|
||
// 加载文件列表
|
||
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. **添加轮询状态**:
|
||
```javascript
|
||
data() {
|
||
return {
|
||
// 轮询相关
|
||
pollingTimer: null,
|
||
pollingEnabled: false,
|
||
pollingInterval: 5000 // 5秒轮询间隔
|
||
}
|
||
}
|
||
```
|
||
|
||
2. **生命周期钩子**:
|
||
```javascript
|
||
mounted() {
|
||
this.loadStatistics()
|
||
this.loadFileList()
|
||
|
||
// 检查是否需要启动轮询
|
||
this.$nextTick(() => {
|
||
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
|
||
this.startPolling()
|
||
}
|
||
})
|
||
},
|
||
|
||
beforeDestroy() {
|
||
this.stopPolling()
|
||
}
|
||
```
|
||
|
||
3. **轮询方法**:
|
||
```javascript
|
||
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()
|
||
}
|
||
}
|
||
```
|
||
|
||
4. **在模板中添加刷新按钮**:
|
||
```vue
|
||
<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接口正常调用
|
||
- [ ] 批量上传弹窗正常工作
|
||
- [ ] 统计卡片正常显示和筛选
|
||
- [ ] 文件列表正常展示和操作
|
||
- [ ] 轮询机制正常(自动启动/停止/手动刷新)
|
||
- [ ] 所有测试项通过
|
||
|
||
## 轮询优化建议(可选)
|
||
|
||
**智能轮询间隔**:
|
||
```javascript
|
||
// 根据活跃任务数动态调整轮询间隔
|
||
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秒轮询
|
||
}
|
||
}
|
||
```
|
||
|
||
**用户体验优化**:
|
||
- 在页面顶部显示"自动刷新中..."状态提示
|
||
- 支持用户手动开关轮询开关
|
||
|
||
---
|
||
|
||
**文档结束**
|