You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PAIR/src/main/java/com/pair/ui/QuizPage.java

314 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// com/ui/QuizPage.java
package com.pair.ui;
import com.pair.model.ChoiceQuestion;
import com.pair.service.QuizService;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import java.util.List;
public class QuizPage extends NavigablePanel {
private final QuizService quizService;
private final Label titleLabel = new Label("中小学数学答题系统");
private final Label progressLabel = new Label("完成 0/10");
private final Label questionLabel = new Label("题目加载中...");
private final ToggleGroup optionGroup = new ToggleGroup();
private final RadioButton[] options = new RadioButton[4];
private final Button prevButton = new Button("上一题");
private final Button nextButton = new Button("下一题");
private final Button submitButton = new Button("交卷");
// 题目导航矩阵容器
private final GridPane questionNavGrid = new GridPane();
private int totalQuestions = 10; // 默认10题由外部设置
private int currentQuestionIndex = 0; // 当前题号0-based
public QuizPage(Runnable onBack, QuizService quizService) {
super(onBack);
this.quizService = quizService;
initializeContent();
}
@Override
protected void buildContent() {
// 设置整体布局BorderPane
this.setPadding(new Insets(20));
this.setStyle("-fx-background-color: " + UIConstants.COLOR_BACKGROUND + ";");
// 顶部标题栏
HBox topBar = createTopBar();
this.setTop(topBar);
// 主体内容:左右分栏
HBox mainContent = new HBox(20);
mainContent.setAlignment(Pos.CENTER);
mainContent.setPadding(new Insets(10));
// 左侧:题目内容区
VBox leftPanel = createLeftPanel();
leftPanel.setMaxWidth(600);
// 右侧:题目导航矩阵
VBox rightPanel = createRightPanel();
rightPanel.setMaxWidth(300);
rightPanel.setMinWidth(280);
HBox.setHgrow(leftPanel, Priority.ALWAYS);
HBox.setHgrow(rightPanel, Priority.NEVER);
mainContent.getChildren().addAll(leftPanel, rightPanel);
this.setCenter(mainContent);
// 底部按钮
VBox bottomBarContainer = new VBox(10);
bottomBarContainer.setAlignment(Pos.CENTER);
bottomBarContainer.setPadding(new Insets(10));
nextButton.setStyle(UIConstants.BUTTON_STYLE);
submitButton.setStyle(UIConstants.BUTTON_STYLE + "-fx-background-color: " + UIConstants.COLOR_ERROR + ";");
prevButton.setStyle(UIConstants.BUTTON_STYLE);
nextButton.setPrefSize(UIConstants.BUTTON_WIDTH, UIConstants.BUTTON_HEIGHT);
submitButton.setPrefSize(UIConstants.BUTTON_WIDTH, UIConstants.BUTTON_HEIGHT);
prevButton.setPrefSize(UIConstants.BUTTON_WIDTH, UIConstants.BUTTON_HEIGHT);
HBox buttonRow = new HBox(20);
buttonRow.setAlignment(Pos.CENTER);
buttonRow.getChildren().addAll(prevButton, nextButton, submitButton);
bottomBarContainer.getChildren().add(buttonRow);
this.setBottom(bottomBarContainer);
// 初始化按钮状态
updateButtonVisibility();
}
/**
* 创建顶部标题栏
*/
private HBox createTopBar() {
HBox topBar = new HBox(20);
topBar.setAlignment(Pos.CENTER_LEFT);
topBar.setPadding(new Insets(10));
topBar.setStyle("-fx-background-color: white; -fx-border-width: 0 0 1 0; -fx-border-color: #bdc3c7;");
titleLabel.setFont(Font.font(UIConstants.FONT_FAMILY, FontWeight.BOLD, UIConstants.TITLE_FONT_SIZE));
progressLabel.setStyle("-fx-font-size: " + UIConstants.HINT_FONT_SIZE + "px; -fx-text-fill: " + UIConstants.COLOR_HINT + ";");
topBar.getChildren().addAll(titleLabel, progressLabel);
return topBar;
}
/**
* 创建左侧题目内容面板
*/
private VBox createLeftPanel() {
// 直接使用带样式的 VBox 作为内容容器
VBox content = new VBox(UIConstants.DEFAULT_SPACING);
content.setPadding(new Insets(20));
content.setStyle(UIConstants.FORM_STYLE);
content.setPrefWidth(550);
content.setMinWidth(550);
content.setMaxWidth(550);
content.setPrefHeight(400);
content.setMinHeight(400);
content.setMaxHeight(400);
questionLabel.setWrapText(true);
questionLabel.setPrefWidth(500);
questionLabel.setFont(Font.font(UIConstants.FONT_FAMILY, UIConstants.QUIZ_TITLE_FONT_SIZE));
questionLabel.setStyle("-fx-font-size: " + UIConstants.QUIZ_TITLE_FONT_SIZE + "px; -fx-text-fill: " + UIConstants.COLOR_PRIMARY + ";");
VBox optionsBox = new VBox(10);
optionsBox.setAlignment(Pos.CENTER_LEFT);
for (int i = 0; i < 4; i++) {
options[i] = new RadioButton("选项 " + (char)('A' + i));
options[i].setToggleGroup(optionGroup);
options[i].setStyle("-fx-font-size: " + UIConstants.LABEL_FONT_SIZE + "px;");
optionsBox.getChildren().add(options[i]);
}
content.getChildren().addAll(questionLabel, optionsBox);
return content;
}
/**
* 创建右侧题目导航矩阵面板
*/
private VBox createRightPanel() {
VBox rightPanel = new VBox(10);
rightPanel.setAlignment(Pos.TOP_CENTER);
rightPanel.setPadding(new Insets(20));
rightPanel.setStyle(UIConstants.FORM_STYLE);
Label navTitle = new Label("题目导航");
navTitle.setFont(Font.font(UIConstants.FONT_FAMILY, FontWeight.BOLD, UIConstants.SUBTITLE_FONT_SIZE));
navTitle.setAlignment(Pos.CENTER);
// 初始化题目导航矩阵
initQuestionNavGrid();
rightPanel.getChildren().addAll(navTitle, questionNavGrid);
return rightPanel;
}
/**
* 初始化题目导航网格
*/
private void initQuestionNavGrid() {
questionNavGrid.getChildren().clear();
questionNavGrid.setHgap(5);
questionNavGrid.setVgap(5);
questionNavGrid.setAlignment(Pos.CENTER);
int cols = 5;
int rows = (totalQuestions + cols - 1) / cols;
for (int i = 0; i < totalQuestions; i++) {
int row = i / cols;
int col = i % cols;
String text = String.valueOf(i + 1);
Button btn = new Button(text);
btn.setPrefSize(50, 40);
btn.setFont(Font.font(UIConstants.FONT_FAMILY, UIConstants.LABEL_FONT_SIZE));
btn.setStyle(getButtonStyleForStatus(i));
final int index = i;
btn.setOnAction(e -> goToQuestion(index));
questionNavGrid.add(btn, col, row);
}
}
/**
* 根据题目状态返回按钮样式
* @param index 题号0-based
* @return CSS样式字符串
*/
private String getButtonStyleForStatus(int index) {
if (index == currentQuestionIndex) {
// 当前题
return "-fx-background-color: " + UIConstants.COLOR_ACCENT + "; -fx-text-fill: white; -fx-font-weight: bold;";
} else if (quizService.isAnswered(index)) {
// 已作答
return "-fx-background-color: #2ecc71; -fx-text-fill: white;";
} else {
// 未作答
return "-fx-background-color: #ecf0f1; -fx-text-fill: #2c3e50;";
}
}
/**
* 更新按钮可见性(最后一题显示“交卷”,否则显示“下一题”)
*/
private void updateButtonVisibility() {
if (currentQuestionIndex == totalQuestions - 1) {
nextButton.setVisible(false);
submitButton.setVisible(true);
} else {
nextButton.setVisible(true);
submitButton.setVisible(false);
}
}
/**
* 跳转到指定题目
* @param index 题号0-based
*/
public void goToQuestion(int index) {
currentQuestionIndex = index;
quizService.goToQuestion(index);
updateProgressLabel();
updateQuestionNavButtons();
updateButtonVisibility();
loadQuestion(index);
}
/**
* 更新进度标签
*/
private void updateProgressLabel() {
int answeredCount = quizService.getAnsweredCount();
progressLabel.setText("完成 " + answeredCount + "/" + totalQuestions + " 题");
}
/**
* 更新题目导航按钮样式
*/
private void updateQuestionNavButtons() {
for (Node node : questionNavGrid.getChildren()) {
if (node instanceof Button) {
Button btn = (Button) node;
int index = Integer.parseInt(btn.getText()) - 1; // 转换为0-based
btn.setStyle(getButtonStyleForStatus(index));
}
}
}
/**
* 加载题目(从 QuizService 获取)
* @param index 题号
*/
private void loadQuestion(int index) {
System.out.println("🔄 加载题目 " + index + ", options[0]=" + options[0]);
if (options[0] == null) {
System.err.println("⚠️ RadioButton 未初始化,跳过题目加载");
return; // 防止 NPE
}
ChoiceQuestion question = quizService.getQuestion(index);
if (question == null) return;
// 显示题目
questionLabel.setText("第 " + (index + 1) + " 题:\n" + question.getQuestionText());
List<?> optionsList = question.getOptions();
for (int i = 0; i < 4; i++) {
Object option = optionsList.get(i);
String optionText = option != null ? option.toString() : "未知";
options[i].setText((char)('A' + i) + ". " + optionText);
}
Integer userAnswer = quizService.getUserAnswer(index);
if (userAnswer != null && userAnswer >= 0 && userAnswer < 4) {
optionGroup.selectToggle(options[userAnswer]);
} else {
optionGroup.selectToggle(null);
}
// ✅ 强制刷新 UI可选通常不需要
// Platform.runLater(() -> {
// this.requestLayout(); // 触发重新布局
// });
}
// ========== Getter 方法 ==========
public Label getProgressLabel() { return progressLabel; }
public Label getQuestionLabel() { return questionLabel; }
public RadioButton[] getOptions() { return options; }
public ToggleGroup getOptionGroup() { return optionGroup; }
public Button getNextButton() { return nextButton; }
public Button getPrevButton() { return prevButton; }
public Button getSubmitButton() { return submitButton; }
// ========== 设置器方法 ==========
public void setTotalQuestions(int total) {
this.totalQuestions = total;
initQuestionNavGrid(); // 重新初始化导航矩阵
updateProgressLabel();
}
public void setCurrentQuestionIndex(int index) {
this.currentQuestionIndex = index;
updateQuestionNavButtons();
updateButtonVisibility();
loadQuestion(index);
}
}