Files
aue/docs/superpowers/specs/2026-05-16-question-management-design.md
2026-06-03 13:16:30 +08:00

746 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 题目管理系统设计方案
**日期**: 2026-05-16
**基于**: 前端优化方案与设计规范.md
**模式**: 集成式设计(生成→待审核列表→操作)
---
## 一、系统架构
### 1.1 数据流图
```
[用户操作]
[GeneratePanel.vue]
├─ 配置区域 (文件选择/题型/难度)
├─ 生成按钮 → POST /api/exam/generate
[待审核列表区域]
├─ 初始化: GET /api/question/pending (自动加载)
├─ 搜索/筛选
├─ 题目卡片展示
│ ├─ 审批: PUT /api/question/review/{id}
│ ├─ 编辑: PUT /api/question/{id}
│ └─ 发布: PUT /api/question/review/{id} (status=published)
└─ 批量操作
```
### 1.2 状态管理
```javascript
// useQuestionManagement.js (新增composable)
const state = reactive({
// 生成相关
isGenerating: false,
generatedCount: 0,
// 列表相关
pendingQuestions: [], // 待审核题目列表
loading: false,
total: 0,
currentPage: 1,
pageSize: 10,
// 筛选条件
searchKeyword: '',
statusFilter: 'all', // all/pending/approved/rejected/published
typeFilter: 'all', // all/single_choice/multiple_choice/...
// 编辑状态
editingQuestion: null, // 当前编辑的题目
showEditModal: false,
// 批量操作
selectedIds: [], // 选中的题目ID
})
```
### 1.3 接口调用规划
| 功能 | 方法 | 接口 | 触发时机 |
|------|------|------|---------|
| **生成题目** | `POST` | `/api/exam/generate` | 点击"开始生成"按钮 |
| **获取待审核列表** | `GET` | `/api/question/pending` | 页面加载/生成完成/刷新 |
| **搜索题目** | `GET` | `/api/question/pending?keyword=xxx` | 输入搜索关键词 |
| **筛选题目** | `GET` | `/api/question/pending?status=xxx&type=xxx` | 选择筛选条件 |
| **审批通过** | `PUT` | `/api/question/review/{id}?status=approved` | 点击"通过"按钮 |
| **驳回题目** | `PUT` | `/api/question/review/{id}?status=rejected&comment=xxx` | 点击"驳回"+填写原因 |
| **编辑题目** | `PUT` | `/api/question/{id}` | 点击"编辑"→修改→保存 |
| **发布题目** | `PUT` | `/api/question/review/{id}?status=published` | 点击"发布"按钮 |
| **批量审批** | `PUT` | `/api/question/batch-review` | 选择多条→批量操作 |
| **删除题目** | `DELETE` | `/api/question/{id}` | 点击"删除" |
---
## 二、UI组件设计遵循设计规范
### 2.1 组件结构树
```
GeneratePanel.vue (主容器)
├── PageHeader.vue (标题栏)
├── StatsRow.vue (统计信息)
├── ContentCard: "智能出题配置"
│ ├── FileSelector (文件选择器)
│ ├── DifficultySelect (难度选择)
│ ├── TypeConfigGrid (题型数量配置)
│ └── GenerateButton (生成按钮)
├── ContentCard: "待审核题目管理"
│ ├── Toolbar (工具栏)
│ │ ├── SearchInput (搜索框)
│ │ ├── StatusFilter (状态筛选)
│ │ ├── TypeFilter (题型筛选)
│ │ └── BatchActions (批量操作按钮组)
│ ├── QuestionList (题目列表)
│ │ └── QuestionCard (题目卡片) *N
│ │ ├── QuestionHeader (题号+类型+状态)
│ │ ├── QuestionContent (题干内容)
│ │ ├── QuestionOptions (选项展示)
│ │ └── ActionButtons (操作按钮组)
│ └── Pagination (分页器)
└── EditModal.vue (编辑弹窗) [可选]
```
### 2.2 题目卡片设计QuestionCard
#### 视觉规范
```css
/* 白色体系 + 设计规范 */
.question-card {
background: #FFFFFF;
border: 1px solid #E9ECEF; /* --border-light */
border-radius: 8px; /* --radius-lg */
box-shadow: 0 1px 2px rgba(0,0,0,0.04); /* --shadow-sm */
padding: 16px; /* --space-lg */
margin-bottom: 12px; /* --space-md */
transition: all 0.15s ease; /* --transition-fast */
}
.question-card:hover {
border-color: #DEE2E6; /* --border-default */
box-shadow: 0 2px 8px rgba(0,0,0,0.06); /* --shadow-md */
}
```
#### 卡片内容布局
```
┌─────────────────────────────────────────────────────┐
│ [☑] 第1题 [单选题] [待审核] 中等难度 │ ← Header
├─────────────────────────────────────────────────────┤
│ 在按价位段自选投放工作指引中,用于评估样本零售客户 │ ← Content
│ 库存水平与销售速度匹配程度的核心公式是? │
│ │
│ A. 客户实际订货数量/订货客户订单提报需求数量*100% │ ← Options
│ B. 样本零售客户期末库存/月销量 │
│ C. 实际零售价格/零售指导价格*100% │
│ D. 特定聚类内市场状态结果按销量加权平均 │
│ │
│ [✓ 通过] [✗ 驳回] [✎ 编辑] [📤 发布] [🗑️ 删除] │ ← Actions
└─────────────────────────────────────────────────────┘
```
#### 状态徽章设计
```css
/* 状态标识 - 仅使用功能色 */
.status-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 4px; /* --radius-sm */
font-size: 12px; /* --font-sm */
font-weight: 500; /* --weight-medium */
}
.status-pending {
background: #FFF9DB; /* --bg-warning */
color: #E67700; /* --color-warning */
border: 1px solid #FFE066;
}
.status-approved {
background: #EBFBEE; /* --bg-success */
color: #2B8A3E; /* --color-success */
border: 1px solid #B2F2BB;
}
.status-rejected {
background: #FFF5F5; /* --bg-error */
color: #C92A2A; /* --color-error */
border: 1px solid #FFC9C9;
}
.status-published {
background: #E7F5FF; /* --bg-info */
color: #1C7ED6; /* --color-info */
border: 1px solid #A5D8FF;
}
```
### 2.3 操作按钮设计
```css
/* 按钮组 - 遵循设计规范 5.3 */
.action-buttons {
display: flex;
gap: 6px; /* --space-xs + 2px */
margin-top: 12px; /* --space-md */
padding-top: 12px;
border-top: 1px solid #E9ECEF; /* --border-light */
}
.btn-action {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 12px; /* 紧凑尺寸 */
border-radius: 6px; /* --radius-md */
font-size: 12px; /* --font-sm */
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
border: 1px solid transparent;
}
.btn-approve {
background: #EBFBEE; /* 浅绿底 */
color: #2B8A3E; /* 绿色文字 */
border-color: #B2F2BB;
}
.btn-approve:hover {
background: #D3F9D8;
}
.btn-reject {
background: #FFF5F5; /* 浅红底 */
color: #C92A2A; /* 红色文字 */
border-color: #FFC9C9;
}
.btn-reject:hover {
background: #FFE3E3;
}
.btn-edit {
background: #FFFFFF;
color: #495057; /* --text-secondary */
border-color: #DEE2E6; /* --border-default */
}
.btn-edit:hover {
background: #F8F9FA; /* --bg-container */
border-color: #ADB5BD;
}
.btn-publish {
background: #E7F5FF; /* 浅蓝底 */
color: #1C7ED6; /* 蓝色文字 */
border-color: #A5D8FF;
}
.btn-publish:hover {
background: #D0EBFF;
}
.btn-delete {
background: #FFFFFF;
color: #C92A2A; /* 红色文字 */
border-color: #FFC9C9;
}
.btn-delete:hover {
background: #FFF5F5;
}
```
### 2.4 工具栏设计Toolbar
```
┌─────────────────────────────────────────────────────┐
│ 🔍 搜索题目... [状态: 全部 ▼] [题型: 全部 ▼] │
│ [✓ 批量通过] [✗ 批量驳回] [刷新] │
└─────────────────────────────────────────────────────┘
```
```css
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #F8F9FA; /* --bg-container */
border: 1px solid #E9ECEF;
border-radius: 8px; /* --radius-lg */
margin-bottom: 16px; /* --space-lg */
}
.search-input {
flex: 1;
max-width: 320px;
height: 36px; /* 表单规范 */
padding: 6px 12px;
border: 1px solid #DEE2E6;
border-radius: 6px;
font-size: 13px; /* --font-md */
}
.filter-select {
height: 36px;
padding: 6px 12px;
border: 1px solid #DEE2E6;
border-radius: 6px;
font-size: 13px;
margin-left: 8px;
}
```
---
## 三、交互流程设计
### 3.1 生成题目流程
```
1. 用户配置参数(文件/题型/难度)
2. 点击"开始生成"按钮
3. 显示loading状态 + 进度提示
4. 调用 POST /api/exam/generate
5. 轮询检查生成状态(已有逻辑)
6. 生成完成后:
- 显示成功提示:"成功生成 X 道题目"
- 自动调用 GET /api/question/pending 刷新列表
- 滚动到待审核列表区域
7. 展示待审核题目列表
```
### 3.2 审批流程
```
单个审批:
1. 用户点击"通过"/"驳回"按钮
2. 弹出确认对话框(驳回时需要填写原因)
3. 调用 PUT /api/question/review/{id}?status=approved/rejected
4. 更新本地状态(乐观更新)
5. 刷新列表数据
批量审批:
1. 用户勾选多个题目复选框
2. 点击"批量通过"/"批量驳回"
3. 弹出确认对话框
4. 循环调用单个接口 或 调用批量接口
5. 刷新列表
```
### 3.3 编辑流程
```
方式1内联编辑推荐简单字段
1. 点击"编辑"按钮
2. 题目卡片变为编辑模式
3. 直接修改题干/选项
4. 点击"保存"/"取消"
方式2弹窗编辑推荐复杂编辑
1. 点击"编辑"按钮
2. 打开 EditModal 弹窗
3. 完整的表单编辑界面
4. 点击"保存"/"取消"
```
---
## 四、响应式与滚动策略
### 4.1 布局原则(解决之前的滚动问题)
```css
/* 主容器 - 允许自然流动,不限制高度 */
.generate-panel {
display: flex;
flex-direction: column;
min-height: 0; /* 允许收缩 */
/* 移除 height: 100% 和 overflow: hidden */
}
/* 配置面板 - 固定不滚动 */
.config-panel {
flex-shrink: 0; /* 不压缩 */
margin-bottom: 16px;
}
/* 结果面板 - 内容可撑开 */
.result-panel {
flex: 1;
display: flex;
flex-direction: column;
/* 不设置 max-height 或 overflow */
}
/* 题目列表 - 自然流动 */
.question-list {
display: flex;
flex-direction: column;
gap: 12px;
/* 不设置 overflow-y: auto */
}
```
**效果**
- ✅ 配置区域固定在顶部
- ✅ 题目列表随内容自然增长
- ✅ 当内容超出视口时,整个页面可滚动
- ✅ 无局部滚动条
### 4.2 分页策略
当题目数量较多时(>20道使用分页
```javascript
const pagination = reactive({
current: 1,
pageSize: 10, // 每页10道
total: 0,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50'],
})
// 分页变化时重新请求
const handlePageChange = async (page, size) => {
pagination.current = page
pagination.pageSize = size
await fetchPendingQuestions()
}
```
---
## 五、错误处理与边界情况
### 5.1 错误处理
| 场景 | 处理方式 |
|------|---------|
| 生成失败 | 显示错误消息,保留配置不变 |
| 网络超时 | 提供重试按钮 |
| 审批接口失败 | 回滚到之前状态,显示错误 |
| 并发冲突 | 提示"数据已被其他人修改",刷新列表 |
| 权限不足 | 显示"无权限操作",隐藏操作按钮 |
### 5.2 空状态处理
```html
<!-- 无待审核题目 -->
<div class="empty-state">
<div class="empty-icon">📋</div>
<p class="empty-title">暂无需审核的题目</p>
<p class="empty-hint">
您可以:<br/>
• 使用上方配置生成新题目<br/>
• 所有题目已审核完毕 ✨
</p>
</div>
```
### 5.3 加载状态
```html
<!-- 加载中 -->
<div class="loading-state">
<a-spin size="large" />
<p>正在加载待审核题目...</p>
</div>
<!-- 生成中 -->
<div class="generating-state">
<a-spin tip="AI正在生成题目请耐心等待..." />
<progress :percent="generateProgress" />
</div>
```
---
## 六、实施计划
### Phase 1: 核心功能优先级P0
**目标**:实现基本的生成+展示+审批流程
1. **修改 useGenerateState.js**
- ✅ 修复接口调用:生成后调用 `/question/pending`
- ✅ 添加 `question_count` 参数
- ✅ 保留现有的轮询逻辑
2. **创建 useQuestionManagement.js**
- 实现 `fetchPendingQuestions()`
- 实现 `approveQuestion(id)`
- 实现 `rejectQuestion(id, comment)`
- 实现搜索/筛选逻辑
3. **重构 GeneratePanel.vue**
- 添加工具栏(搜索/筛选)
- 重构题目列表为卡片式
- 添加操作按钮组
- 实现分页功能
### Phase 2: 编辑功能优先级P1
4. **创建 EditModal.vue**
- 题干编辑器
- 选项编辑器(动态增删)
- 答案设置
- 表单验证
5. **集成编辑功能**
- 调用 `PUT /question/{id}`
- 保存后刷新列表
### Phase 3: 批量操作优先级P2
6. **实现批量选择**
- 复选框
- 全选/反选
- 批量审批接口
### Phase 4: 体验优化优先级P3
7. **性能优化**
- 虚拟滚动(大量题目时)
- 防抖搜索
- 缓存策略
8. **视觉优化**
- 动画过渡
- 拖拽排序
- 快捷键支持
---
## 七、技术要点
### 7.1 关键代码示例
#### 获取待审核题目
```javascript
// useQuestionManagement.js
const fetchPendingQuestions = async () => {
loading.value = true
try {
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
}
if (searchKeyword.value) {
params.keyword = searchKeyword.value
}
if (statusFilter.value !== 'all') {
params.status = statusFilter.value
}
if (typeFilter.value !== 'all') {
params.type = typeFilter.value
}
const res = await questionAPI.getPendingQuestions(params)
pendingQuestions.value = transformQuestionData(res.data?.records || [])
pagination.total = res.data?.total || 0
console.log(`✅ 获取待审核题目: ${pendingQuestions.value.length}`)
} catch (error) {
console.error('❌ 获取待审核题目失败:', error)
message.error('加载题目列表失败')
} finally {
loading.value = false
}
}
```
#### 审批操作
```javascript
const approveQuestion = async (questionId) => {
try {
await questionAPI.reviewQuestion(questionId, 'approved')
message.success('审批通过 ✓')
// 乐观更新立即更新UI
const index = pendingQuestions.value.findIndex(q => q.id === questionId)
if (index !== -1) {
pendingQuestions.value[index].status = 'approved'
}
// 可选:延迟刷新确保数据一致性
setTimeout(() => fetchPendingQuestions(), 500)
} catch (error) {
console.error('❌ 审批失败:', error)
message.error('审批操作失败,请重试')
// 回滚:重新获取数据
await fetchPendingQuestions()
}
}
const rejectQuestion = async (questionId, comment) => {
try {
await questionAPI.reviewQuestion(questionId, 'rejected', comment)
message.success('已驳回')
// 更新本地状态
const index = pendingQuestions.value.findIndex(q => q.id === questionId)
if (index !== -1) {
pendingQuestions.value[index].status = 'rejected'
}
setTimeout(() => fetchPendingQuestions(), 500)
} catch (error) {
message.error('驳回操作失败')
await fetchPendingQuestions()
}
}
```
### 7.2 数据转换函数(复用)
```javascript
// 与 useGenerateState.js 保持一致
const transformQuestionData = (rawQuestions) => {
return rawQuestions.map((q, qIdx) => {
let contentObj = {}
try {
if (typeof q.content === 'string' && q.content.startsWith('{')) {
contentObj = JSON.parse(q.content || '{}')
} else if (typeof q.content === 'object') {
contentObj = q.content
}
} catch (e) {
console.warn(`题目${qIdx} content解析失败:`, e.message)
}
return {
id: q.id,
questionId: q.questionId,
type: String(q.questionType || q.type || '').toLowerCase(),
typeLabel: formatQuestionType(q.questionType),
difficulty: q.difficulty || 2,
score: Number(q.score) || 5,
stem: contentObj.stem || q.stem || (typeof q.content === 'string' ? q.content : ''),
options: formatOptions(contentObj.options || q.options || []),
answer: q.answer || contentObj.answer || '',
status: q.status || 'pending',
reviewerComment: q.reviewerComment || '',
createdAt: q.createdAt,
updatedAt: q.updatedAt,
}
})
}
```
---
## 八、测试场景
### 8.1 功能测试
- [ ] 生成5道题目验证列表显示正确
- [ ] 搜索题目,验证过滤结果准确
- [ ] 按状态筛选(待审核/已通过/已驳回/已发布)
- [ ] 单个审批通过/驳回
- [ ] 批量审批多道题目
- [ ] 编辑题目并保存
- [ ] 发布已通过的题目
- [ ] 删除题目(带确认)
- [ ] 分页切换第1页/第2页/每页20条
### 8.2 边界测试
- [ ] 生成了0道题目空状态显示
- [ ] 生成了100道题目性能测试
- [ ] 同时点击多次生成(防重复提交)
- [ ] 网络断开时的错误处理
- [ ] 权限不足时的UI反馈
### 8.3 UI/UX测试
- [ ] 页面滚动流畅(无局部滚动条)
- [ ] 按钮hover效果正常
- [ ] 加载动画显示正确
- [ ] 响应式布局适配不同屏幕
- [ ] 空状态提示友好
---
## 九、成功标准
### 9.1 功能完整性
✅ 用户可以完整执行以下流程:
1. 选择文件 → 配置参数 → 生成题目
2. 查看待审核题目列表
3. 对每道题目进行审批/编辑/发布
4. 搜索、筛选、分页浏览
### 9.2 用户体验
✅ 符合设计规范:
- 白色体系配色
- 紧凑但清晰的布局
- 一致的组件样式
- 流畅的交互动画
- 友好的错误提示
### 9.3 技术质量
✅ 代码质量:
- 无语法错误
- 接口调用正确
- 状态管理清晰
- 错误处理完善
- 性能可接受
---
## 十、后续扩展方向
1. **题目版本管理**:记录每次编辑的历史版本
2. **批量导入导出**支持Excel格式导入/导出题目
3. **AI辅助审核**AI自动预审人工复核
4. **统计分析**:题目通过率、平均审核时间等
5. **权限细化**:角色权限(管理员/审核员/出题人)
6. **移动端适配**:响应式布局优化手机端体验
---
**文档版本**: v1.0
**最后更新**: 2026-05-16
**状态**: 待用户确认