按钮与试题绑定,动态生效 2025年3月21日17:37:36

master
qmstyle 2025-03-21 17:37:47 +08:00
parent b131f7da38
commit 3a6c6d1f1d
8 changed files with 186 additions and 15 deletions

View File

@ -3,6 +3,7 @@ package com.zhangmeng.online.exam.ui.components;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane; import javafx.scene.layout.FlowPane;
@ -11,6 +12,7 @@ import java.util.Map;
/** /**
* *
*
* @author zm * @author zm
* @date 2025/3/21 13:59 * @date 2025/3/21 13:59
* @version: 1.0 * @version: 1.0
@ -23,7 +25,10 @@ public class ExamButtonComponent extends FlowPane {
private final Map<Button, Integer> buttonMap = new HashMap<>(); private final Map<Button, Integer> buttonMap = new HashMap<>();
public ExamButtonComponent(int count, ExamComponent examComponent) { public ExamButtonComponent(int count, ExamComponent examComponent) {
this.getStylesheets().add(getClass().getResource("/css/button.css").toExternalForm());
this.examComponent = examComponent; this.examComponent = examComponent;
this.countButton.set(count); this.countButton.set(count);
this.setHgap(10); this.setHgap(10);
@ -33,15 +38,33 @@ public class ExamButtonComponent extends FlowPane {
Button button = new Button(String.valueOf(i)); Button button = new Button(String.valueOf(i));
button.setPrefWidth(45); button.setPrefWidth(45);
button.setOnAction(event -> { button.setOnAction(event -> {
//滚动到指定题目 //滚动到指定题目
int currentIndex = buttonMap.get(button); int currentIndex = buttonMap.get(button);
examComponent.scrollToIndex(currentIndex); examComponent.scrollToIndex(currentIndex);
}); });
buttonMap.put(button, i); buttonMap.put(button, i);
Node node = examComponent.getNode(i);
if (node instanceof SingleChoiceComponent singleChoiceComponent) {
singleChoiceComponent.setIndexButton(button);
}
if (node instanceof MultiChoiceComponent multiChoiceComponent) {
multiChoiceComponent.setIndexButton(button);
}
if (node instanceof TrueFalseComponent judgementComponent) {
judgementComponent.setIndexButton(button);
}
if (node instanceof FillBlankComponent fillBlankComponent) {
fillBlankComponent.setIndexButton(button);
}
if (node instanceof ShortAnswerComponent shortAnswerComponent) {
shortAnswerComponent.setIndexButton(button);
}
this.getChildren().add(button); this.getChildren().add(button);
} }
} }

View File

@ -54,6 +54,7 @@ public class ExamComponent extends ScrollPane {
private final ObservableList<ShortAnswerComponent> shortAnswerComponents = FXCollections.observableArrayList(); private final ObservableList<ShortAnswerComponent> shortAnswerComponents = FXCollections.observableArrayList();
private final SimpleIntegerProperty totalCount = new SimpleIntegerProperty(0); private final SimpleIntegerProperty totalCount = new SimpleIntegerProperty(0);
private final SimpleIntegerProperty totalIndex = new SimpleIntegerProperty(1);
private VBox vBox; private VBox vBox;
@ -72,22 +73,35 @@ public class ExamComponent extends ScrollPane {
int total = jsonObject.getIntValue("total"); int total = jsonObject.getIntValue("total");
int index = 1; int index = 1;
List<JSONObject> singleChoice_list = new ArrayList<>();
List<JSONObject> multiChoice_list = new ArrayList<>();
List<JSONObject> judgment_list = new ArrayList<>();
List<JSONObject> numerical_list = new ArrayList<>();
List<JSONObject> fillInBlank_list = new ArrayList<>();
List<JSONObject> shortAnswer_list = new ArrayList<>();
for (Object datum : data) { for (Object datum : data) {
JSONObject question = (JSONObject) datum; JSONObject question = (JSONObject) datum;
String type = question.getString("type"); String type = question.getString("type");
switch (type) { switch (type) {
case "单选题" -> singleChoice(question); case "单选题" -> singleChoice_list.add(question);
case "多选题" -> multiChoice(question); case "多选题" -> multiChoice_list.add(question);
case "判断题" -> judgment(question); case "判断题" -> judgment_list.add(question);
case "计算题" -> numerical(question); case "计算题" -> numerical_list.add(question);
case "填空题" -> fillInBlank(question); case "填空题" -> fillInBlank_list.add(question);
case "简答题" -> shortAnswer(question); case "简答题" -> shortAnswer_list.add(question);
default -> throw new IllegalStateException("Unexpected value: " + type); default -> throw new IllegalStateException("Unexpected value: " + type);
} }
} }
singleChoice_list.forEach(this::singleChoice);
multiChoice_list.forEach(this::multiChoice);
judgment_list.forEach(this::judgment);
numerical_list.forEach(this::numerical);
fillInBlank_list.forEach(this::fillInBlank);
shortAnswer_list.forEach(this::shortAnswer);
int count = singleChoiceComponents.size() + multiChoiceComponents.size() + judgmentComponents.size() + numericalComponents.size() + fillInBlankComponents.size() + shortAnswerComponents.size(); int count = singleChoiceComponents.size() + multiChoiceComponents.size() + judgmentComponents.size() + numericalComponents.size() + fillInBlankComponents.size() + shortAnswerComponents.size();
totalCount.set(count); totalCount.set(count);
@ -96,7 +110,6 @@ public class ExamComponent extends ScrollPane {
// label.setFont(new Font("黑体", 18)); // label.setFont(new Font("黑体", 18));
// this.vBox.getChildren().add(label); // this.vBox.getChildren().add(label);
this.vBox.getChildren().addAll(singleChoiceComponents); this.vBox.getChildren().addAll(singleChoiceComponents);
this.vBox.getChildren().addAll(multiChoiceComponents); this.vBox.getChildren().addAll(multiChoiceComponents);
this.vBox.getChildren().addAll(judgmentComponents); this.vBox.getChildren().addAll(judgmentComponents);
@ -132,6 +145,7 @@ public class ExamComponent extends ScrollPane {
//简答题 //简答题
private void shortAnswer(JSONObject question) { private void shortAnswer(JSONObject question) {
SHORT_ANSWER_COUNT.set(totalIndex.get());
String list = question.getString("options"); String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class); List<Map> maps = JSONArray.parseArray(list, Map.class);
List<Map<String, Object>> optionList = new ArrayList<>(); List<Map<String, Object>> optionList = new ArrayList<>();
@ -152,6 +166,7 @@ public class ExamComponent extends ScrollPane {
shortAnswerComponent.setContext(context); shortAnswerComponent.setContext(context);
shortAnswerComponents.add(shortAnswerComponent); shortAnswerComponents.add(shortAnswerComponent);
SHORT_ANSWER_COUNT.set(SHORT_ANSWER_COUNT.get() + 1); SHORT_ANSWER_COUNT.set(SHORT_ANSWER_COUNT.get() + 1);
totalIndex.set(totalIndex.get() + 1);
} }
//填空题 //填空题
@ -164,7 +179,7 @@ public class ExamComponent extends ScrollPane {
//判断题 //判断题
private void judgment(JSONObject question) { private void judgment(JSONObject question) {
JUDGMENT_COUNT.set(totalIndex.get());
String list = question.getString("options"); String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class); List<Map> maps = JSONArray.parseArray(list, Map.class);
String correctAnswer = null; String correctAnswer = null;
@ -186,6 +201,7 @@ public class ExamComponent extends ScrollPane {
trueFalseComponent.setContext(context); trueFalseComponent.setContext(context);
judgmentComponents.add(trueFalseComponent); judgmentComponents.add(trueFalseComponent);
JUDGMENT_COUNT.set(JUDGMENT_COUNT.get() + 1); JUDGMENT_COUNT.set(JUDGMENT_COUNT.get() + 1);
totalIndex.set(totalIndex.get() + 1);
} }
@ -210,7 +226,7 @@ public class ExamComponent extends ScrollPane {
option.put("content", map.get("content").toString()); option.put("content", map.get("content").toString());
optionList.add(option); optionList.add(option);
} }
SingleChoiceComponent singleChoiceComponent = new SingleChoiceComponent(SINGLE_CHOICE_COUNT.get() + "." + question.getString("name"), optionList); SingleChoiceComponent singleChoiceComponent = new SingleChoiceComponent(SINGLE_CHOICE_COUNT.get() + "." + question.getString("name"), optionList,SINGLE_CHOICE_COUNT.get());
singleChoiceComponent.setCorrectAnswer(correctAnswer); singleChoiceComponent.setCorrectAnswer(correctAnswer);
Map<String, Object> context = new HashMap<>(); Map<String, Object> context = new HashMap<>();
context.put("question_id", question.getString("id")); context.put("question_id", question.getString("id"));
@ -218,6 +234,7 @@ public class ExamComponent extends ScrollPane {
singleChoiceComponent.setContext(context); singleChoiceComponent.setContext(context);
singleChoiceComponents.add(singleChoiceComponent); singleChoiceComponents.add(singleChoiceComponent);
SINGLE_CHOICE_COUNT.set(SINGLE_CHOICE_COUNT.get() + 1); SINGLE_CHOICE_COUNT.set(SINGLE_CHOICE_COUNT.get() + 1);
totalIndex.set(totalIndex.get() + 1);
} }
public int getTotalCount() { public int getTotalCount() {
@ -232,5 +249,7 @@ public class ExamComponent extends ScrollPane {
this.vBox = vBox; this.vBox = vBox;
} }
public Node getNode(int index){
return this.vBox.getChildren().get(index-1);
}
} }

View File

@ -18,6 +18,8 @@ import java.util.List;
*/ */
public class FillBlankComponent extends VBox { public class FillBlankComponent extends VBox {
private Button indexButton;
// 题目数据模型 // 题目数据模型
private static class Question { private static class Question {
String content; // 如 "Java中多线程可通过实现__1__接口或继承__2__类" String content; // 如 "Java中多线程可通过实现__1__接口或继承__2__类"
@ -92,4 +94,12 @@ public class FillBlankComponent extends VBox {
this.setPadding(new Insets(20)); this.setPadding(new Insets(20));
this.getChildren().addAll(questionBox, submitBtn, resultLabel); this.getChildren().addAll(questionBox, submitBtn, resultLabel);
} }
public Button getIndexButton() {
return indexButton;
}
public void setIndexButton(Button indexButton) {
this.indexButton = indexButton;
}
} }

View File

@ -86,4 +86,9 @@ public class MultiChoiceComponent extends VBox {
cb.setStyle("-fx-font-size: 14px; -fx-text-fill: #444;"); cb.setStyle("-fx-font-size: 14px; -fx-text-fill: #444;");
return cb; return cb;
} }
public void setIndexButton(Button button) {
}
} }

