# 题目管理系统设计方案

**日期**: 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
**状态**: 待用户确认
