前端项目初始化提交

This commit is contained in:
2026-06-03 13:16:30 +08:00
commit 0910ba9cbe
163 changed files with 110032 additions and 0 deletions

View 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` 模式一致。