parent
9d17e676fd
commit
9d7116793b
@ -0,0 +1,257 @@
|
|||||||
|
package com.example.mathsystemtogether;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试进行界面控制器
|
||||||
|
*/
|
||||||
|
public class ExamTakingController {
|
||||||
|
|
||||||
|
// 界面控件
|
||||||
|
@FXML private Label examInfoLabel;
|
||||||
|
@FXML private Label questionNumberLabel;
|
||||||
|
@FXML private Label timeLabel;
|
||||||
|
@FXML private Label questionTextLabel;
|
||||||
|
@FXML private RadioButton optionA;
|
||||||
|
@FXML private RadioButton optionB;
|
||||||
|
@FXML private RadioButton optionC;
|
||||||
|
@FXML private RadioButton optionD;
|
||||||
|
@FXML private ToggleGroup answerGroup;
|
||||||
|
@FXML private Button submitButton;
|
||||||
|
@FXML private Button nextButton;
|
||||||
|
@FXML private Button exitExamButton;
|
||||||
|
@FXML private ProgressBar progressBar;
|
||||||
|
@FXML private Label progressLabel;
|
||||||
|
|
||||||
|
// 数据成员
|
||||||
|
private List<Question> examQuestions;
|
||||||
|
private int currentQuestionIndex = 0;
|
||||||
|
private Map<Integer, String> userAnswers = new HashMap<>();
|
||||||
|
private ChoiceQuestionGenerator questionGenerator;
|
||||||
|
private String username;
|
||||||
|
private String level;
|
||||||
|
private int totalQuestions;
|
||||||
|
private Timeline timer;
|
||||||
|
private int remainingSeconds;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
setupAnswerGroup();
|
||||||
|
setupTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAnswerGroup() {
|
||||||
|
answerGroup = new ToggleGroup();
|
||||||
|
optionA.setToggleGroup(answerGroup);
|
||||||
|
optionB.setToggleGroup(answerGroup);
|
||||||
|
optionC.setToggleGroup(answerGroup);
|
||||||
|
optionD.setToggleGroup(answerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTimer() {
|
||||||
|
// 设置30分钟倒计时
|
||||||
|
remainingSeconds = 30 * 60; // 30分钟 = 1800秒
|
||||||
|
timer = new Timeline(new KeyFrame(Duration.seconds(1), e -> updateTimer()));
|
||||||
|
timer.setCycleCount(Timeline.INDEFINITE);
|
||||||
|
timer.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimer() {
|
||||||
|
remainingSeconds--;
|
||||||
|
if (remainingSeconds <= 0) {
|
||||||
|
timer.stop();
|
||||||
|
handleTimeUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = remainingSeconds / 60;
|
||||||
|
int seconds = remainingSeconds % 60;
|
||||||
|
timeLabel.setText(String.format("⏰ 剩余时间: %02d:%02d", minutes, seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTimeUp() {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||||
|
alert.setTitle("时间到");
|
||||||
|
alert.setHeaderText("考试时间已到!");
|
||||||
|
alert.setContentText("系统将自动提交您的答案。");
|
||||||
|
alert.showAndWait();
|
||||||
|
showResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExamData(List<Question> questions, String username, String level) {
|
||||||
|
this.examQuestions = questions;
|
||||||
|
this.username = username;
|
||||||
|
this.level = level;
|
||||||
|
this.totalQuestions = questions.size();
|
||||||
|
|
||||||
|
examInfoLabel.setText(String.format("👤 %s | 📚 %s | 📝 %d题", username, level, totalQuestions));
|
||||||
|
displayCurrentQuestion();
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayCurrentQuestion() {
|
||||||
|
if (currentQuestionIndex >= examQuestions.size()) {
|
||||||
|
showResults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Question question = examQuestions.get(currentQuestionIndex);
|
||||||
|
questionNumberLabel.setText(String.format("第 %d 题 / 共 %d 题",
|
||||||
|
currentQuestionIndex + 1, examQuestions.size()));
|
||||||
|
questionTextLabel.setText(question.getQuestionText());
|
||||||
|
|
||||||
|
optionA.setText("A. " + question.getOptionA());
|
||||||
|
optionB.setText("B. " + question.getOptionB());
|
||||||
|
optionC.setText("C. " + question.getOptionC());
|
||||||
|
optionD.setText("D. " + question.getOptionD());
|
||||||
|
|
||||||
|
// 清除之前的选择
|
||||||
|
answerGroup.selectToggle(null);
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
if (currentQuestionIndex == examQuestions.size() - 1) {
|
||||||
|
submitButton.setText("✅ 提交并完成考试");
|
||||||
|
nextButton.setVisible(false);
|
||||||
|
} else {
|
||||||
|
submitButton.setText("✅ 提交答案");
|
||||||
|
nextButton.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgress() {
|
||||||
|
double progress = (double) currentQuestionIndex / examQuestions.size();
|
||||||
|
progressBar.setProgress(progress);
|
||||||
|
progressLabel.setText(String.format("进度: %d/%d (%.1f%%)",
|
||||||
|
currentQuestionIndex + 1, examQuestions.size(), progress * 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void handleSubmitAnswer() {
|
||||||
|
RadioButton selectedOption = (RadioButton) answerGroup.getSelectedToggle();
|
||||||
|
if (selectedOption == null) {
|
||||||
|
showAlert("请选择一个答案", "请先选择答案再提交");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String answer = selectedOption.getText().substring(0, 1); // 获取A、B、C、D
|
||||||
|
userAnswers.put(currentQuestionIndex, answer);
|
||||||
|
|
||||||
|
if (currentQuestionIndex == examQuestions.size() - 1) {
|
||||||
|
// 最后一题,显示结果
|
||||||
|
timer.stop();
|
||||||
|
showResults();
|
||||||
|
} else {
|
||||||
|
// 下一题
|
||||||
|
currentQuestionIndex++;
|
||||||
|
displayCurrentQuestion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void handleNextQuestion() {
|
||||||
|
currentQuestionIndex++;
|
||||||
|
displayCurrentQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void handleExitExam() {
|
||||||
|
if (showExitConfirmation()) {
|
||||||
|
timer.stop();
|
||||||
|
returnToMainMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showExitConfirmation() {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
alert.setTitle("确认退出");
|
||||||
|
alert.setHeaderText("确定要退出考试吗?");
|
||||||
|
alert.setContentText("退出后当前进度将丢失。");
|
||||||
|
return alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAlert(String title, String message) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showResults() {
|
||||||
|
try {
|
||||||
|
// 停止计时器
|
||||||
|
timer.stop();
|
||||||
|
|
||||||
|
// 计算成绩
|
||||||
|
int correctCount = 0;
|
||||||
|
StringBuilder resultDetails = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < examQuestions.size(); i++) {
|
||||||
|
Question question = examQuestions.get(i);
|
||||||
|
String userAnswer = userAnswers.get(i);
|
||||||
|
boolean isCorrect = question.isCorrect(userAnswer);
|
||||||
|
|
||||||
|
if (isCorrect) {
|
||||||
|
correctCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultDetails.append("第").append(i + 1).append("题: ");
|
||||||
|
resultDetails.append(question.getQuestionText()).append("\n");
|
||||||
|
resultDetails.append("你的答案: ").append(userAnswer != null ? userAnswer : "未作答").append(" ");
|
||||||
|
resultDetails.append("正确答案: ").append(question.getCorrectAnswer()).append(" ");
|
||||||
|
resultDetails.append(isCorrect ? "✓" : "✗").append("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int score = (int) Math.round((double) correctCount / examQuestions.size() * 100);
|
||||||
|
|
||||||
|
// 显示结果对话框
|
||||||
|
Alert resultAlert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
resultAlert.setTitle("考试完成");
|
||||||
|
resultAlert.setHeaderText("🎉 考试结果");
|
||||||
|
resultAlert.setContentText(String.format(
|
||||||
|
"得分: %d分\n正确: %d题\n总计: %d题\n正确率: %.1f%%",
|
||||||
|
score, correctCount, examQuestions.size(), (double) correctCount / examQuestions.size() * 100
|
||||||
|
));
|
||||||
|
resultAlert.showAndWait();
|
||||||
|
|
||||||
|
// 返回主菜单
|
||||||
|
returnToMainMenu();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
showAlert("错误", "显示结果时出错:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnToMainMenu() {
|
||||||
|
try {
|
||||||
|
// 关闭当前窗口
|
||||||
|
Stage currentStage = (Stage) submitButton.getScene().getWindow();
|
||||||
|
currentStage.close();
|
||||||
|
|
||||||
|
// 打开主菜单
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-view.fxml"));
|
||||||
|
Scene scene = new Scene(loader.load(), 1000, 900);
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.setTitle("数学考试系统");
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.show();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
showAlert("错误", "返回主菜单时出错:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.mathsystemtogether.ExamTakingController" style="-fx-background-color: linear-gradient(to bottom, #6A0DAD, #4B0082, #2E0854);">
|
||||||
|
<top>
|
||||||
|
<VBox spacing="15.0" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC, #8B008B); -fx-background-radius: 15; -fx-padding: 20;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="15.0" left="20.0" right="20.0" top="20.0"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="🎓 数学考试进行中" textFill="white" textAlignment="CENTER" style="-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.5), 10, 0, 0, 2);">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="examInfoLabel" text="考试信息" textFill="#E6E6FA" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System" size="14.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
</top>
|
||||||
|
<center>
|
||||||
|
<VBox spacing="20.0" style="-fx-padding: 20;">
|
||||||
|
<!-- 题目信息区域 -->
|
||||||
|
<HBox spacing="20.0" alignment="CENTER" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC); -fx-background-radius: 20; -fx-padding: 15; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 8, 0, 0, 3);">
|
||||||
|
<Label fx:id="questionNumberLabel" text="第 1 题 / 共 10 题" textFill="white" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="timeLabel" text="⏰ 剩余时间: 30:00" textFill="white" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- 题目显示区域 -->
|
||||||
|
<VBox spacing="20.0" style="-fx-background-color: linear-gradient(to bottom, #F0E6FF, #E6E6FA, #DDA0DD); -fx-padding: 30; -fx-background-radius: 20; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.3), 15, 0, 0, 5);">
|
||||||
|
<Label text="📝 题目" textFill="#4B0082" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="18.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Label fx:id="questionTextLabel" textFill="#4B0082" textAlignment="CENTER" wrapText="true" prefHeight="100.0" minHeight="100.0" style="-fx-background-color: linear-gradient(to bottom, white, #F8F0FF); -fx-padding: 25; -fx-background-radius: 15; -fx-border-color: #8A2BE2; -fx-border-radius: 15; -fx-border-width: 3; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.2), 8, 0, 0, 3);">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="20.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<!-- 选项区域 -->
|
||||||
|
<VBox spacing="15.0" style="-fx-padding: 20; -fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 5, 0, 0, 2);">
|
||||||
|
<Label text="🔘 请选择答案" textFill="#4B0082" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<VBox spacing="12.0">
|
||||||
|
<RadioButton fx:id="optionA" text="A. 选项A" prefHeight="45.0" minHeight="45.0" style="-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #4B0082; -fx-padding: 12; -fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 10; -fx-border-color: #8A2BE2; -fx-border-radius: 10; -fx-border-width: 2; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 3, 0, 0, 1);"/>
|
||||||
|
<RadioButton fx:id="optionB" text="B. 选项B" prefHeight="45.0" minHeight="45.0" style="-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #4B0082; -fx-padding: 12; -fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 10; -fx-border-color: #8A2BE2; -fx-border-radius: 10; -fx-border-width: 2; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 3, 0, 0, 1);"/>
|
||||||
|
<RadioButton fx:id="optionC" text="C. 选项C" prefHeight="45.0" minHeight="45.0" style="-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #4B0082; -fx-padding: 12; -fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 10; -fx-border-color: #8A2BE2; -fx-border-radius: 10; -fx-border-width: 2; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 3, 0, 0, 1);"/>
|
||||||
|
<RadioButton fx:id="optionD" text="D. 选项D" prefHeight="45.0" minHeight="45.0" style="-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #4B0082; -fx-padding: 12; -fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 10; -fx-border-color: #8A2BE2; -fx-border-radius: 10; -fx-border-width: 2; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 3, 0, 0, 1);"/>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<!-- 操作按钮区域 -->
|
||||||
|
<HBox spacing="20.0" alignment="CENTER" style="-fx-background-color: rgba(255,255,255,0.9); -fx-background-radius: 15; -fx-padding: 20; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.2), 8, 0, 0, 3);">
|
||||||
|
<Button fx:id="submitButton" text="✅ 提交答案" onAction="#handleSubmitAnswer" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 12; -fx-padding: 15 30; -fx-font-size: 16; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
|
||||||
|
<Button fx:id="nextButton" text="➡️ 下一题" onAction="#handleNextQuestion" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC); -fx-text-fill: white; -fx-background-radius: 12; -fx-padding: 15 30; -fx-font-size: 16; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
|
||||||
|
<Button fx:id="exitExamButton" text="🚪 退出考试" onAction="#handleExitExam" style="-fx-background-color: linear-gradient(to right, #DC143C, #B22222); -fx-text-fill: white; -fx-background-radius: 12; -fx-padding: 15 30; -fx-font-size: 16; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<VBox spacing="10.0" alignment="CENTER">
|
||||||
|
<Label text="📊 考试进度" textFill="#4B0082" textAlignment="CENTER">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<ProgressBar fx:id="progressBar" prefWidth="400.0" style="-fx-background-radius: 10; -fx-border-radius: 10; -fx-border-color: #8A2BE2; -fx-border-width: 2;"/>
|
||||||
|
<Label fx:id="progressLabel" textFill="#4B0082" textAlignment="CENTER" style="-fx-background-color: rgba(255,255,255,0.9); -fx-padding: 8 15; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.2), 3, 0, 0, 1);">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0"/>
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
||||||
Loading…
Reference in new issue