# 题目管理系统设计方案 **日期**: 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
📋

暂无需审核的题目

您可以:
• 使用上方配置生成新题目
• 所有题目已审核完毕 ✨

``` ### 5.3 加载状态 ```html

正在加载待审核题目...

``` --- ## 六、实施计划 ### 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 **状态**: 待用户确认