|
|
// 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);
|
|
|
}
|
|
|
} |