View File

@ -7,8 +7,12 @@ import javafx.geometry.Insets;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import java.util.*; import java.util.*;
@ -20,6 +24,8 @@ import java.util.*;
*/ */
public class ShortAnswerComponent extends VBox { public class ShortAnswerComponent extends VBox {
private Button indexButton;
private Map<String,Object> context = new HashMap<>(); private Map<String,Object> context = new HashMap<>();
public Map<String, Object> getContext() { public Map<String, Object> getContext() {
@ -44,6 +50,20 @@ public class ShortAnswerComponent extends VBox {
private List<String> keywords = new ArrayList<>(); // 参考答案关键词(如 ["封装","继承","多态"] private List<String> keywords = new ArrayList<>(); // 参考答案关键词(如 ["封装","继承","多态"]
private static final String DEFAULT_STYLE_CLASS = "button";
public void setBackground() {
indexButton.getStyleClass().remove("custom-button-cancel");
indexButton.getStyleClass().add("custom-button-click");
}
//取消
public void cancelBackground() {
indexButton.getStyleClass().remove("custom-button-click");
indexButton.getStyleClass().add("custom-button-cancel");
}
public ShortAnswerComponent(String question, List<Map<String, Object>> options) { public ShortAnswerComponent(String question, List<Map<String, Object>> options) {
this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" + this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" +
@ -68,6 +88,17 @@ public class ShortAnswerComponent extends VBox {
answerArea.setWrapText(true); // 自动换行‌:ml-citation{ref="1" data="citationList"} answerArea.setWrapText(true); // 自动换行‌:ml-citation{ref="1" data="citationList"}
this.getChildren().add(answerArea); this.getChildren().add(answerArea);
answerArea.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (newValue.length() > 0) {
setBackground();
}else {
cancelBackground();
}
}
});
// 功能组件 // 功能组件
Button submitBtn = new Button("提交答案"); Button submitBtn = new Button("提交答案");
@ -117,4 +148,12 @@ public class ShortAnswerComponent extends VBox {
this.hBox.getChildren().add(1,resultLabel); this.hBox.getChildren().add(1,resultLabel);
} }
public Button getIndexButton() {
return indexButton;
}
public void setIndexButton(Button indexButton) {
this.indexButton = indexButton;
}
} }

View File

@ -27,6 +27,12 @@ import java.util.Map;
*/ */
public class SingleChoiceComponent extends VBox { public class SingleChoiceComponent extends VBox {
private String TYPE = "single_choice";
private Button indexButton;
private int index;//该组件索引
private Map<String,Object> context = new HashMap<>(); private Map<String,Object> context = new HashMap<>();
public Map<String, Object> getContext() { public Map<String, Object> getContext() {
@ -47,8 +53,12 @@ public class SingleChoiceComponent extends VBox {
private Label questionLabel; private Label questionLabel;
public SingleChoiceComponent(String question, List<Map<String, Object>> options) { public void setBackground() {
this.indexButton.setStyle("-fx-background-color: #4698ed;");
}
public SingleChoiceComponent(String question, List<Map<String, Object>> options,int index) {
this.index = index;
this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" + this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" +
"-fx-border-radius: 10px; -fx-border-width: 1px; -fx-border-style: solid;-fx-background-radius: 10px;"); "-fx-border-radius: 10px; -fx-border-width: 1px; -fx-border-style: solid;-fx-background-radius: 10px;");
@ -120,6 +130,11 @@ public class SingleChoiceComponent extends VBox {
result_answer.set(option.get("id").toString()); result_answer.set(option.get("id").toString());
//提交数据 //提交数据
ApiUtils.submitAnswer(context); ApiUtils.submitAnswer(context);
//改变颜色
setBackground();
resultLabel.setText("你的选择:"+ option.get("option")); resultLabel.setText("你的选择:"+ option.get("option"));
resultLabel.setStyle("-fx-text-fill: #2ecc71;"); resultLabel.setStyle("-fx-text-fill: #2ecc71;");
@ -151,4 +166,29 @@ public class SingleChoiceComponent extends VBox {
public void setCorrectAnswer(String correctAnswer) { public void setCorrectAnswer(String correctAnswer) {
this.correctAnswer = correctAnswer; this.correctAnswer = correctAnswer;
} }
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getTYPE() {
return TYPE;
}
public void setTYPE(String TYPE) {
this.TYPE = TYPE;
}
public Button getIndexButton() {
return indexButton;
}
public void setIndexButton(Button indexButton) {
this.indexButton = indexButton;
}
} }

View File

@ -23,6 +23,8 @@ import java.util.Map;
*/ */
public class TrueFalseComponent extends VBox { public class TrueFalseComponent extends VBox {
private Button indexButton;
private static final String TRUE_TEXT = "✔ 正确"; private static final String TRUE_TEXT = "✔ 正确";
private static final String FALSE_TEXT = "✘ 错误"; private static final String FALSE_TEXT = "✘ 错误";
@ -47,6 +49,10 @@ public class TrueFalseComponent extends VBox {
private Label questionLabel; private Label questionLabel;
public void setBackground() {
this.indexButton.setStyle("-fx-background-color: #4698ed;");
}
public TrueFalseComponent(String question, List<Map<String, Object>> options) { public TrueFalseComponent(String question, List<Map<String, Object>> options) {
this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" + "-fx-border-radius: 10px; -fx-border-width: 1px; -fx-border-style: solid;-fx-background-radius: 10px;"); this.setStyle("-fx-background-color: white; -fx-border-color: #eadcdc;" + "-fx-border-radius: 10px; -fx-border-width: 1px; -fx-border-style: solid;-fx-background-radius: 10px;");
@ -112,6 +118,7 @@ public class TrueFalseComponent extends VBox {
resultLabel.setText("你的选择:"+ FALSE_TEXT); resultLabel.setText("你的选择:"+ FALSE_TEXT);
} }
resultLabel.setStyle("-fx-text-fill: #2ecc71;"); resultLabel.setStyle("-fx-text-fill: #2ecc71;");
setBackground();
} else { } else {
resultLabel.setText("⚠ 请先选择答案"); resultLabel.setText("⚠ 请先选择答案");
} }
@ -131,4 +138,12 @@ public class TrueFalseComponent extends VBox {
rb.setUserData(option); rb.setUserData(option);
return rb; return rb;
} }
public Button getIndexButton() {
return indexButton;
}
public void setIndexButton(Button indexButton) {
this.indexButton = indexButton;
}
} }

View File

@ -0,0 +1,20 @@
.custom-button-click {
-fx-background-color: #4698ed;
}
.custom-button-cancel {
-fx-background-color: #dadada43; /* 默认背景色 */
-fx-border-color: #7e7676;
-fx-border-width: 0.5px; /* 默认边框颜色 */
-fx-border-radius: 3px; /* 默认边框圆角 */
}
.custom-button-cancel:focused {
-fx-background-color: derive(#e6e6e6, 20%); /* 焦点时略微变亮 */
-fx-border-color: #0096C9;
-fx-border-width: 1.5px; /* 默认边框颜色 */
}
.custom-button-cancel:hover {
-fx-background-color: derive(#dadada, 20%); /* 鼠标悬停时略微变亮 */
}