feat: 添加复制题目提示词与导入页 AI 生成提示词模板
Deploy Examination / deploy (push) Successful in 22s

- 题目卡片新增「📋 复制题目」按钮,按题型组织提示词复制到剪贴板
- 导入弹窗新增可折叠的 AI 生成提示词区域,支持 7 种题型切换与复制
- 更新 architecture.md 和 requirements.md 文档

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-02 20:12:32 +08:00
parent 71ceeaf704
commit 890d75cf00
3 changed files with 477 additions and 5 deletions
+450 -2
View File
@@ -163,6 +163,26 @@ body { font-family: -apple-system, 'Segoe UI', sans-serif; background: var(--bg)
.file-input { display: block; width: 100%; padding: 12px; border: 1px dashed var(--surface2); border-radius: 8px; text-align: center; color: var(--text2); cursor: pointer; font-size: 13px; }
.file-input:hover { border-color: var(--accent); }
/* Copy button in question card */
.q-header .copy-btn { padding: 3px 10px; font-size: 12px; border-radius: 6px; border: 1px solid var(--surface2); background: transparent; color: var(--text2); cursor: pointer; transition: all 0.2s; white-space: nowrap; }
.q-header .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
.q-header .copy-btn.copied { border-color: var(--green); color: var(--green); }
/* Import prompt section */
.prompt-section { margin-bottom: 16px; border: 1px solid var(--surface2); border-radius: 8px; overflow: hidden; }
.prompt-toggle { width: 100%; padding: 10px 14px; background: var(--bg); border: none; color: var(--text2); cursor: pointer; font-size: 13px; text-align: left; display: flex; align-items: center; gap: 6px; transition: color 0.2s; }
.prompt-toggle:hover { color: var(--text); }
.prompt-toggle .chevron { font-size: 10px; transition: transform 0.2s; margin-left: auto; }
.prompt-section.open .prompt-toggle .chevron { transform: rotate(90deg); }
.prompt-body { display: none; padding: 12px 14px; border-top: 1px solid var(--surface2); }
.prompt-section.open .prompt-body { display: block; }
.prompt-type-select { width: 100%; padding: 8px 10px; border-radius: 6px; border: 1px solid var(--surface2); background: var(--surface); color: var(--text); font-size: 13px; margin-bottom: 10px; outline: none; }
.prompt-type-select:focus { border-color: var(--accent); }
.prompt-preview { background: var(--bg); border: 1px solid var(--surface2); border-radius: 6px; padding: 10px; font-size: 12px; font-family: monospace; color: var(--text2); max-height: 200px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; line-height: 1.5; margin-bottom: 10px; }
.prompt-copy-btn { width: 100%; padding: 8px; border-radius: 6px; border: 1px solid var(--accent); background: transparent; color: var(--accent); cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s; }
.prompt-copy-btn:hover { background: var(--accent); color: white; }
.prompt-copy-btn.copied { border-color: var(--green); color: var(--green); background: transparent; }
/* Responsive */
@media (max-width: 768px) {
.app { flex-direction: column; }
@@ -199,7 +219,28 @@ body { font-family: -apple-system, 'Segoe UI', sans-serif; background: var(--bg)
<div class="modal-overlay" id="importModal">
<div class="modal">
<h3>📥 导入题目</h3>
<p style="color:var(--text2);font-size:13px;margin-bottom:12px;">粘符合规范的 JSON 数据(topics 格式)</p>
<p style="color:var(--text2);font-size:13px;margin-bottom:12px;">粘贴符合规范的 JSON 数据(topics 格式)</p>
<div class="prompt-section" id="promptSection">
<button class="prompt-toggle" onclick="togglePromptSection()">
📋 复制 AI 生成提示词(让 AI 帮你生成标准格式的题目 JSON)
<span class="chevron">▶</span>
</button>
<div class="prompt-body">
<select class="prompt-type-select" id="promptTypeSelect" onchange="updatePromptPreview()">
<option value="single_choice">单选题 (single_choice)</option>
<option value="multiple_choice">多选题 (multiple_choice)</option>
<option value="true_false">判断题 (true_false)</option>
<option value="fill_blank">填空题 (fill_blank)</option>
<option value="short_answer">简答题 (short_answer)</option>
<option value="code_reading">代码阅读题 (code_reading)</option>
<option value="scenario">场景分析题 (scenario)</option>
</select>
<div class="prompt-preview" id="promptPreview"></div>
<button class="prompt-copy-btn" id="promptCopyBtn" onclick="copyImportPrompt()">📋 复制提示词</button>
</div>
</div>
<textarea id="importText" placeholder='{"topic":"custom","type":"single_choice","questions":[...]}'></textarea>
<div class="modal-or">— 或者 —</div>
<label class="file-input">
@@ -420,6 +461,7 @@ function renderQuestionCard(q, i) {
let html = `<div class="q-card ${cardClass} type-${q.type}" id="card-${q.id}">
<div class="q-header">
<span class="q-number">#${i + 1}</span>
<button class="copy-btn" data-copy="${q.id}" onclick="copyQuestionPrompt('${q.id}')">📋 复制题目</button>
<div class="q-difficulty">${stars}</div>
</div>
<div class="q-text">`;
@@ -583,7 +625,10 @@ function resetAll() {
}
// ─── Import ───
function openImportModal() { document.getElementById('importModal').classList.add('show'); }
function openImportModal() {
document.getElementById('importModal').classList.add('show');
updatePromptPreview();
}
function closeImportModal() { document.getElementById('importModal').classList.remove('show'); }
function doImport() {
@@ -612,6 +657,409 @@ function handleFileImport(event) {
reader.readAsText(file);
}
// ─── Import Prompt Templates ───
const IMPORT_PROMPTS = {
single_choice: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 10
**难度范围**: 1-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 3,
"tags": ["知识点1", "知识点2"],
"question": "题目内容?",
"options": {
"A": "选项 A 的内容",
"B": "选项 B 的内容",
"C": "选项 C 的内容",
"D": "选项 D 的内容"
},
"answer": "A",
"explanation": "详细解析:A 正确因为……,B 错误因为……,C 错误因为……,D 错误因为……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 每题 4 个选项,只有 1 个正确答案
- 选项应具有合理的干扰性
- explanation 需说明正确答案正确的原因和其他选项错误的原因`,
multiple_choice: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 10
**难度范围**: 1-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "multiple_choice",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "mc-001",
"type": "multiple_choice",
"difficulty": 3,
"tags": ["知识点1", "知识点2"],
"question": "以下哪些说法正确?(多选)",
"options": {
"A": "选项 A 的内容",
"B": "选项 B 的内容",
"C": "选项 C 的内容",
"D": "选项 D 的内容"
},
"answer": ["A", "C"],
"explanation": "A 正确因为……,B 错误因为……,C 正确因为……,D 错误因为……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 每题 4-6 个选项,2-3 个正确答案
- answer 为正确选项字母的数组
- explanation 需说明每个选项对错的原因`,
true_false: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 10
**难度范围**: 1-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "true_false",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "tf-001",
"type": "true_false",
"difficulty": 2,
"tags": ["知识点1", "知识点2"],
"question": "判断以下陈述是否正确:某概念的描述。",
"answer": true,
"explanation": "该陈述正确/错误,因为……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 陈述一个明确的事实或概念
- answer 为 true 或 false(布尔值)
- explanation 需解释为什么对或错`,
fill_blank: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 10
**难度范围**: 1-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "fill_blank",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "fb-001",
"type": "fill_blank",
"difficulty": 3,
"tags": ["知识点1", "知识点2"],
"question": "______ 协议工作在 OSI 模型的第 ______ 层。",
"answer": ["HTTP", "7"],
"answer_rule": "ordered",
"explanation": "HTTP 是应用层协议,工作在 OSI 模型的第 7 层(应用层)。",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 题目中用 ______(至少 3 个下划线)标记空白处
- answer 数组可包含多个可接受的答案
- answer_rule: "any"(任一匹配)、"all"(全部匹配)、"ordered"(按顺序匹配)`,
short_answer: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 10
**难度范围**: 1-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "short_answer",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "sa-001",
"type": "short_answer",
"difficulty": 4,
"tags": ["知识点1", "知识点2"],
"question": "请简述某概念的原理和应用场景。",
"answer": "参考答案内容……",
"keywords": ["关键词1", "关键词2", "关键词3"],
"scoring_rubric": "答对核心原理得 60 分;举出应用场景得 20 分;说明优缺点得 20 分。",
"explanation": "完整解析……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 提出开放性问题
- answer 为参考答案
- keywords 用于自动评分匹配
- scoring_rubric 描述评分标准`,
code_reading: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 5
**难度范围**: 3-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "code_reading",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "cr-001",
"type": "code_reading",
"difficulty": 4,
"tags": ["知识点1", "知识点2"],
"question": "阅读以下代码,回答问题。",
"code": "// 完整的代码片段",
"language": "python",
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "子问题 1 的内容?",
"options": { "A": "选项 A", "B": "选项 B", "C": "选项 C", "D": "选项 D" },
"answer": "A",
"explanation": "解析……"
},
{
"index": 2,
"type": "short_answer",
"question": "子问题 2 的内容?",
"answer": "参考答案",
"keywords": ["关键词"],
"explanation": "解析……"
}
],
"explanation": "整体代码解析……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 提供完整的、有教学价值的代码片段
- 设置 2-3 个子问题(可以是 single_choice / true_false / short_answer 等)
- language 指定代码语言`,
scenario: `你是一个计算机科学教育专家。请根据以下要求生成题目。
**主题**: {在此填写主题名称}
**数量**: 5
**难度范围**: 3-5
**知识点标签**: {在此填写相关知识点}
请严格按照以下 JSON 格式输出,不要添加任何额外内容:
\`\`\`json
{
"topic": "{topic_slug}",
"type": "scenario",
"schema_version": "1.0.0",
"generated": "{ISO-8601 时间戳}",
"questions": [
{
"id": "sn-001",
"type": "scenario",
"difficulty": 5,
"tags": ["知识点1", "知识点2"],
"question": "请分析以下技术场景。",
"context": "详细场景描述……",
"code": null,
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "分析性问题 1",
"answer": "参考答案",
"keywords": ["关键词"],
"scoring_rubric": "评分标准",
"explanation": "解析……"
},
{
"index": 2,
"type": "multiple_choice",
"question": "选择性问题 2",
"options": { "A": "选项 A", "B": "选项 B", "C": "选项 C", "D": "选项 D" },
"answer": ["A", "C"],
"explanation": "解析……"
}
],
"explanation": "整体场景分析……",
"source": null,
"related": []
}
]
}
\`\`\`
要求:
- 描述一个实际的技术场景
- context 为场景描述,code 可为 null 或附带代码
- 设置 2-3 个分析性子问题`
};
function togglePromptSection() {
document.getElementById('promptSection').classList.toggle('open');
}
function updatePromptPreview() {
const type = document.getElementById('promptTypeSelect').value;
document.getElementById('promptPreview').textContent = IMPORT_PROMPTS[type] || '';
}
function copyImportPrompt() {
const type = document.getElementById('promptTypeSelect').value;
const text = IMPORT_PROMPTS[type];
if (!text) return;
navigator.clipboard.writeText(text).then(() => {
const btn = document.getElementById('promptCopyBtn');
btn.textContent = '✅ 已复制';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = '📋 复制提示词'; btn.classList.remove('copied'); }, 1500);
});
}
// ─── Copy Question as Prompt ───
function copyQuestionPrompt(qId) {
const q = [...currentQuestions, ...customQuestions].find(x => x.id === qId);
if (!q) return;
const typeNames = {
fill_blank: '填空题', single_choice: '单选题', multiple_choice: '多选题',
true_false: '判断题', short_answer: '简答题', code_reading: '代码阅读题', scenario: '场景分析题'
};
const typeName = typeNames[q.type] || q.type;
let prompt = `请帮我解析以下${typeName}题目,给出答案和详细解析。\n\n`;
prompt += `【题型】${typeName}\n`;
prompt += `【难度】${'★'.repeat(q.difficulty)}${'☆'.repeat(5 - q.difficulty)}\n`;
if (q.tags?.length) prompt += `【知识点】${q.tags.join('、')}\n`;
prompt += `\n【题目】${q.question}\n`;
if (q.type === 'fill_blank') {
prompt += `\n请填写空白处的内容。`;
} else if (q.type === 'single_choice' || q.type === 'multiple_choice') {
if (q.options) {
prompt += `\n【选项】\n`;
for (const [letter, text] of Object.entries(q.options)) {
prompt += `${letter}. ${text}\n`;
}
}
if (q.type === 'multiple_choice') prompt += `\n(多选)`;
} else if (q.type === 'true_false') {
prompt += `\n请判断该陈述是否正确。`;
} else if (q.type === 'short_answer') {
prompt += `\n请给出完整回答。`;
} else if (q.type === 'code_reading') {
if (q.code) {
prompt += `\n【代码】\n\`\`\`${q.language || ''}\n${q.code}\n\`\`\``;
}
if (q.sub_questions?.length) {
prompt += `\n\n【子问题】\n`;
q.sub_questions.forEach(sq => {
prompt += `${sq.index}. ${sq.question}\n`;
if (sq.options) {
for (const [letter, text] of Object.entries(sq.options)) {
prompt += ` ${letter}. ${text}\n`;
}
}
});
}
} else if (q.type === 'scenario') {
if (q.context) prompt += `\n【场景】\n${q.context}`;
if (q.code) prompt += `\n\n【代码】\n\`\`\`\n${q.code}\n\`\`\``;
if (q.sub_questions?.length) {
prompt += `\n\n【子问题】\n`;
q.sub_questions.forEach(sq => {
prompt += `${sq.index}. ${sq.question}\n`;
if (sq.options) {
for (const [letter, text] of Object.entries(sq.options)) {
prompt += ` ${letter}. ${text}\n`;
}
}
});
}
}
navigator.clipboard.writeText(prompt).then(() => {
const btn = document.querySelector(`[data-copy="${qId}"]`);
if (btn) {
btn.textContent = '✅ 已复制';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = '📋 复制题目'; btn.classList.remove('copied'); }, 1500);
}
});
}
// ─── Util ───
function escapeHtml(s) {
if (!s) return '';