|
|
|
|
@ -0,0 +1,376 @@
|
|
|
|
|
#include "examwidget.h"
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <random>
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
ExamWidget::ExamWidget(QWidget *parent) : QWidget(parent), currentQuestion(0), totalQuestions(0)
|
|
|
|
|
{
|
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
|
|
|
|
|
// 进度标签
|
|
|
|
|
progressLabel = new QLabel();
|
|
|
|
|
progressLabel->setAlignment(Qt::AlignCenter);
|
|
|
|
|
progressLabel->setStyleSheet("font-size: 16px; font-weight: bold; margin: 20px; color: #2c3e50;");
|
|
|
|
|
|
|
|
|
|
// 题目区域
|
|
|
|
|
QGroupBox *questionGroup = new QGroupBox("题目");
|
|
|
|
|
questionGroup->setStyleSheet("QGroupBox {"
|
|
|
|
|
"font-size: 16px;"
|
|
|
|
|
"font-weight: bold;"
|
|
|
|
|
"margin-top: 10px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QGroupBox::title {"
|
|
|
|
|
"subcontrol-origin: margin;"
|
|
|
|
|
"subcontrol-position: top center;"
|
|
|
|
|
"padding: 0 5px;"
|
|
|
|
|
"}");
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *questionLayout = new QVBoxLayout(questionGroup);
|
|
|
|
|
|
|
|
|
|
questionLabel = new QLabel();
|
|
|
|
|
questionLabel->setWordWrap(true);
|
|
|
|
|
questionLabel->setStyleSheet("font-size: 18px; margin: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;");
|
|
|
|
|
questionLabel->setAlignment(Qt::AlignCenter);
|
|
|
|
|
questionLabel->setMinimumHeight(80);
|
|
|
|
|
|
|
|
|
|
questionLayout->addWidget(questionLabel);
|
|
|
|
|
|
|
|
|
|
// 选项区域
|
|
|
|
|
QGroupBox *optionsGroup = new QGroupBox("请选择答案");
|
|
|
|
|
optionsGroup->setStyleSheet("QGroupBox {"
|
|
|
|
|
"font-size: 16px;"
|
|
|
|
|
"font-weight: bold;"
|
|
|
|
|
"margin-top: 10px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QGroupBox::title {"
|
|
|
|
|
"subcontrol-origin: margin;"
|
|
|
|
|
"subcontrol-position: top center;"
|
|
|
|
|
"padding: 0 5px;"
|
|
|
|
|
"}");
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *optionsLayout = new QVBoxLayout(optionsGroup);
|
|
|
|
|
optionGroup = new QButtonGroup(this);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
|
optionButtons[i] = new QRadioButton();
|
|
|
|
|
optionButtons[i]->setStyleSheet("QRadioButton {"
|
|
|
|
|
"font-size: 14px;"
|
|
|
|
|
"margin: 8px;"
|
|
|
|
|
"padding: 8px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QRadioButton::indicator {"
|
|
|
|
|
"width: 20px;"
|
|
|
|
|
"height: 20px;"
|
|
|
|
|
"}");
|
|
|
|
|
optionGroup->addButton(optionButtons[i], i);
|
|
|
|
|
optionsLayout->addWidget(optionButtons[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按钮区域
|
|
|
|
|
QWidget *buttonWidget = new QWidget();
|
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget);
|
|
|
|
|
|
|
|
|
|
// 上一题按钮
|
|
|
|
|
previousButton = new QPushButton("上一题");
|
|
|
|
|
previousButton->setStyleSheet("QPushButton {"
|
|
|
|
|
"background-color: #95a5a6;"
|
|
|
|
|
"color: white;"
|
|
|
|
|
"border: none;"
|
|
|
|
|
"padding: 10px 20px;"
|
|
|
|
|
"font-size: 14px;"
|
|
|
|
|
"border-radius: 5px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QPushButton:hover {"
|
|
|
|
|
"background-color: #7f8c8d;"
|
|
|
|
|
"}"
|
|
|
|
|
"QPushButton:disabled {"
|
|
|
|
|
"background-color: #bdc3c7;"
|
|
|
|
|
"color: #7f8c8d;"
|
|
|
|
|
"}");
|
|
|
|
|
previousButton->setFixedWidth(120);
|
|
|
|
|
previousButton->setEnabled(false); // 第一题时禁用
|
|
|
|
|
|
|
|
|
|
nextButton = new QPushButton("下一题");
|
|
|
|
|
nextButton->setStyleSheet("QPushButton {"
|
|
|
|
|
"background-color: #3498db;"
|
|
|
|
|
"color: white;"
|
|
|
|
|
"border: none;"
|
|
|
|
|
"padding: 10px 20px;"
|
|
|
|
|
"font-size: 14px;"
|
|
|
|
|
"border-radius: 5px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QPushButton:hover {"
|
|
|
|
|
"background-color: #2980b9;"
|
|
|
|
|
"}");
|
|
|
|
|
nextButton->setFixedWidth(120);
|
|
|
|
|
|
|
|
|
|
submitButton = new QPushButton("提交试卷");
|
|
|
|
|
submitButton->setStyleSheet("QPushButton {"
|
|
|
|
|
"background-color: #e74c3c;"
|
|
|
|
|
"color: white;"
|
|
|
|
|
"border: none;"
|
|
|
|
|
"padding: 10px 20px;"
|
|
|
|
|
"font-size: 14px;"
|
|
|
|
|
"border-radius: 5px;"
|
|
|
|
|
"}"
|
|
|
|
|
"QPushButton:hover {"
|
|
|
|
|
"background-color: #c0392b;"
|
|
|
|
|
"}");
|
|
|
|
|
submitButton->setFixedWidth(120);
|
|
|
|
|
submitButton->setVisible(false);
|
|
|
|
|
|
|
|
|
|
buttonLayout->addStretch();
|
|
|
|
|
buttonLayout->addWidget(previousButton);
|
|
|
|
|
buttonLayout->addSpacing(10);
|
|
|
|
|
buttonLayout->addWidget(nextButton);
|
|
|
|
|
buttonLayout->addSpacing(10);
|
|
|
|
|
buttonLayout->addWidget(submitButton);
|
|
|
|
|
buttonLayout->addStretch();
|
|
|
|
|
|
|
|
|
|
// 添加到主布局
|
|
|
|
|
mainLayout->addWidget(progressLabel);
|
|
|
|
|
mainLayout->addWidget(questionGroup);
|
|
|
|
|
mainLayout->addWidget(optionsGroup);
|
|
|
|
|
mainLayout->addSpacing(20);
|
|
|
|
|
mainLayout->addWidget(buttonWidget);
|
|
|
|
|
mainLayout->addStretch();
|
|
|
|
|
|
|
|
|
|
// 连接信号槽
|
|
|
|
|
connect(previousButton, &QPushButton::clicked, this, &ExamWidget::onPreviousClicked);
|
|
|
|
|
connect(nextButton, &QPushButton::clicked, this, &ExamWidget::onNextClicked);
|
|
|
|
|
connect(submitButton, &QPushButton::clicked, this, &ExamWidget::onSubmitClicked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::startExam(Grade grade, int questionCount)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
qDebug() << "ExamWidget: 开始考试,年级:" << static_cast<int>(grade) << "题目数量:" << questionCount;
|
|
|
|
|
|
|
|
|
|
questionGenerator.setGrade(grade);
|
|
|
|
|
questions = questionGenerator.generateQuestions(questionCount);
|
|
|
|
|
totalQuestions = static_cast<int>(questions.size());
|
|
|
|
|
currentQuestion = 0;
|
|
|
|
|
userAnswers.clear();
|
|
|
|
|
correctAnswers.clear();
|
|
|
|
|
questionOptions.clear();
|
|
|
|
|
|
|
|
|
|
if (questions.empty()) {
|
|
|
|
|
qDebug() << "ExamWidget: 题目生成失败";
|
|
|
|
|
QMessageBox::warning(this, "错误", "题目生成失败,请重试");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qDebug() << "ExamWidget: 成功生成" << totalQuestions << "个题目";
|
|
|
|
|
|
|
|
|
|
// 预计算所有题目的正确答案和选项
|
|
|
|
|
for (int i = 0; i < totalQuestions; ++i) {
|
|
|
|
|
try {
|
|
|
|
|
double correctValue = questionGenerator.calculateCorrectAnswer(questions[i]);
|
|
|
|
|
|
|
|
|
|
// 生成选项并保存
|
|
|
|
|
std::vector<std::wstring> options = questionGenerator.generateMeaningfulOptions(correctValue);
|
|
|
|
|
questionOptions.push_back(options);
|
|
|
|
|
|
|
|
|
|
// 找到正确答案的索引
|
|
|
|
|
int correctIndex = 0;
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < options.size(); ++j) {
|
|
|
|
|
try {
|
|
|
|
|
double optionValue = std::stod(options[j]);
|
|
|
|
|
if (std::abs(optionValue - correctValue) < 0.0001) {
|
|
|
|
|
correctIndex = static_cast<int>(j);
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch (...) {
|
|
|
|
|
// 如果转换失败,使用字符串比较
|
|
|
|
|
std::stringstream oss;
|
|
|
|
|
oss << std::fixed << std::setprecision(6) << correctValue;
|
|
|
|
|
std::string correctStr = oss.str();
|
|
|
|
|
std::wstring wcorrectStr(correctStr.begin(), correctStr.end());
|
|
|
|
|
|
|
|
|
|
if (options[j] == wcorrectStr) {
|
|
|
|
|
correctIndex = static_cast<int>(j);
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
|
qDebug() << "警告:未找到第" << i << "题的正确答案在选项中";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
correctAnswers.push_back(correctIndex);
|
|
|
|
|
qDebug() << "ExamWidget: 第" << i << "题正确答案:" << correctValue << "索引:" << correctIndex;
|
|
|
|
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
qDebug() << "ExamWidget: 计算第" << i << "题答案异常:" << e.what();
|
|
|
|
|
correctAnswers.push_back(0); // 默认第一个选项为正确答案
|
|
|
|
|
questionOptions.push_back({L"1.000000", L"2.000000", L"3.000000", L"4.000000"});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showQuestion(0);
|
|
|
|
|
updateButtonStates(); // 更新按钮状态
|
|
|
|
|
qDebug() << "ExamWidget: 考试初始化完成";
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
qDebug() << "ExamWidget: 开始考试异常:" << e.what();
|
|
|
|
|
QMessageBox::critical(this, "错误", QString("考试初始化失败: %1").arg(e.what()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::showQuestion(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index < totalQuestions) {
|
|
|
|
|
currentQuestion = index;
|
|
|
|
|
progressLabel->setText(QString("第 %1 题 / 共 %2 题").arg(index + 1).arg(totalQuestions));
|
|
|
|
|
|
|
|
|
|
// 显示题目
|
|
|
|
|
QString questionText = QString::fromStdWString(questions[index]);
|
|
|
|
|
questionLabel->setText(questionText);
|
|
|
|
|
|
|
|
|
|
// 设置选项文本 - 使用预先生成的选项
|
|
|
|
|
if (static_cast<size_t>(index) < questionOptions.size()) {
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
|
if (i < static_cast<int>(questionOptions[index].size())) {
|
|
|
|
|
optionButtons[i]->setText(QString::fromStdWString(questionOptions[index][i]));
|
|
|
|
|
} else {
|
|
|
|
|
optionButtons[i]->setText(QString("选项 %1").arg(i + 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 清除选择
|
|
|
|
|
optionGroup->setExclusive(false);
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
|
optionButtons[i]->setChecked(false);
|
|
|
|
|
}
|
|
|
|
|
optionGroup->setExclusive(true);
|
|
|
|
|
|
|
|
|
|
// 恢复之前的选择(如果有)
|
|
|
|
|
if (static_cast<size_t>(index) < userAnswers.size()) {
|
|
|
|
|
int previousAnswer = userAnswers[index];
|
|
|
|
|
if (previousAnswer >= 0 && previousAnswer < 4) {
|
|
|
|
|
optionButtons[previousAnswer]->setChecked(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新按钮状态
|
|
|
|
|
updateButtonStates();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::updateButtonStates()
|
|
|
|
|
{
|
|
|
|
|
// 更新上一题按钮状态
|
|
|
|
|
previousButton->setEnabled(currentQuestion > 0);
|
|
|
|
|
|
|
|
|
|
// 更新下一题和提交按钮状态
|
|
|
|
|
nextButton->setVisible(currentQuestion < totalQuestions - 1);
|
|
|
|
|
submitButton->setVisible(currentQuestion == totalQuestions - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::generateOptions()
|
|
|
|
|
{
|
|
|
|
|
// 这个方法现在不再使用,因为选项在startExam中已经预生成
|
|
|
|
|
// 保留这个方法是为了保持接口兼容性
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::onPreviousClicked()
|
|
|
|
|
{
|
|
|
|
|
// 保存当前答案
|
|
|
|
|
int selected = optionGroup->checkedId();
|
|
|
|
|
if (selected != -1) {
|
|
|
|
|
if (static_cast<size_t>(currentQuestion) >= userAnswers.size()) {
|
|
|
|
|
userAnswers.resize(currentQuestion + 1);
|
|
|
|
|
}
|
|
|
|
|
userAnswers[currentQuestion] = selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示上一题
|
|
|
|
|
if (currentQuestion > 0) {
|
|
|
|
|
showQuestion(currentQuestion - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::onNextClicked()
|
|
|
|
|
{
|
|
|
|
|
int selected = optionGroup->checkedId();
|
|
|
|
|
if (selected == -1) {
|
|
|
|
|
QMessageBox::warning(this, "提示", "请选择一个答案");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存当前答案
|
|
|
|
|
if (static_cast<size_t>(currentQuestion) >= userAnswers.size()) {
|
|
|
|
|
userAnswers.resize(currentQuestion + 1);
|
|
|
|
|
}
|
|
|
|
|
userAnswers[currentQuestion] = selected;
|
|
|
|
|
|
|
|
|
|
showQuestion(currentQuestion + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExamWidget::onSubmitClicked()
|
|
|
|
|
{
|
|
|
|
|
int selected = optionGroup->checkedId();
|
|
|
|
|
if (selected == -1) {
|
|
|
|
|
QMessageBox::warning(this, "提示", "请选择一个答案");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存最后一题的答案
|
|
|
|
|
if (static_cast<size_t>(currentQuestion) >= userAnswers.size()) {
|
|
|
|
|
userAnswers.resize(currentQuestion + 1);
|
|
|
|
|
}
|
|
|
|
|
userAnswers[currentQuestion] = selected;
|
|
|
|
|
|
|
|
|
|
// 计算分数 - 使用预先生成的选项进行比较
|
|
|
|
|
int score = 0;
|
|
|
|
|
for (size_t i = 0; i < userAnswers.size(); ++i) {
|
|
|
|
|
if (i < correctAnswers.size() && i < questionOptions.size()) {
|
|
|
|
|
if (userAnswers[i] >= 0 && userAnswers[i] < static_cast<int>(questionOptions[i].size())) {
|
|
|
|
|
std::wstring selectedOption = questionOptions[i][userAnswers[i]];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
double selectedValue = std::stod(selectedOption);
|
|
|
|
|
double correctValue = questionGenerator.calculateCorrectAnswer(questions[i]);
|
|
|
|
|
|
|
|
|
|
qDebug() << "第" << i << "题 - 选择值:" << selectedValue
|
|
|
|
|
<< "正确答案:" << correctValue
|
|
|
|
|
<< "差值:" << std::abs(selectedValue - correctValue);
|
|
|
|
|
|
|
|
|
|
if (std::abs(selectedValue - correctValue) < 1e-5) {
|
|
|
|
|
score++;
|
|
|
|
|
qDebug() << "第" << i << "题回答正确";
|
|
|
|
|
} else {
|
|
|
|
|
qDebug() << "第" << i << "题回答错误,选择值:" << selectedValue
|
|
|
|
|
<< "正确答案:" << correctValue;
|
|
|
|
|
}
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
qDebug() << "第" << i << "题数值转换异常:" << e.what();
|
|
|
|
|
// 回退到索引比较
|
|
|
|
|
if (userAnswers[i] == correctAnswers[i]) {
|
|
|
|
|
score++;
|
|
|
|
|
qDebug() << "第" << i << "题通过索引比较回答正确";
|
|
|
|
|
} else {
|
|
|
|
|
qDebug() << "第" << i << "题通过索引比较回答错误";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qDebug() << "第" << i << "题用户答案索引越界:" << userAnswers[i];
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qDebug() << "第" << i << "题数据不完整";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qDebug() << "ExamWidget: 考试完成,分数:" << score << "/" << totalQuestions;
|
|
|
|
|
emit examFinished(score, totalQuestions);
|
|
|
|
|
}
|