前端项目初始化提交
This commit is contained in:
272
docs/superpowers/specs/2026-05-29-upload-task-panel-design.md
Normal file
272
docs/superpowers/specs/2026-05-29-upload-task-panel-design.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 上传任务列表组件 — 设计规格
|
||||
|
||||
> **日期**: 2026-05-29
|
||||
> **目标**: 在 ReadModule 中嵌入上传任务列表组件,实现文件上传后自动跟踪向量化+出题状态,支持轮询刷新、分页、筛选
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据源分析
|
||||
|
||||
### 1.1 后端 file 表关键字段(来自 `file.sql`)
|
||||
|
||||
| 字段 | 类型 | 用途 | 可能值 |
|
||||
|------|------|------|--------|
|
||||
| `process_step_status` | varchar(32) | **流程状态**(主状态) | `UPLOADED` / `VECTORIZING` / `VECTORIZED` / `VECTORIZE_FAILED` / `EXAM_GENERATING` / `EXAM_GENERATED` / `EXAM_GENERATE_FAILED` / `COMPLETED` |
|
||||
| `process_step_message` | varchar(512) | **流程步骤信息**(向量化详情) | `"向量化失败: 确保collection存在失败..."` / `"文件不存在于服务器"` / null |
|
||||
| `exam_status` | varchar(20) | **出题状态** | `UNGENERATED` / `GENERATING` / `GENERATED` / `FAILED` |
|
||||
| `process_message` | text | **处理消息**(出题详情) | `"生成成功,共4道题"` / `"生成失败: 所有题目保存失败..."` / null |
|
||||
|
||||
### 1.2 状态映射规则
|
||||
|
||||
**向量化状态**(从 `process_step_status` + `process_step_message` 判断):
|
||||
|
||||
| process_step_status | 判定结果 | UI 展示 |
|
||||
|---------------------|----------|---------|
|
||||
| `UPLOADED` | 待向量化 | ⏳ 灰色 - 等待中 |
|
||||
| `VECTORIZING` | 向量化中 | 🔄 蓝色 - 处理中(动画) |
|
||||
| `VECTORIZED` | 向量化成功 | ✅ 绿色 - 成功 |
|
||||
| `VECTORIZE_FAILED` | 向量化失败 | ❌ 红色 - 失败(显示 process_step_message) |
|
||||
|
||||
**出题状态**(从 `exam_status` + `process_message` 判断):
|
||||
|
||||
| exam_status | 判定结果 | UI 展示 |
|
||||
|-------------|----------|---------|
|
||||
| `UNGENERATED` | 未出题 | ⏸ 灰色 - 未开始 |
|
||||
| `GENERATING` | 出题中 | 🔄 蓝色 - 处理中(动画) |
|
||||
| `GENERATED` | 出题成功 | ✅ 绿色 - 成功(显示题目数) |
|
||||
| `FAILED` | 出题失败 | ❌ 红色 - 失败(显示 process_message) |
|
||||
|
||||
### 1.3 触发条件
|
||||
|
||||
- 上传成功后:新上传的文件自动加入任务列表
|
||||
- 页面加载时:加载最近 N 条有处理状态的文件(非 UPLOADED/COMPLETED 的活跃任务)
|
||||
- 轮询间隔:5 秒一次,仅对"处理中"状态的任务轮询
|
||||
|
||||
---
|
||||
|
||||
## 2. 组件架构
|
||||
|
||||
```
|
||||
ReadModule.vue
|
||||
├── 文件列表 Tab(已有)
|
||||
├── 文件审批 Tab(已有)
|
||||
└── [新增] 上传任务面板 (UploadTaskPanel)
|
||||
├── 任务头部统计栏(总数 / 处理中 / 成功 / 失败)
|
||||
├── 筛选工具栏(状态筛选 + 搜索 + 手动刷新按钮)
|
||||
├── 任务列表(分页展示)
|
||||
│ └── UploadTaskItem × N
|
||||
│ ├── 文件基本信息(名称、大小、部门、时间)
|
||||
│ ├── 向量化状态条
|
||||
│ └── 出题状态条
|
||||
└── 分页器
|
||||
```
|
||||
|
||||
### 2.1 文件结构
|
||||
|
||||
| 文件 | 职责 | 预估行数 |
|
||||
|------|------|----------|
|
||||
| `src/components/exam/composables/useUploadTasks.js` | 任务数据管理、轮询逻辑、状态解析 | ~250 行 |
|
||||
| `src/components/exam/UploadTaskPanel.vue` | 任务列表面板容器 | ~180 行 |
|
||||
| `src/components/exam/UploadTaskItem.vue` | 单个任务卡片 | ~150 行 |
|
||||
| 修改 `src/components/ReadModule.vue` | 嵌入 UploadTaskPanel,上传后触发添加任务 | ~30 行改动 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 各组件详细设计
|
||||
|
||||
### 3.1 useUploadTasks.js — Composable
|
||||
|
||||
**职责**:所有任务数据的获取、缓存、状态解析、轮询控制。
|
||||
|
||||
**导出接口**:
|
||||
```javascript
|
||||
export function useUploadTasks() {
|
||||
// 状态
|
||||
const tasks = ref([]) // 任务列表原始数据
|
||||
const loading = ref(false)
|
||||
const polling = ref(false)
|
||||
|
||||
// 筛选
|
||||
const statusFilter = ref('all') // all | vectorizing | vectorized | vector_failed | exam_generating | generated | failed
|
||||
|
||||
// 分页
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 统计
|
||||
const stats = computed(() => ({ total, processing, success, failed }))
|
||||
const filteredTasks = computed(() => { /* 筛选+分页 */ })
|
||||
|
||||
// 核心方法
|
||||
function addTask(fileData) // 上传成功后调用,将文件加入任务列表
|
||||
function parseVectorStatus(task) // 解析向量化状态 → { phase, status, label, message, color }
|
||||
function parseExamStatus(task) // 解析出题状态 → { phase, status, label, message, color }
|
||||
function startPolling() // 启动定时轮询
|
||||
function stopPolling() // 停止轮询
|
||||
function refreshTasks() // 手动刷新
|
||||
function fetchTasks() // 从后端获取任务列表 API
|
||||
|
||||
return { tasks, loading, polling, statusFilter, currentPage, pageSize,
|
||||
stats, filteredTasks, addTask, parseVectorStatus, parseExamStatus,
|
||||
startPolling, stopPolling, refreshTasks, fetchTasks }
|
||||
}
|
||||
```
|
||||
|
||||
**状态解析函数核心逻辑**:
|
||||
```javascript
|
||||
function parseVectorStatus(task) {
|
||||
const pss = task.processStepStatus || task.process_step_status || ''
|
||||
const psm = task.processStepMessage || task.process_step_message || ''
|
||||
|
||||
if (['VECTORIZING'].includes(pss))
|
||||
return { phase: 'vectorize', status: 'processing', label: '向量化中', message: '正在处理...', color: 'info', animating: true }
|
||||
if (pss === 'VECTORIZED')
|
||||
return { phase: 'vectorize', status: 'success', label: '向量化完成', message: null, color: 'success' }
|
||||
if (pss === 'VECTORIZE_FAILED')
|
||||
return { phase: 'vectorize', status: 'error', label: '向量化失败', message: psm || '未知错误', color: 'error' }
|
||||
if (['UPLOADED'].includes(pss))
|
||||
return { phase: 'vectorize', status: 'pending', label: '等待向量化', message: null, color: 'pending' }
|
||||
if (['EXAM_GENERATING', 'EXAM_GENERATED', 'EXAM_GENERATE_FAILED', 'COMPLETED'].includes(pss))
|
||||
return { phase: 'vectorize', status: 'success', label: '已入库', message: null, color: 'success' }
|
||||
|
||||
return { phase: 'vectorize', status: 'unknown', label: '未知', message: null, color: 'pending' }
|
||||
}
|
||||
|
||||
function parseExamStatus(task) {
|
||||
const es = task.examStatus || task.exam_status || ''
|
||||
const pm = task.processMessage || task.process_message || ''
|
||||
|
||||
if (es === 'GENERATING')
|
||||
return { phase: 'exam', status: 'processing', label: '生成题目中...', message: null, color: 'info', animating: true }
|
||||
if (es === 'GENERATED') {
|
||||
const match = pm?.match(/共(\d+)道题/)
|
||||
return { phase: 'exam', status: 'success', label: match ? `生成${match[1]}道题` : '题目生成完成', message: pm, color: 'success' }
|
||||
}
|
||||
if (es === 'FAILED')
|
||||
return { phase: 'exam', status: 'error', label: '出题失败', message: pm || '未知错误', color: 'error' }
|
||||
if (es === 'UNGENERATED')
|
||||
return { phase: 'exam', status: 'pending', label: '未出题', message: null, color: 'pending' }
|
||||
|
||||
return { phase: 'exam', status: 'unknown', label: '-', message: null, color: 'pending' }
|
||||
}
|
||||
```
|
||||
|
||||
**轮询策略**:
|
||||
- 仅当存在 `status === 'processing' && animating === true` 的任务时启动轮询
|
||||
- 调用 `fileAPI.getFiles({ pageNum: 1, pageSize: 50 })` 获取最新数据
|
||||
- 对比 `process_step_status` 和 `exam_status` 变化,更新对应任务
|
||||
- 全部任务完成后自动停止轮询
|
||||
- 用户关闭面板或切换 Tab 时停止轮询
|
||||
|
||||
### 3.2 UploadTaskPanel.vue — 面板容器
|
||||
|
||||
**Props**: 无(内部使用 composable)
|
||||
|
||||
**布局**:
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 📋 上传任务 [🔄 刷新] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 全部(12) 处理中(3) 成功(7) 失败(2) │ ← 统计标签栏
|
||||
├─────────────────────────────────────────────┤
|
||||
│ [状态筛选 ▾] [🔍 搜索...] │ ← 工具栏
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 📄 longrule.docx 采购部门 │ │
|
||||
│ │ 14KB · 2026-05-29 12:01 │ │
|
||||
│ │ ✅ 向量化完成 │ │ ← 向量化状态条
|
||||
│ │ ✅ 生成4道题 │ │ ← 出题状态条
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ ... │
|
||||
│ [< 1 2 >] 共12条 │ ← 分页
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**样式规范**:全部使用 design-tokens.css 变量,与 ReadModule 现有风格一致(白色扁平设计)。
|
||||
|
||||
### 3.3 UploadTaskItem.vue — 任务卡片
|
||||
|
||||
**Props**:
|
||||
```javascript
|
||||
{
|
||||
task: Object, // 原始任务数据(file 表记录)
|
||||
vectorStatus: Object, // parseVectorStatus() 返回值
|
||||
examStatus: Object // parseExamStatus() 返回值
|
||||
}
|
||||
```
|
||||
|
||||
**每个状态条的视觉设计**:
|
||||
|
||||
**状态条通用结构**:
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ [图标] 状态文字 详情/错误消息 │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 状态 | 图标 | 文字颜色 | 背景 | 特殊效果 |
|
||||
|------|------|----------|------|----------|
|
||||
| pending(等待) | ⏸ 时钟 | --text-disabled | --bg-container | 无 |
|
||||
| processing(处理中) | 🔄 spinner | --color-info | --bg-info | 脉冲动画 |
|
||||
| success(成功) | ✓ CheckCircleOutlined | --color-success | --bg-success | 无 |
|
||||
| error(失败) | ✗ CloseCircleOutlined | --color-error | --bg-error | 可展开查看错误消息 |
|
||||
| unknown(未知) | - 问号 | --text-disabled | --bg-container | 无 |
|
||||
|
||||
**错误消息展开**:点击 error 状态条可展开/收起完整的 `process_step_message` 或 `process_message` 文本。
|
||||
|
||||
---
|
||||
|
||||
## 4. 与 ReadModule 集成方式
|
||||
|
||||
### 4.1 位置
|
||||
|
||||
在 ReadModule 的 Tab 栏下方、内容区上方,以可折叠面板形式呈现。默认折叠,上传后自动展开。
|
||||
|
||||
### 4.2 上传成功后联动
|
||||
|
||||
在 `handleUploadSubmit()` 成功回调中:
|
||||
```javascript
|
||||
// 上传成功后
|
||||
const result = await fileAPI.uploadFile(formData)
|
||||
if (result.data.code === 200) {
|
||||
const newFile = result.data.data
|
||||
uploadTasks.addTask(newFile) // 添加到任务列表
|
||||
uploadTasks.startPolling() // 启动轮询
|
||||
showUploadTaskPanel.value = true // 展开任务面板
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 生命周期
|
||||
|
||||
- `onMounted`: 如果有未完成的任务,自动加载并启动轮询
|
||||
- `onUnmounted`: 停止轮询清理定时器
|
||||
- 切换到其他 Tab: 暂停轮询(可选)
|
||||
|
||||
---
|
||||
|
||||
## 5. API 调用
|
||||
|
||||
复用已有的 `fileAPI.getFiles(params)` 接口,参数:
|
||||
```javascript
|
||||
{ pageNum: 1, pageSize: 100 } // 获取足够多的记录用于前端筛选
|
||||
```
|
||||
|
||||
无需新增后端接口。通过 `process_step_status` 和 `exam_status` 字段在前端做状态判断。
|
||||
|
||||
---
|
||||
|
||||
## 6. 筛选与分页
|
||||
|
||||
### 6.1 状态筛选选项
|
||||
|
||||
| 筛选值 | 匹配条件 |
|
||||
|--------|----------|
|
||||
| `all` | 显示全部 |
|
||||
| `processing` | 向量化中 OR 出题中 |
|
||||
| `success` | 向量化成功 AND (未出题 OR 出题成功) |
|
||||
| `failed` | 向量化失败 OR 出题失败 |
|
||||
| `pending` | UPLOADED 或 UNGENERATED 且无进行中的步骤 |
|
||||
|
||||
### 6.2 分页
|
||||
|
||||
前端分页,与现有 `paginatedDocuments` 模式一致。
|
||||
Reference in New Issue
Block a user