工作内容 - 新增 /teacher/questions 页面,支持 Suspense/空状态/加载态 - 题库 CRUD Server Actions:创建/更新/递归删除子题,变更后 revalidatePath - getQuestions 支持 q/type/difficulty/knowledgePointId 筛选与分页返回 meta - UI:表格列/筛选器/创建编辑弹窗,content JSON 兼容组卷 - 更新中文设计文档:docs/design/004_question_bank_implementation.md
133 lines
4.4 KiB
Markdown
133 lines
4.4 KiB
Markdown
# 题库模块实现
|
||
|
||
## 1. 概述
|
||
题库模块(`src/modules/questions`)是教师管理考试资源的核心组件,提供完整的 CRUD 能力,并支持搜索/筛选等常用管理能力。
|
||
|
||
**状态**:已实现
|
||
**日期**:2025-12-23
|
||
**作者**:前端高级工程师
|
||
|
||
---
|
||
|
||
## 2. 架构与技术栈
|
||
|
||
### 2.1 垂直切片(Vertical Slice)架构
|
||
遵循项目的架构规范,所有与题库相关的逻辑都收敛在 `src/modules/questions` 下:
|
||
- `components/`:UI 组件(表格、弹窗、筛选器)
|
||
- `actions.ts`:Server Actions(数据变更)
|
||
- `data-access.ts`:数据库查询逻辑
|
||
- `schema.ts`:Zod 校验 Schema
|
||
- `types.ts`:TypeScript 类型定义
|
||
|
||
### 2.2 关键技术
|
||
- **数据表格**:`@tanstack/react-table`(高性能表格渲染)
|
||
- **状态管理**:`nuqs`(基于 URL Query 的筛选/搜索状态)
|
||
- **表单**:`react-hook-form` + `zod` + `shadcn/ui` 表单组件
|
||
- **校验**:Zod 提供严格的服务端/客户端校验
|
||
|
||
---
|
||
|
||
## 3. 组件设计
|
||
|
||
### 3.1 QuestionDataTable(`question-data-table.tsx`)
|
||
- **能力**:分页、排序、行选择
|
||
- **性能**:尽可能采用与 `React.memo` 兼容的写法(`useReactTable` 本身不做 memo)
|
||
- **响应式**:移动端优先;复杂列支持横向滚动
|
||
|
||
### 3.2 QuestionColumns(`question-columns.tsx`)
|
||
用于增强单元格展示的自定义渲染:
|
||
- **题型 Badge**:不同题型的颜色/样式区分(单选、多选等)
|
||
- **难度展示**:难度标签 + 数值
|
||
- **行操作**:下拉菜单(编辑、删除、查看详情、复制 ID)
|
||
|
||
### 3.3 创建/编辑弹窗(`create-question-dialog.tsx`)
|
||
创建与编辑共用同一个弹窗组件:
|
||
- **动态字段**:根据题型显示/隐藏“选项”区域
|
||
- **选项编辑**:支持添加/删除选项(选择题)
|
||
- **交互反馈**:提交中 Loading 状态
|
||
|
||
### 3.4 筛选器(`question-filters.tsx`)
|
||
- **URL 同步**:搜索、题型、难度等筛选条件与 URL 参数保持同步
|
||
- **无 Debounce(当前)**:搜索输入每次变更都会更新 URL
|
||
- **服务端筛选**:在服务端组件中通过 `getQuestions` 执行筛选查询
|
||
|
||
---
|
||
|
||
## 4. 实现细节
|
||
|
||
### 4.1 数据流
|
||
1. **读取**:`page.tsx`(Server Component)根据 `searchParams` 拉取数据
|
||
2. **写入**:客户端组件调用 Server Actions -> `revalidatePath` -> UI 更新
|
||
3. **筛选**:用户操作 -> 更新 URL -> 服务端组件重新渲染 -> 返回新数据
|
||
|
||
### 4.2 类型安全
|
||
共享的 `Question` 类型用于保证前后端一致:
|
||
|
||
```typescript
|
||
export interface Question {
|
||
id: string
|
||
content: unknown
|
||
type: QuestionType
|
||
difficulty: number
|
||
// ... 关联字段
|
||
}
|
||
```
|
||
|
||
### 4.3 UI/UX 规范
|
||
- **空状态**:无数据时展示 `EmptyState`
|
||
- **加载态**:表格加载使用 Skeleton
|
||
- **反馈**:`Sonner` toast 展示成功/失败提示
|
||
- **确认弹窗**:删除等破坏性操作使用 `AlertDialog`
|
||
|
||
---
|
||
|
||
## 5. 后续计划
|
||
- [x] 接入真实数据库(替换 Mock Data)
|
||
- [ ] 为题目内容引入富文本编辑器(Slate.js / Tiptap)
|
||
- [ ] 增加“批量导入”能力
|
||
- [ ] 增加知识点“标签”管理能力
|
||
|
||
---
|
||
|
||
## 6. 实现更新(2025-12-30)
|
||
|
||
### 6.1 教师路由与加载态
|
||
- 实现 `/teacher/questions` 页面(Suspense + 空状态)
|
||
- 新增路由级加载 UI:`/teacher/questions/loading.tsx`
|
||
|
||
### 6.2 Content JSON 约定
|
||
为与考试组卷/预览组件保持一致,`questions.content` 采用最小 JSON 结构:
|
||
|
||
```typescript
|
||
type ChoiceOption = {
|
||
id: string
|
||
text: string
|
||
isCorrect?: boolean
|
||
}
|
||
|
||
type QuestionContent = {
|
||
text: string
|
||
options?: ChoiceOption[]
|
||
}
|
||
```
|
||
|
||
### 6.3 数据访问层(`getQuestions`)
|
||
- 新增服务端筛选:`q`(content LIKE)、`type`、`difficulty`、`knowledgePointId`
|
||
- 默认仅返回根题(`parentId IS NULL`),除非显式按 `ids` 查询
|
||
- 返回 `{ data, meta }`(包含分页统计),并为 UI 映射关联数据
|
||
|
||
### 6.4 Server Actions(CRUD)
|
||
- `createNestedQuestion` 支持 FormData(字段 `json`)与递归 `subQuestions`
|
||
- `updateQuestionAction` 更新题目与知识点关联
|
||
- `deleteQuestionAction` 递归删除子题
|
||
- 所有变更都会对 `/teacher/questions` 做缓存再验证
|
||
|
||
### 6.5 UI 集成
|
||
- `CreateQuestionDialog` 提交 `QuestionContent` JSON,并支持选择题正确答案勾选
|
||
- `QuestionActions` 在编辑/删除后刷新列表
|
||
- 表格内容预览优先展示 `content.text`
|
||
|
||
### 6.6 校验
|
||
- `npm run lint`:0 errors(仓库其他位置仍存在 warnings)
|
||
- `npm run typecheck`:通过
|