2026-09-02 18:22:20 +08:00
|
|
|
|
# 架构文档
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 系统总览
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 用户浏览器 │
|
|
|
|
|
|
│ ┌───────────────────────────────────────────────┐ │
|
|
|
|
|
|
│ │ index.html (单文件 SPA) │ │
|
|
|
|
|
|
│ │ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │ │
|
|
|
|
|
|
│ │ │ 导航栏 │ │ 做题区域 │ │ 导入模态框 │ │ │
|
|
|
|
|
|
│ │ └────┬────┘ └─────┬────┘ └──────┬──────┘ │ │
|
|
|
|
|
|
│ └───────┼─────────────┼──────────────┼──────────┘ │
|
|
|
|
|
|
└──────────┼─────────────┼──────────────┼─────────────┘
|
|
|
|
|
|
│ fetch │ fetch │ JSON.parse
|
|
|
|
|
|
▼ ▼ ▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ topics/ (数据层) │
|
|
|
|
|
|
│ ├── index.json ← 主题索引 │
|
|
|
|
|
|
│ └── {topic-slug}/ │
|
|
|
|
|
|
│ ├── meta.json ← 主题元信息(稳定) │
|
|
|
|
|
|
│ ├── fill_blank.json ← 填空题集 │
|
|
|
|
|
|
│ └── single_choice.json ← 单选题集 │
|
|
|
|
|
|
└─────────────────────────────────────────────────────┘
|
|
|
|
|
|
▲
|
|
|
|
|
|
│ git push / pull
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Agent / LLM (生成层) │
|
|
|
|
|
|
│ ├── Claude Code Skill (generate / validate) │
|
|
|
|
|
|
│ ├── LLM + 提示词模板 (schema/prompt-template.md) │
|
|
|
|
|
|
│ └── JSON Schema 校验 (schema/question.schema.json) │
|
|
|
|
|
|
└─────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 目录结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
examination/
|
|
|
|
|
|
├── index.html # 前端入口(单文件 SPA)
|
|
|
|
|
|
├── topics/ # 题目数据
|
|
|
|
|
|
│ ├── index.json # 主题索引
|
|
|
|
|
|
│ └── {topic-slug}/ # 主题目录
|
|
|
|
|
|
│ ├── meta.json # 主题元信息
|
|
|
|
|
|
│ ├── fill_blank.json # 填空题
|
|
|
|
|
|
│ ├── single_choice.json # 单选题
|
|
|
|
|
|
│ ├── multiple_choice.json # 多选题(可选)
|
|
|
|
|
|
│ ├── true_false.json # 判断题(可选)
|
|
|
|
|
|
│ ├── short_answer.json # 简答题(可选)
|
|
|
|
|
|
│ ├── code_reading.json # 代码阅读题(可选)
|
|
|
|
|
|
│ └── scenario.json # 场景分析题(可选)
|
|
|
|
|
|
├── schema/ # Schema 与模板
|
|
|
|
|
|
│ ├── question.schema.json # 题目 JSON Schema(Draft-07)
|
|
|
|
|
|
│ ├── prompt-template.md # LLM 生成提示词模板
|
|
|
|
|
|
│ └── templates/ # 各题型示例
|
|
|
|
|
|
│ ├── single_choice.json
|
|
|
|
|
|
│ ├── multiple_choice.json
|
|
|
|
|
|
│ ├── true_false.json
|
|
|
|
|
|
│ ├── fill_blank.json
|
|
|
|
|
|
│ ├── short_answer.json
|
|
|
|
|
|
│ ├── code_reading.json
|
|
|
|
|
|
│ └── scenario.json
|
|
|
|
|
|
├── docs/ # 文档
|
|
|
|
|
|
│ ├── requirements.md # 需求文档
|
|
|
|
|
|
│ └── architecture.md # 架构文档(本文件)
|
|
|
|
|
|
├── .gitignore # 排除 .ref/
|
|
|
|
|
|
└── README.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 数据模型
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 主题索引 — `topics/index.json`
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"version": "1.0.0",
|
|
|
|
|
|
"updated": "2026-09-02",
|
|
|
|
|
|
"topics": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"slug": "gc-jvm",
|
|
|
|
|
|
"name": "GC / JVM / 三色标记",
|
|
|
|
|
|
"description": "...",
|
|
|
|
|
|
"path": "topics/gc-jvm",
|
|
|
|
|
|
"stats": {
|
|
|
|
|
|
"total": 20,
|
|
|
|
|
|
"by_type": { "fill_blank": 10, "single_choice": 10 }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
前端加载此文件渲染主题列表,`stats` 字段用于展示题目数量。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 主题元信息 — `topics/{slug}/meta.json`
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"slug": "gc-jvm",
|
|
|
|
|
|
"name": "GC / JVM / 三色标记",
|
|
|
|
|
|
"description": "JVM 堆内存模型、GC 算法、三色标记法、垃圾收集器",
|
|
|
|
|
|
"tags": ["jvm", "gc", "heap"],
|
|
|
|
|
|
"difficulty_range": [1, 5],
|
|
|
|
|
|
"schema_version": "1.0.0",
|
|
|
|
|
|
"question_files": ["fill_blank", "single_choice"],
|
|
|
|
|
|
"stats": {
|
|
|
|
|
|
"total": 20,
|
|
|
|
|
|
"by_type": { "fill_blank": 10, "single_choice": 10 }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `question_files` 列出该主题包含的题型文件名(不含 `.json` 后缀)
|
|
|
|
|
|
- 前端根据此列表动态加载对应的题目文件
|
|
|
|
|
|
- `stats` 在每次生成/追加题目后由 Agent 更新
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 题目文件 — `topics/{slug}/{type}.json`
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"topic": "gc-jvm",
|
|
|
|
|
|
"type": "fill_blank",
|
|
|
|
|
|
"schema_version": "1.0.0",
|
|
|
|
|
|
"generated": "2026-09-02T00:00:00Z",
|
|
|
|
|
|
"questions": [ /* 题目数组 */ ]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
每种题型一个文件,支持多 Agent 并行写入不同题型文件而无冲突。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 题目对象
|
|
|
|
|
|
|
|
|
|
|
|
#### 公共字段(所有题型必有)
|
|
|
|
|
|
|
|
|
|
|
|
| 字段 | 类型 | 必填 | 说明 |
|
|
|
|
|
|
|------|------|------|------|
|
|
|
|
|
|
| `id` | string | ✅ | 格式 `{type_short}-{seq}`,如 `fb-001` |
|
|
|
|
|
|
| `type` | enum | ✅ | 题型标识 |
|
|
|
|
|
|
| `difficulty` | integer | ✅ | 1-5 |
|
|
|
|
|
|
| `tags` | string[] | ✅ | 细粒度知识点标签 |
|
|
|
|
|
|
| `question` | string | ✅ | 题干 |
|
|
|
|
|
|
| `explanation` | string | ✅ | 解析 |
|
|
|
|
|
|
| `source` | string | ❌ | 来源 |
|
|
|
|
|
|
| `related` | string[] | ❌ | 关联题目 id |
|
|
|
|
|
|
|
|
|
|
|
|
#### 填空题 (`fill_blank`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"answer": ["老", "Old"],
|
|
|
|
|
|
"answer_rule": "any"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `answer`: string[],可接受的答案列表
|
|
|
|
|
|
- `answer_rule`: `"any"`(任一匹配) | `"all"`(全部匹配) | `"ordered"`(按序匹配)
|
|
|
|
|
|
|
|
|
|
|
|
#### 单选题 (`single_choice`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"options": { "A": "选项A", "B": "选项B", "C": "选项C", "D": "选项D" },
|
|
|
|
|
|
"answer": "B"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 多选题 (`multiple_choice`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"options": { "A": "...", "B": "...", "C": "...", "D": "..." },
|
|
|
|
|
|
"answer": ["A", "C"]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 判断题 (`true_false`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{ "answer": true }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 简答题 (`short_answer`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"answer": "参考答案文本",
|
|
|
|
|
|
"keywords": ["关键词1", "关键词2"],
|
|
|
|
|
|
"scoring_rubric": "评分标准说明"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 代码阅读题 (`code_reading`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": "int x = 1;\nprintf(\"%d\", x);",
|
|
|
|
|
|
"language": "c",
|
|
|
|
|
|
"sub_questions": [
|
|
|
|
|
|
{ "index": 1, "type": "single_choice", "question": "...", "options": {...}, "answer": "A", "explanation": "..." }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景分析题 (`scenario`)
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"context": "一个电商系统在高峰期出现超卖问题...",
|
|
|
|
|
|
"code": null,
|
|
|
|
|
|
"sub_questions": [
|
|
|
|
|
|
{ "index": 1, "type": "short_answer", "question": "...", "answer": "...", "keywords": [...], "explanation": "..." }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5 ID 编码规则
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
{type_short}-{sequence}
|
|
|
|
|
|
fb - 001
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| type_short | 题型 |
|
|
|
|
|
|
|------------|------|
|
|
|
|
|
|
| `sc` | single_choice |
|
|
|
|
|
|
| `mc` | multiple_choice |
|
|
|
|
|
|
| `tf` | true_false |
|
|
|
|
|
|
| `fb` | fill_blank |
|
|
|
|
|
|
| `sa` | short_answer |
|
|
|
|
|
|
| `cr` | code_reading |
|
|
|
|
|
|
| `sn` | scenario |
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 前端架构
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 页面结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌──────────────────────────────────────────┐
|
2026-09-02 18:40:50 +08:00
|
|
|
|
│ Header: 🎯 CS 知识应试强化 [🌙/☀️] │
|
2026-09-02 18:22:20 +08:00
|
|
|
|
├────────────┬─────────────────────────────┤
|
|
|
|
|
|
│ Sidebar │ Main Content │
|
|
|
|
|
|
│ │ │
|
2026-09-02 18:40:50 +08:00
|
|
|
|
│ 📚 主题 │ ┌─ Tabs ─────────────────┐ │
|
|
|
|
|
|
│ · gc-jvm │ │ 全部 | 填空 | 选择 |.. │ │
|
2026-09-02 18:22:20 +08:00
|
|
|
|
│ · ai-so │ └────────────────────────┘ │
|
2026-09-02 18:40:50 +08:00
|
|
|
|
│ · ai-mm │ ┌─ Toolbar ──────────────┐ │
|
|
|
|
|
|
│ · ai-he │ │ 重置 全部显示 提交全部 │ │
|
|
|
|
|
|
│ · ai-at │ └────────────────────────┘ │
|
2026-09-02 18:22:20 +08:00
|
|
|
|
│ │ ┌─ Question Card ────────┐ │
|
2026-09-02 18:40:50 +08:00
|
|
|
|
│ ──────── │ │ #1 ★★★ │ │
|
|
|
|
|
|
│ 📥 导入 │ │ 题干 [____] │ │
|
|
|
|
|
|
│ │ │ [检查] [显示答案] │ │
|
|
|
|
|
|
│ │ │ 💡 解析... │ │
|
2026-09-02 18:22:20 +08:00
|
|
|
|
│ │ └────────────────────────┘ │
|
|
|
|
|
|
└────────────┴─────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 数据流
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
页面加载
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
fetch topics/index.json ──→ 渲染侧边栏主题列表
|
|
|
|
|
|
│
|
|
|
|
|
|
│ 用户点击主题
|
|
|
|
|
|
▼
|
|
|
|
|
|
fetch topics/{slug}/meta.json ──→ 获取 question_files 列表
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
fetch topics/{slug}/{type}.json (并行) ──→ 合并 questions 数组
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
2026-09-02 18:40:50 +08:00
|
|
|
|
renderQuiz() ──→ 渲染 Tab 栏 + 按当前 Tab 过滤题目卡片
|
2026-09-02 18:22:20 +08:00
|
|
|
|
│
|
2026-09-02 18:40:50 +08:00
|
|
|
|
│ 用户交互(选择/输入/检查/显示答案/切换 Tab)
|
2026-09-02 18:22:20 +08:00
|
|
|
|
▼
|
2026-09-02 18:40:50 +08:00
|
|
|
|
answers / activeTab 状态更新 ──→ re-render
|
2026-09-02 18:22:20 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 状态管理
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-09-02 18:40:50 +08:00
|
|
|
|
// 题型配置(常量)
|
|
|
|
|
|
const QUESTION_TYPES = {
|
|
|
|
|
|
fill_blank: { label: '填空', color: '--type-fill_blank' },
|
|
|
|
|
|
single_choice: { label: '选择', color: '--type-single_choice' },
|
|
|
|
|
|
// ...
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 应用状态
|
2026-09-02 18:22:20 +08:00
|
|
|
|
let topics = []; // 从 index.json 加载的主题列表
|
|
|
|
|
|
let currentTopic = null; // 当前选中的主题 slug
|
|
|
|
|
|
let currentQuestions = []; // 当前主题的所有题目(合并自多个题型文件)
|
|
|
|
|
|
let answers = {}; // qId → { value, correct, revealed }
|
|
|
|
|
|
let customQuestions = []; // 通过导入功能临时添加的题目
|
2026-09-02 18:40:50 +08:00
|
|
|
|
let activeTab = 'all'; // 当前 Tab:'all' 或题型名称
|
2026-09-02 18:22:20 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-02 18:40:50 +08:00
|
|
|
|
### 4.4 题型配置与渲染
|
|
|
|
|
|
|
|
|
|
|
|
题型颜色通过 CSS 变量定义在 `:root` 中,新增题型只需:
|
|
|
|
|
|
1. 在 `QUESTION_TYPES` 添加配置
|
|
|
|
|
|
2. 在 `:root` 添加 `--type-{name}` 变量
|
|
|
|
|
|
3. CSS 自动生效(通过 `.q-card.type-{name}` 规则)
|
|
|
|
|
|
|
|
|
|
|
|
| 题型 | 渲染方式 | 判定逻辑 | 边框颜色变量 |
|
|
|
|
|
|
|------|----------|----------|--------------|
|
|
|
|
|
|
| fill_blank | 题干中的 `______` 替换为 `<input>` | `matchFillAnswer()` 根据 `answer_rule` 匹配 | `--type-fill_blank` |
|
|
|
|
|
|
| single_choice | 选项列表,radio 样式 | `value === answer` | `--type-single_choice` |
|
|
|
|
|
|
| multiple_choice | 选项列表,checkbox 样式 | 数组比较 | `--type-multiple_choice` |
|
|
|
|
|
|
| true_false | 两个大按钮 | `value === answer` | `--type-true_false` |
|
|
|
|
|
|
| short_answer | `<textarea>` | 关键词匹配(辅助) | `--type-short_answer` |
|
|
|
|
|
|
| code_reading | 代码块 + 子题列表 | 按子题类型分别判定 | `--type-code_reading` |
|
|
|
|
|
|
| scenario | 场景描述 + 子题列表 | 按子题类型分别判定 | `--type-scenario` |
|
2026-09-02 18:22:20 +08:00
|
|
|
|
|
|
|
|
|
|
## 5. 生成工作流
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 非 Agent 环境(手动 LLM 对话)
|
|
|
|
|
|
|
|
|
|
|
|
1. 打开 `schema/prompt-template.md`,复制提示词模板
|
|
|
|
|
|
2. 替换 `{topic_name}`、`{question_type}`、`{count}` 等变量
|
|
|
|
|
|
3. 将提示词发送给 LLM(ChatGPT / Claude 等)
|
|
|
|
|
|
4. 获取 JSON 输出后,粘贴到对应题型文件中
|
|
|
|
|
|
5. 更新 `meta.json` 的 `stats` 字段
|
|
|
|
|
|
6. 刷新前端页面
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 Agent 环境(Claude Code Skill)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
/examination generate --topic gc-jvm --type fill_blank --count 10
|
|
|
|
|
|
/examination validate --file topics/gc-jvm/fill_blank.json
|
|
|
|
|
|
/examination batch-generate --topic gc-jvm --types fill_blank,single_choice
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Skill 内部流程:
|
|
|
|
|
|
1. 读取 `meta.json` 获取主题上下文
|
|
|
|
|
|
2. 调用 LLM 生成题目(使用 schema 约束)
|
|
|
|
|
|
3. 校验输出是否符合 `question.schema.json`
|
|
|
|
|
|
4. 写入对应题型文件
|
|
|
|
|
|
5. 更新 `meta.json` 和 `index.json` 的 `stats`
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 多 Agent 并行生成
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Agent 1 ──→ topics/gc-jvm/fill_blank.json
|
|
|
|
|
|
Agent 2 ──→ topics/gc-jvm/single_choice.json
|
|
|
|
|
|
Agent 3 ──→ topics/gc-jvm/multiple_choice.json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
由于每种题型写入独立文件,多个 Agent 可并行生成同一主题的不同题型而无写冲突。
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 扩展指南
|
|
|
|
|
|
|
|
|
|
|
|
### 新增主题
|
|
|
|
|
|
|
|
|
|
|
|
1. 创建 `topics/{new-slug}/` 目录
|
|
|
|
|
|
2. 创建 `meta.json`
|
|
|
|
|
|
3. 按题型创建题目 JSON 文件
|
|
|
|
|
|
4. 更新 `topics/index.json`
|
|
|
|
|
|
|
|
|
|
|
|
### 新增题型
|
|
|
|
|
|
|
|
|
|
|
|
1. 在 `schema/question.schema.json` 的 `definitions.question.type.enum` 中添加新类型
|
|
|
|
|
|
2. 添加对应的 `allOf` 条件分支定义字段约束
|
|
|
|
|
|
3. 在前端 `typeLabel()` 中添加映射
|
|
|
|
|
|
4. 在前端 `renderQuestionCard()` 中添加渲染逻辑
|
|
|
|
|
|
5. 在 `schema/templates/` 中添加示例模板
|