new version
This commit is contained in:
116
analyze_question_tips.py
Normal file
116
analyze_question_tips.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import json
|
||||
from openai import OpenAI
|
||||
import time
|
||||
|
||||
# 配置API
|
||||
client = OpenAI(
|
||||
api_key="sk-2b675306a92d4fb389766291ab3f1ec1",
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
)
|
||||
|
||||
def analyze_question(question):
|
||||
"""分析题目并生成记忆要点"""
|
||||
prompt = f"""分析这道D365考试题目,生成简洁的记忆要点帮助快速记忆。
|
||||
|
||||
题目:
|
||||
{question['stem']}
|
||||
|
||||
选项:
|
||||
{chr(10).join([f"{opt['label']}. {opt['text']}" for opt in question['options']])}
|
||||
|
||||
正确答案:{question['answer']}
|
||||
|
||||
请用中文生成以下内容(每项不超过100字):
|
||||
|
||||
1. 关键词提示:题目中的关键英文单词或短语
|
||||
2. 题目特点:这道题的独特之处
|
||||
3. 记忆技巧:如何快速记住这道题的答案
|
||||
4. 答案解析:为什么这个答案是对的
|
||||
|
||||
请用JSON格式返回:
|
||||
{{
|
||||
"keywords": "关键词提示",
|
||||
"features": "题目特点",
|
||||
"memory_tips": "记忆技巧",
|
||||
"explanation": "答案解析"
|
||||
}}
|
||||
"""
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="qwen-plus",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是一个D365考试专家,擅长总结题目要点和记忆技巧。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
result = response.choices[0].message.content.strip()
|
||||
|
||||
# 提取JSON
|
||||
if '```json' in result:
|
||||
result = result.split('```json')[1].split('```')[0].strip()
|
||||
elif '```' in result:
|
||||
result = result.split('```')[1].split('```')[0].strip()
|
||||
|
||||
return json.loads(result)
|
||||
except Exception as e:
|
||||
print(f"Error analyzing question {question['topic']}-{question['question_num']}: {e}")
|
||||
return {
|
||||
"keywords": "分析失败",
|
||||
"features": "分析失败",
|
||||
"memory_tips": "分析失败",
|
||||
"explanation": "分析失败"
|
||||
}
|
||||
|
||||
def main():
|
||||
# 加载题目
|
||||
with open('exam_data/questions_translated.json', 'r', encoding='utf-8') as f:
|
||||
questions = json.load(f)
|
||||
|
||||
print(f"总共 {len(questions)} 道题目需要分析")
|
||||
|
||||
# 加载已分析的题目
|
||||
try:
|
||||
with open('exam_data/question_tips.json', 'r', encoding='utf-8') as f:
|
||||
tips_data = json.load(f)
|
||||
analyzed_count = len(tips_data)
|
||||
except:
|
||||
tips_data = {}
|
||||
analyzed_count = 0
|
||||
|
||||
print(f"已分析 {analyzed_count} 道题目")
|
||||
|
||||
# 分析题目
|
||||
for i, question in enumerate(questions):
|
||||
key = f"{question['topic']}-{question['question_num']}"
|
||||
|
||||
# 跳过已分析的题目
|
||||
if key in tips_data:
|
||||
print(f"[{i+1}/{len(questions)}] 跳过已分析: {key}")
|
||||
continue
|
||||
|
||||
print(f"[{i+1}/{len(questions)}] 分析题目: {key}")
|
||||
|
||||
tips = analyze_question(question)
|
||||
tips_data[key] = tips
|
||||
|
||||
# 每分析10道题保存一次
|
||||
if (i + 1) % 10 == 0:
|
||||
with open('exam_data/question_tips.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(tips_data, f, ensure_ascii=False, indent=2)
|
||||
print(f"已保存 {len(tips_data)} 道题目的分析结果")
|
||||
|
||||
# 避免API限流
|
||||
time.sleep(0.5)
|
||||
|
||||
# 最终保存
|
||||
with open('exam_data/question_tips.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(tips_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n完成!共分析 {len(tips_data)} 道题目")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
80
analyze_question_types.py
Normal file
80
analyze_question_types.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
with open('exam_data/questions_translated.json', 'r', encoding='utf-8') as f:
|
||||
questions = json.load(f)
|
||||
|
||||
# 分类统计
|
||||
yes_no_questions = [] # 判断题
|
||||
single_choice = [] # 单选题
|
||||
multi_choice = [] # 多选题
|
||||
drag_drop_questions = [] # 拖拽题
|
||||
order_questions = [] # 排序题
|
||||
case_questions = [] # 特殊case题
|
||||
|
||||
for q in questions:
|
||||
options = q['options']
|
||||
answer = q['answer']
|
||||
stem = q['stem'].lower()
|
||||
stem_cn = q.get('stem_cn', '').lower()
|
||||
|
||||
# 判断题:只有2个选项,且是Yes/No类型
|
||||
if len(options) == 2:
|
||||
opt_texts = [opt['text'].lower() for opt in options]
|
||||
if any('yes' in t for t in opt_texts) or any('no' in t for t in opt_texts):
|
||||
yes_no_questions.append(q)
|
||||
continue
|
||||
|
||||
# 拖拽题:包含 drag, drop 关键词
|
||||
if 'drag' in stem or 'drop' in stem or '拖拽' in stem_cn or '拖放' in stem_cn:
|
||||
drag_drop_questions.append(q)
|
||||
continue
|
||||
|
||||
# 排序题:包含 order, sequence, arrange 关键词
|
||||
if 'order' in stem or 'sequence' in stem or 'arrange' in stem or '排序' in stem_cn or '顺序' in stem_cn:
|
||||
order_questions.append(q)
|
||||
continue
|
||||
|
||||
# 特殊case题:包含 case, scenario 关键词
|
||||
if 'case ' in stem or 'scenario' in stem or '案例' in stem_cn or '场景' in stem_cn:
|
||||
case_questions.append(q)
|
||||
continue
|
||||
|
||||
# 根据答案长度判断单选/多选
|
||||
if len(answer) == 1:
|
||||
single_choice.append(q)
|
||||
else:
|
||||
multi_choice.append(q)
|
||||
|
||||
print("=" * 50)
|
||||
print("题目类型统计:")
|
||||
print("=" * 50)
|
||||
print(f"总题目数: {len(questions)}")
|
||||
print(f"判断题 (Yes/No): {len(yes_no_questions)}")
|
||||
print(f"拖拽题 (Drag/Drop): {len(drag_drop_questions)}")
|
||||
print(f"排序题 (Order/Sequence): {len(order_questions)}")
|
||||
print(f"特殊Case题: {len(case_questions)}")
|
||||
print(f"单选题: {len(single_choice)}")
|
||||
print(f"多选题: {len(multi_choice)}")
|
||||
print()
|
||||
|
||||
# 显示各类型示例
|
||||
print("=" * 50)
|
||||
print("拖拽题示例:")
|
||||
print("=" * 50)
|
||||
for q in drag_drop_questions[:2]:
|
||||
print(f"Topic {q['topic']} - Q{q['question_num']}: {q['stem'][:100]}...")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("排序题示例:")
|
||||
print("=" * 50)
|
||||
for q in order_questions[:2]:
|
||||
print(f"Topic {q['topic']} - Q{q['question_num']}: {q['stem'][:100]}...")
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("特殊Case题示例:")
|
||||
print("=" * 50)
|
||||
for q in case_questions[:2]:
|
||||
print(f"Topic {q['topic']} - Q{q['question_num']}: {q['stem'][:100]}...")
|
||||
2642
exam-viewer/public/question_tips.json
Normal file
2642
exam-viewer/public/question_tips.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,21 @@
|
||||
<div class="logo">
|
||||
<h2>MB-330 考试学习</h2>
|
||||
</div>
|
||||
|
||||
<div class="exam-button-container">
|
||||
<el-button
|
||||
type="success"
|
||||
size="large"
|
||||
@click="handleStartExam"
|
||||
:disabled="loading"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>
|
||||
开始考试
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-menu
|
||||
v-if="!examMode"
|
||||
:default-active="String(currentTopic)"
|
||||
@select="handleTopicSelect"
|
||||
class="topic-menu"
|
||||
@@ -19,6 +33,30 @@
|
||||
<el-badge :value="topicStats[topic] || 0" class="topic-badge" />
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<div v-else class="exam-sidebar">
|
||||
<div class="exam-info">
|
||||
<el-tag type="warning" size="large">考试模式</el-tag>
|
||||
<div class="exam-progress">
|
||||
<span>已答: {{ examProgress }} / 50</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="exam-question-grid">
|
||||
<div
|
||||
v-for="index in 50"
|
||||
:key="index"
|
||||
class="exam-question-item"
|
||||
:class="{
|
||||
'answered': examAnswers.has(index - 1),
|
||||
'submitted': examSubmittedQuestions.has(index - 1),
|
||||
'current': examCurrentIndex === index - 1
|
||||
}"
|
||||
@click="handleExamJumpToQuestion(index - 1)"
|
||||
>
|
||||
{{ index }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-aside>
|
||||
|
||||
<el-main class="main-content">
|
||||
@@ -27,10 +65,237 @@
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 考试模式 -->
|
||||
<template v-else-if="examMode">
|
||||
<div v-if="!examFinished" class="exam-container">
|
||||
<div class="question-header">
|
||||
<h3>考试题目 {{ examCurrentIndex + 1 }} / 50</h3>
|
||||
<div class="header-right">
|
||||
<el-tag :type="getQuestionTypeTag(currentQuestion?.questionType)">
|
||||
{{ getQuestionTypeLabel(currentQuestion?.questionType) }}
|
||||
</el-tag>
|
||||
<span class="question-progress">
|
||||
已答: {{ examProgress }} 题
|
||||
</span>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleExitExam"
|
||||
>
|
||||
<el-icon><Close /></el-icon>
|
||||
退出考试
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="question-content">
|
||||
<div class="bilingual-container">
|
||||
<div class="language-panel english-panel">
|
||||
<div class="panel-header">
|
||||
<el-tag type="primary">English</el-tag>
|
||||
</div>
|
||||
<div class="stem-text">
|
||||
{{ currentQuestion?.stem }}
|
||||
</div>
|
||||
<div class="options-list">
|
||||
<div
|
||||
v-for="option in currentQuestion?.options"
|
||||
:key="option.label"
|
||||
class="option-item exam-option"
|
||||
:class="{
|
||||
'selected': isOptionSelected(option.label) && !showAnswer,
|
||||
'correct-option': showAnswer && currentQuestion?.answer.includes(option.label),
|
||||
'wrong-option': showAnswer && isOptionSelected(option.label) && !currentQuestion?.answer.includes(option.label)
|
||||
}"
|
||||
@click="!showAnswer && handleSelectAnswer(option.label)"
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="isOptionSelected(option.label)"
|
||||
:disabled="showAnswer"
|
||||
@change="!showAnswer && handleSelectAnswer(option.label)"
|
||||
/>
|
||||
<span class="option-label">{{ option.label }}.</span>
|
||||
<span class="option-text">{{ option.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="language-panel chinese-panel">
|
||||
<div class="panel-header">
|
||||
<el-tag type="success">中文</el-tag>
|
||||
</div>
|
||||
<div class="stem-text">
|
||||
{{ currentQuestion?.stem_cn || '待翻译...' }}
|
||||
</div>
|
||||
<div class="options-list">
|
||||
<div
|
||||
v-for="option in currentQuestion?.options"
|
||||
:key="option.label"
|
||||
class="option-item exam-option"
|
||||
:class="{
|
||||
'selected': isOptionSelected(option.label) && !showAnswer,
|
||||
'correct-option': showAnswer && currentQuestion?.answer.includes(option.label),
|
||||
'wrong-option': showAnswer && isOptionSelected(option.label) && !currentQuestion?.answer.includes(option.label)
|
||||
}"
|
||||
@click="!showAnswer && handleSelectAnswer(option.label)"
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="isOptionSelected(option.label)"
|
||||
:disabled="showAnswer"
|
||||
@change="!showAnswer && handleSelectAnswer(option.label)"
|
||||
/>
|
||||
<span class="option-label">{{ option.label }}.</span>
|
||||
<span class="option-text">{{ option.text_cn || '待翻译...' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 答题反馈 -->
|
||||
<div v-if="showAnswer && currentQuestion" class="answer-feedback">
|
||||
<!-- 没有答案的题目 -->
|
||||
<el-alert
|
||||
v-if="!currentQuestion.answer || currentQuestion.answer === ''"
|
||||
title="此题无标准答案"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #default>
|
||||
<div class="feedback-content">
|
||||
<p>此题无标准答案,自动计为正确</p>
|
||||
<p>你的答案: <strong>{{ selectedAnswer || '未作答' }}</strong></p>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
<!-- 有答案的题目 -->
|
||||
<el-alert
|
||||
v-else-if="selectedAnswer === currentQuestion.answer"
|
||||
title="回答正确!"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #default>
|
||||
<div class="feedback-content">
|
||||
<p>你的答案: <strong>{{ selectedAnswer }}</strong></p>
|
||||
<p>正确答案: <strong>{{ currentQuestion.answer }}</strong></p>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-alert
|
||||
v-else
|
||||
title="回答错误!"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #default>
|
||||
<div class="feedback-content">
|
||||
<p>你的答案: <strong>{{ selectedAnswer || '未作答' }}</strong></p>
|
||||
<p>正确答案: <strong>{{ currentQuestion.answer }}</strong></p>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<div class="question-actions">
|
||||
<el-button @click="handleExamPrevQuestion" :disabled="examCurrentIndex === 0">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
上一题
|
||||
</el-button>
|
||||
|
||||
<el-button type="danger" @click="handleFinishExam">
|
||||
<el-icon><Check /></el-icon>
|
||||
提交考试
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleExamNextQuestion"
|
||||
:disabled="examCurrentIndex === 49 && showAnswer"
|
||||
>
|
||||
<span v-if="!showAnswer && selectedAnswer">确认答案</span>
|
||||
<span v-else-if="showAnswer && examCurrentIndex < 49">下一题</span>
|
||||
<span v-else>下一题</span>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 考试结果 -->
|
||||
<div v-else class="exam-result-container">
|
||||
<el-card class="result-card">
|
||||
<template #header>
|
||||
<div class="result-header">
|
||||
<h2>考试结果</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="result-content">
|
||||
<div class="score-display">
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="(examScore / 50) * 100"
|
||||
:width="200"
|
||||
:stroke-width="20"
|
||||
:color="examPassed ? '#67c23a' : '#f56c6c'"
|
||||
>
|
||||
<template #default>
|
||||
<span class="score-number">{{ examScore }}</span>
|
||||
<span class="score-total">/ 50</span>
|
||||
</template>
|
||||
</el-progress>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
:title="examPassed ? '恭喜!考试通过!' : '很遗憾,考试未通过'"
|
||||
:type="examPassed ? 'success' : 'error'"
|
||||
:description="examPassed ? '你答对了40道以上的题目,符合考试要求。' : '你需要答对40道题目才能通过考试,请继续努力!'"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="result-alert"
|
||||
/>
|
||||
|
||||
<div class="result-stats">
|
||||
<el-statistic title="正确题目" :value="examScore" />
|
||||
<el-statistic title="错误题目" :value="50 - examScore" />
|
||||
<el-statistic title="及格线" :value="40" />
|
||||
</div>
|
||||
|
||||
<div class="result-actions">
|
||||
<el-button type="primary" size="large" @click="handleReviewExam">
|
||||
<el-icon><View /></el-icon>
|
||||
查看答题详情
|
||||
</el-button>
|
||||
<el-button type="success" size="large" @click="handleRetakeExam">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
重新考试
|
||||
</el-button>
|
||||
<el-button size="large" @click="handleExitExam">
|
||||
<el-icon><Close /></el-icon>
|
||||
退出考试
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 学习模式 -->
|
||||
<template v-else-if="currentQuestion">
|
||||
<div class="question-header">
|
||||
<h3>Topic {{ currentTopic }} - Question {{ currentQuestion.question_num }}</h3>
|
||||
<div class="header-right">
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleShowTips"
|
||||
>
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
记忆要点
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@@ -181,6 +446,56 @@
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 记忆要点弹窗 -->
|
||||
<el-dialog
|
||||
v-model="tipsDialogVisible"
|
||||
title="记忆要点"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-if="currentQuestion" class="tips-content">
|
||||
<el-alert
|
||||
v-if="!currentQuestion.tips"
|
||||
title="记忆要点正在生成中..."
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<template #default>
|
||||
<p>请稍后再试,或联系管理员生成记忆要点数据。</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<template v-else>
|
||||
<div class="tip-section">
|
||||
<h4><el-icon><Key /></el-icon> 关键词提示</h4>
|
||||
<p>{{ currentQuestion.tips.keywords }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tip-section">
|
||||
<h4><el-icon><Star /></el-icon> 题目特点</h4>
|
||||
<p>{{ currentQuestion.tips.features }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tip-section">
|
||||
<h4><el-icon><MagicStick /></el-icon> 记忆技巧</h4>
|
||||
<p>{{ currentQuestion.tips.memory_tips }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tip-section">
|
||||
<h4><el-icon><Document /></el-icon> 答案解析</h4>
|
||||
<p>{{ currentQuestion.tips.explanation }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="tipsDialogVisible = false">
|
||||
知道了
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -188,6 +503,7 @@
|
||||
import { onMounted, ref, watch, computed } from 'vue'
|
||||
import { useQuestionStore } from './stores/questions'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const store = useQuestionStore()
|
||||
|
||||
@@ -199,12 +515,22 @@ const {
|
||||
currentQuestion,
|
||||
currentTopicQuestions,
|
||||
showAnswer,
|
||||
topicStats
|
||||
topicStats,
|
||||
examMode,
|
||||
examCurrentIndex,
|
||||
examAnswers,
|
||||
examSubmittedQuestions,
|
||||
examProgress,
|
||||
examScore,
|
||||
examPassed,
|
||||
examFinished
|
||||
} = storeToRefs(store)
|
||||
|
||||
const jumpQuestionNum = ref(1)
|
||||
const pdfDialogVisible = ref(false)
|
||||
const isMaximized = ref(false)
|
||||
const selectedAnswer = ref<string>('')
|
||||
const tipsDialogVisible = ref(false)
|
||||
|
||||
const currentPdfUrl = computed(() => {
|
||||
const topicNum = String(currentTopic.value).padStart(2, '0')
|
||||
@@ -215,6 +541,10 @@ watch(currentQuestionIndex, (newIndex) => {
|
||||
jumpQuestionNum.value = newIndex + 1
|
||||
})
|
||||
|
||||
watch(examCurrentIndex, () => {
|
||||
selectedAnswer.value = examAnswers.value.get(examCurrentIndex.value) || ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
store.loadQuestions()
|
||||
})
|
||||
@@ -255,6 +585,173 @@ function closePdfDialog() {
|
||||
pdfDialogVisible.value = false
|
||||
isMaximized.value = false
|
||||
}
|
||||
|
||||
// 考试模式相关函数
|
||||
function handleStartExam() {
|
||||
ElMessageBox.confirm(
|
||||
'考试将随机抽取50道题目,包含判断题、拖拽题、排序题、单选题和特殊Case题。答对40道及以上为合格。确定开始考试吗?',
|
||||
'开始考试',
|
||||
{
|
||||
confirmButtonText: '开始考试',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.startExam()
|
||||
ElMessage.success('考试开始!祝你成功!')
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
function handleShowTips() {
|
||||
tipsDialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleSelectAnswer(optionLabel: string) {
|
||||
if (examFinished.value) return
|
||||
|
||||
// 多选题逻辑
|
||||
const currentAns = selectedAnswer.value
|
||||
const question = currentQuestion.value
|
||||
|
||||
if (!question) return
|
||||
|
||||
// 判断是否是多选题
|
||||
const isMultiChoice = question.answer.length > 1
|
||||
|
||||
if (isMultiChoice) {
|
||||
// 多选题:切换选项
|
||||
if (currentAns.includes(optionLabel)) {
|
||||
selectedAnswer.value = currentAns.split('').filter(l => l !== optionLabel).sort().join('')
|
||||
} else {
|
||||
selectedAnswer.value = (currentAns + optionLabel).split('').sort().join('')
|
||||
}
|
||||
} else {
|
||||
// 单选题:直接选择
|
||||
selectedAnswer.value = optionLabel
|
||||
}
|
||||
|
||||
store.submitExamAnswer(selectedAnswer.value)
|
||||
}
|
||||
|
||||
function isOptionSelected(optionLabel: string): boolean {
|
||||
return selectedAnswer.value.includes(optionLabel)
|
||||
}
|
||||
|
||||
function handleExamNextQuestion() {
|
||||
// 如果当前题目未提交且已作答,先提交显示答案
|
||||
if (!examSubmittedQuestions.value.has(examCurrentIndex.value) && selectedAnswer.value) {
|
||||
store.submitCurrentQuestion()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已提交,跳转到下一题
|
||||
store.examNextQuestion()
|
||||
}
|
||||
|
||||
function handleExamPrevQuestion() {
|
||||
store.examPrevQuestion()
|
||||
}
|
||||
|
||||
function handleExamJumpToQuestion(index: number) {
|
||||
store.examJumpToQuestion(index)
|
||||
}
|
||||
|
||||
function handleFinishExam() {
|
||||
// 先提交当前题目(如果已作答)
|
||||
if (!examSubmittedQuestions.value.has(examCurrentIndex.value) && selectedAnswer.value) {
|
||||
store.submitCurrentQuestion()
|
||||
return
|
||||
}
|
||||
|
||||
const unanswered = 50 - examProgress.value
|
||||
|
||||
if (unanswered > 0) {
|
||||
ElMessageBox.confirm(
|
||||
`你还有 ${unanswered} 道题目未作答,确定要提交考试吗?`,
|
||||
'确认提交',
|
||||
{
|
||||
confirmButtonText: '确定提交',
|
||||
cancelButtonText: '继续答题',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.finishExam()
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
} else {
|
||||
ElMessageBox.confirm(
|
||||
'确定要提交考试吗?提交后将无法修改答案。',
|
||||
'确认提交',
|
||||
{
|
||||
confirmButtonText: '确定提交',
|
||||
cancelButtonText: '继续答题',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.finishExam()
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleReviewExam() {
|
||||
store.examCurrentIndex = 0
|
||||
store.examFinished = false
|
||||
}
|
||||
|
||||
function handleRetakeExam() {
|
||||
store.exitExam()
|
||||
handleStartExam()
|
||||
}
|
||||
|
||||
function handleExitExam() {
|
||||
if (!examFinished.value) {
|
||||
ElMessageBox.confirm(
|
||||
'确定要退出考试吗?当前答题进度将不会保存。',
|
||||
'退出考试',
|
||||
{
|
||||
confirmButtonText: '确定退出',
|
||||
cancelButtonText: '继续考试',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
store.exitExam()
|
||||
ElMessage.info('已退出考试')
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
} else {
|
||||
store.exitExam()
|
||||
}
|
||||
}
|
||||
|
||||
function getQuestionTypeLabel(type?: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
'yes_no': '判断题',
|
||||
'drag_drop': '拖拽题',
|
||||
'order': '排序题',
|
||||
'case': '特殊Case题',
|
||||
'single': '单选题',
|
||||
'multi': '多选题'
|
||||
}
|
||||
return labels[type || 'single'] || '选择题'
|
||||
}
|
||||
|
||||
function getQuestionTypeTag(type?: string): '' | 'success' | 'warning' | 'info' | 'danger' {
|
||||
const tags: Record<string, '' | 'success' | 'warning' | 'info' | 'danger'> = {
|
||||
'yes_no': 'info',
|
||||
'drag_drop': 'warning',
|
||||
'order': 'warning',
|
||||
'case': 'danger',
|
||||
'single': 'success',
|
||||
'multi': ''
|
||||
}
|
||||
return tags[type || 'single'] || ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -285,6 +782,15 @@ function closePdfDialog() {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.exam-button-container {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.exam-button-container .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.topic-menu {
|
||||
border-right: none;
|
||||
}
|
||||
@@ -293,6 +799,65 @@ function closePdfDialog() {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.exam-sidebar {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.exam-info {
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exam-progress {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.exam-question-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.exam-question-item {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.3s;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.exam-question-item:hover {
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.exam-question-item.answered {
|
||||
background-color: #ecf5ff;
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.exam-question-item.submitted {
|
||||
background-color: #67c23a;
|
||||
border-color: #67c23a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.exam-question-item.current {
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
@@ -411,6 +976,25 @@ function closePdfDialog() {
|
||||
background-color: #f0f9eb;
|
||||
}
|
||||
|
||||
.option-item.wrong-option {
|
||||
border-color: #f56c6c;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.option-item.exam-option {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option-item.exam-option.selected {
|
||||
border-color: #409eff;
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.option-item.exam-option:has(.el-checkbox.is-disabled) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
@@ -429,6 +1013,19 @@ function closePdfDialog() {
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.answer-feedback {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.feedback-content {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.feedback-content p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.question-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -436,6 +1033,102 @@ function closePdfDialog() {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.tip-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.tip-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tip-section h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tip-section h4 .el-icon {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.tip-section p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.exam-container {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.exam-result-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-header h2 {
|
||||
margin: 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.score-number {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.score-total {
|
||||
font-size: 24px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.result-alert {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.result-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.pdf-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -14,6 +14,21 @@ export interface Question {
|
||||
stem_cn?: string
|
||||
options: QuestionOption[]
|
||||
answer: string
|
||||
questionType?: 'yes_no' | 'drag_drop' | 'order' | 'case' | 'single' | 'multi'
|
||||
tips?: QuestionTips
|
||||
}
|
||||
|
||||
export interface QuestionTips {
|
||||
keywords: string
|
||||
features: string
|
||||
memory_tips: string
|
||||
explanation: string
|
||||
}
|
||||
|
||||
export interface ExamAnswer {
|
||||
questionIndex: number
|
||||
selectedAnswer: string
|
||||
isCorrect: boolean
|
||||
}
|
||||
|
||||
export const useQuestionStore = defineStore('questions', () => {
|
||||
@@ -23,6 +38,16 @@ export const useQuestionStore = defineStore('questions', () => {
|
||||
const showAnswer = ref<boolean>(false)
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
// 考试模式状态
|
||||
const examMode = ref<boolean>(false)
|
||||
const examQuestions = ref<Question[]>([])
|
||||
const examCurrentIndex = ref<number>(0)
|
||||
const examAnswers = ref<Map<number, string>>(new Map())
|
||||
const examSubmittedQuestions = ref<Set<number>>(new Set()) // 已提交的题目索引
|
||||
const examStartTime = ref<number>(0)
|
||||
const examEndTime = ref<number>(0)
|
||||
const examFinished = ref<boolean>(false)
|
||||
|
||||
const topics = computed(() => {
|
||||
const topicSet = new Set(questions.value.map(q => q.topic))
|
||||
return Array.from(topicSet).sort((a, b) => a - b)
|
||||
@@ -33,6 +58,9 @@ export const useQuestionStore = defineStore('questions', () => {
|
||||
})
|
||||
|
||||
const currentQuestion = computed(() => {
|
||||
if (examMode.value) {
|
||||
return examQuestions.value[examCurrentIndex.value] || null
|
||||
}
|
||||
return currentTopicQuestions.value[currentQuestionIndex.value] || null
|
||||
})
|
||||
|
||||
@@ -44,14 +72,246 @@ export const useQuestionStore = defineStore('questions', () => {
|
||||
return stats
|
||||
})
|
||||
|
||||
// 考试相关计算属性
|
||||
const examProgress = computed(() => {
|
||||
return examAnswers.value.size
|
||||
})
|
||||
|
||||
const examScore = computed(() => {
|
||||
if (!examFinished.value) return 0
|
||||
let correct = 0
|
||||
examQuestions.value.forEach((question, index) => {
|
||||
// 如果题目没有答案,直接算对
|
||||
if (!question.answer || question.answer === '') {
|
||||
correct++
|
||||
} else {
|
||||
const userAnswer = examAnswers.value.get(index)
|
||||
if (userAnswer === question.answer) {
|
||||
correct++
|
||||
}
|
||||
}
|
||||
})
|
||||
return correct
|
||||
})
|
||||
|
||||
const examPassed = computed(() => {
|
||||
return examScore.value >= 40
|
||||
})
|
||||
|
||||
// 分类题目
|
||||
function classifyQuestions() {
|
||||
const classified = {
|
||||
yes_no: [] as Question[],
|
||||
drag_drop: [] as Question[],
|
||||
order: [] as Question[],
|
||||
case: [] as Question[],
|
||||
single: [] as Question[],
|
||||
multi: [] as Question[]
|
||||
}
|
||||
|
||||
questions.value.forEach(q => {
|
||||
const stem = q.stem.toLowerCase()
|
||||
const stem_cn = (q.stem_cn || '').toLowerCase()
|
||||
const options = q.options
|
||||
const answer = q.answer
|
||||
|
||||
// 判断题:只有2个选项,且是Yes/No类型
|
||||
if (options.length === 2) {
|
||||
const opt_texts = options.map(opt => opt.text.toLowerCase())
|
||||
if (opt_texts.some(t => t.includes('yes')) || opt_texts.some(t => t.includes('no'))) {
|
||||
q.questionType = 'yes_no'
|
||||
classified.yes_no.push(q)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽题
|
||||
if (stem.includes('drag') || stem.includes('drop') || stem_cn.includes('拖拽') || stem_cn.includes('拖放')) {
|
||||
q.questionType = 'drag_drop'
|
||||
classified.drag_drop.push(q)
|
||||
return
|
||||
}
|
||||
|
||||
// 排序题
|
||||
if (stem.includes('order') || stem.includes('sequence') || stem.includes('arrange') || stem_cn.includes('排序') || stem_cn.includes('顺序')) {
|
||||
q.questionType = 'order'
|
||||
classified.order.push(q)
|
||||
return
|
||||
}
|
||||
|
||||
// 特殊case题
|
||||
if (stem.includes('case ') || stem.includes('scenario') || stem_cn.includes('案例') || stem_cn.includes('场景')) {
|
||||
q.questionType = 'case'
|
||||
classified.case.push(q)
|
||||
return
|
||||
}
|
||||
|
||||
// 单选/多选
|
||||
if (answer.length === 1) {
|
||||
q.questionType = 'single'
|
||||
classified.single.push(q)
|
||||
} else {
|
||||
q.questionType = 'multi'
|
||||
classified.multi.push(q)
|
||||
}
|
||||
})
|
||||
|
||||
return classified
|
||||
}
|
||||
|
||||
// 随机抽题
|
||||
function generateExamQuestions() {
|
||||
const classified = classifyQuestions()
|
||||
const selected: Question[] = []
|
||||
|
||||
// 判断题 6道
|
||||
const yesNoQuestions = shuffleArray([...classified.yes_no]).slice(0, 6)
|
||||
selected.push(...yesNoQuestions)
|
||||
|
||||
// 拖拽题 5道
|
||||
const dragDropQuestions = shuffleArray([...classified.drag_drop]).slice(0, 5)
|
||||
selected.push(...dragDropQuestions)
|
||||
|
||||
// 排序题 2道
|
||||
const orderQuestions = shuffleArray([...classified.order]).slice(0, 2)
|
||||
selected.push(...orderQuestions)
|
||||
|
||||
// 特殊Case题 7道
|
||||
const caseQuestions = shuffleArray([...classified.case]).slice(0, 7)
|
||||
selected.push(...caseQuestions)
|
||||
|
||||
// 单选题 30道
|
||||
const singleQuestions = shuffleArray([...classified.single]).slice(0, 30)
|
||||
selected.push(...singleQuestions)
|
||||
|
||||
// 打乱顺序
|
||||
return shuffleArray(selected)
|
||||
}
|
||||
|
||||
// Fisher-Yates 洗牌算法
|
||||
function shuffleArray<T>(array: T[]): T[] {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// 开始考试
|
||||
function startExam() {
|
||||
examMode.value = true
|
||||
examQuestions.value = generateExamQuestions()
|
||||
examCurrentIndex.value = 0
|
||||
examAnswers.value = new Map()
|
||||
examSubmittedQuestions.value = new Set()
|
||||
examStartTime.value = Date.now()
|
||||
examEndTime.value = 0
|
||||
examFinished.value = false
|
||||
showAnswer.value = false
|
||||
}
|
||||
|
||||
// 提交考试答案
|
||||
function submitExamAnswer(answer: string) {
|
||||
if (examMode.value && !examFinished.value) {
|
||||
examAnswers.value.set(examCurrentIndex.value, answer)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交当前题目(显示答案并锁定)
|
||||
function submitCurrentQuestion() {
|
||||
if (examMode.value && !examFinished.value) {
|
||||
examSubmittedQuestions.value.add(examCurrentIndex.value)
|
||||
showAnswer.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前题目是否已提交
|
||||
function isCurrentQuestionSubmitted() {
|
||||
return examSubmittedQuestions.value.has(examCurrentIndex.value)
|
||||
}
|
||||
|
||||
// 考试下一题
|
||||
function examNextQuestion() {
|
||||
// 如果当前题目未提交,先提交显示答案
|
||||
if (!examSubmittedQuestions.value.has(examCurrentIndex.value) && examAnswers.value.has(examCurrentIndex.value)) {
|
||||
submitCurrentQuestion()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已提交,跳转到下一题
|
||||
if (examCurrentIndex.value < examQuestions.value.length - 1) {
|
||||
examCurrentIndex.value++
|
||||
showAnswer.value = examSubmittedQuestions.value.has(examCurrentIndex.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 考试上一题
|
||||
function examPrevQuestion() {
|
||||
if (examCurrentIndex.value > 0) {
|
||||
examCurrentIndex.value--
|
||||
showAnswer.value = examSubmittedQuestions.value.has(examCurrentIndex.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到考试题目
|
||||
function examJumpToQuestion(index: number) {
|
||||
if (index >= 0 && index < examQuestions.value.length) {
|
||||
examCurrentIndex.value = index
|
||||
showAnswer.value = examSubmittedQuestions.value.has(index)
|
||||
}
|
||||
}
|
||||
|
||||
// 结束考试
|
||||
function finishExam() {
|
||||
examFinished.value = true
|
||||
examEndTime.value = Date.now()
|
||||
showAnswer.value = true
|
||||
}
|
||||
|
||||
// 退出考试模式
|
||||
function exitExam() {
|
||||
examMode.value = false
|
||||
examQuestions.value = []
|
||||
examCurrentIndex.value = 0
|
||||
examAnswers.value = new Map()
|
||||
examSubmittedQuestions.value = new Set()
|
||||
examStartTime.value = 0
|
||||
examEndTime.value = 0
|
||||
examFinished.value = false
|
||||
showAnswer.value = false
|
||||
}
|
||||
|
||||
async function loadQuestions() {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 加载题目
|
||||
let response = await fetch('/questions_translated.json')
|
||||
if (!response.ok) {
|
||||
response = await fetch('/questions.json')
|
||||
}
|
||||
const data = await response.json()
|
||||
|
||||
// 加载记忆要点
|
||||
try {
|
||||
const tipsResponse = await fetch('/question_tips.json')
|
||||
if (tipsResponse.ok) {
|
||||
const tipsData = await tipsResponse.json()
|
||||
|
||||
// 将记忆要点合并到题目中
|
||||
data.forEach((question: Question) => {
|
||||
const key = `${question.topic}-${question.question_num}`
|
||||
if (tipsData[key]) {
|
||||
question.tips = tipsData[key]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('记忆要点加载成功')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('记忆要点加载失败:', error)
|
||||
}
|
||||
|
||||
questions.value = data
|
||||
} catch (error) {
|
||||
console.error('Failed to load questions:', error)
|
||||
@@ -106,6 +366,27 @@ export const useQuestionStore = defineStore('questions', () => {
|
||||
nextQuestion,
|
||||
prevQuestion,
|
||||
jumpToQuestion,
|
||||
toggleAnswer
|
||||
toggleAnswer,
|
||||
// 考试模式
|
||||
examMode,
|
||||
examQuestions,
|
||||
examCurrentIndex,
|
||||
examAnswers,
|
||||
examSubmittedQuestions,
|
||||
examStartTime,
|
||||
examEndTime,
|
||||
examFinished,
|
||||
examProgress,
|
||||
examScore,
|
||||
examPassed,
|
||||
startExam,
|
||||
submitExamAnswer,
|
||||
submitCurrentQuestion,
|
||||
isCurrentQuestionSubmitted,
|
||||
examNextQuestion,
|
||||
examPrevQuestion,
|
||||
examJumpToQuestion,
|
||||
finishExam,
|
||||
exitExam
|
||||
}
|
||||
})
|
||||
|
||||
2642
exam_data/question_tips.json
Normal file
2642
exam_data/question_tips.json
Normal file
File diff suppressed because it is too large
Load Diff
110
retry_failed_tips.py
Normal file
110
retry_failed_tips.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import json
|
||||
from openai import OpenAI
|
||||
import time
|
||||
|
||||
# 配置API
|
||||
client = OpenAI(
|
||||
api_key="sk-2b675306a92d4fb389766291ab3f1ec1",
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
)
|
||||
|
||||
def analyze_question(question):
|
||||
"""分析题目并生成记忆要点"""
|
||||
prompt = f"""分析这道D365考试题目,生成简洁的记忆要点帮助快速记忆。
|
||||
|
||||
题目:
|
||||
{question['stem']}
|
||||
|
||||
选项:
|
||||
{chr(10).join([f"{opt['label']}. {opt['text']}" for opt in question['options']])}
|
||||
|
||||
正确答案:{question['answer']}
|
||||
|
||||
请用中文生成以下内容(每项不超过100字):
|
||||
|
||||
1. 关键词提示:题目中的关键英文单词或短语
|
||||
2. 题目特点:这道题的独特之处
|
||||
3. 记忆技巧:如何快速记住这道题的答案
|
||||
4. 答案解析:为什么这个答案是对的
|
||||
|
||||
请用JSON格式返回:
|
||||
{{
|
||||
"keywords": "关键词提示",
|
||||
"features": "题目特点",
|
||||
"memory_tips": "记忆技巧",
|
||||
"explanation": "答案解析"
|
||||
}}
|
||||
"""
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="qwen-plus",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是一个D365考试专家,擅长总结题目要点和记忆技巧。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
result = response.choices[0].message.content.strip()
|
||||
|
||||
# 提取JSON
|
||||
if '```json' in result:
|
||||
result = result.split('```json')[1].split('```')[0].strip()
|
||||
elif '```' in result:
|
||||
result = result.split('```')[1].split('```')[0].strip()
|
||||
|
||||
return json.loads(result)
|
||||
except Exception as e:
|
||||
print(f"Error analyzing question {question['topic']}-{question['question_num']}: {e}")
|
||||
return {
|
||||
"keywords": "分析失败",
|
||||
"features": "分析失败",
|
||||
"memory_tips": "分析失败",
|
||||
"explanation": "分析失败"
|
||||
}
|
||||
|
||||
def main():
|
||||
# 加载题目
|
||||
with open('exam_data/questions_translated.json', 'r', encoding='utf-8') as f:
|
||||
questions = json.load(f)
|
||||
|
||||
# 加载已有的记忆要点
|
||||
with open('exam_data/question_tips.json', 'r', encoding='utf-8') as f:
|
||||
tips_data = json.load(f)
|
||||
|
||||
print(f"总共 {len(questions)} 道题目")
|
||||
print(f"已有 {len(tips_data)} 道题目的记忆要点")
|
||||
|
||||
# 找出需要重新分析的题目(前20道)
|
||||
failed_keys = [k for k, v in tips_data.items() if v.get('keywords') == '分析失败']
|
||||
print(f"需要重新分析 {len(failed_keys)} 道题目")
|
||||
|
||||
# 重新分析失败的题目
|
||||
for i, question in enumerate(questions):
|
||||
key = f"{question['topic']}-{question['question_num']}"
|
||||
|
||||
if key in failed_keys:
|
||||
print(f"[{i+1}/{len(questions)}] 重新分析题目: {key}")
|
||||
|
||||
tips = analyze_question(question)
|
||||
tips_data[key] = tips
|
||||
|
||||
# 每分析5道题保存一次
|
||||
if (i + 1) % 5 == 0:
|
||||
with open('exam_data/question_tips.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(tips_data, f, ensure_ascii=False, indent=2)
|
||||
print(f"已保存进度")
|
||||
|
||||
# 避免API限流
|
||||
time.sleep(0.5)
|
||||
|
||||
# 最终保存
|
||||
with open('exam_data/question_tips.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(tips_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n完成!共重新分析 {len(failed_keys)} 道题目")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user