题型添加

master
qmstyle 2025-03-21 11:49:26 +08:00
parent eeebf4bcb1
commit 1aaf69fbdf
7 changed files with 332 additions and 95 deletions

View File

@ -5,18 +5,23 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zhangmeng.online.exam.ui.api.ApiUtils;
import com.zhangmeng.online.exam.ui.utils.HttpUtils;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.StyleableProperty;
import javafx.geometry.Insets;
import javafx.scene.AccessibleRole;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import java.util.*;
/**
*
*
*
* @author zm
@ -25,10 +30,33 @@ import java.util.*;
*/
public class ExamComponent extends ScrollPane {
//计数器
private final SimpleIntegerProperty SINGLE_CHOICE_COUNT = new SimpleIntegerProperty(1);
private final SimpleIntegerProperty MULTIPLE_CHOICE_COUNT = new SimpleIntegerProperty(1);
private final SimpleIntegerProperty JUDGMENT_COUNT = new SimpleIntegerProperty(1);
private final SimpleIntegerProperty NUMERICAL_COUNT = new SimpleIntegerProperty(1);
private final SimpleIntegerProperty Fill_IN_THE_BLANKS_COUNT = new SimpleIntegerProperty(1);
private final SimpleIntegerProperty SHORT_ANSWER_COUNT = new SimpleIntegerProperty(1);
// 单选题
private final ObservableList<SingleChoiceComponent> singleChoiceComponents = FXCollections.observableArrayList();
// 多选题
private final ObservableList<MultiChoiceComponent> multiChoiceComponents = FXCollections.observableArrayList();
// 判断题
private final ObservableList<TrueFalseComponent> judgmentComponents = FXCollections.observableArrayList();
// 计算题
private final ObservableList<NumericalComponent> numericalComponents = FXCollections.observableArrayList();
// 填空题
private final ObservableList<FillBlankComponent> fillInBlankComponents = FXCollections.observableArrayList();
// 简答题
private final ObservableList<ShortAnswerComponent> shortAnswerComponents = FXCollections.observableArrayList();
private VBox vBox;
public ExamComponent() {
this.vBox = new VBox(5);
this.vBox.setPadding(new Insets(10));
Map<String, Object> params = new HashMap<>();
params.put("pageNum", ApiUtils.PAGE_NUM);
@ -39,48 +67,126 @@ public class ExamComponent extends ScrollPane {
JSONArray data = jsonObject.getJSONArray("data");
int total = jsonObject.getIntValue("total");
int index = 1;
for (Object datum : data) {
JSONObject question = (JSONObject) datum;
String type = question.getString("type");
switch (type) {
case "单选题" -> {
String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class);
String correctAnswer = null;
List<Map<String,Object>> optionList = new ArrayList<>();
for (Map map : maps) {
boolean isAnswer = (boolean) map.get("isAnswer");
Map<String,Object> option = new HashMap<>();
option.put("id", map.get("id"));
option.put("text",map.get("name").toString()+ map.get("content").toString());
option.put("isAnswer",isAnswer);
optionList.add(option);
}
SingleChoiceComponent singleChoiceComponent = new SingleChoiceComponent(index + "." + question.getString("name"), optionList);
singleChoiceComponent.setCorrectAnswer(correctAnswer);
Map<String,Object> context = new HashMap<>();
context.put("question_id", question.getString("id"));
context.put("paper_id",1);
singleChoiceComponent.setContext(context);
this.vBox.getChildren().add(singleChoiceComponent);
index ++;
}
// case "多选题" -> MULTIPLE_CHOICE_SCORE.set(MULTIPLE_CHOICE_SCORE.get() + score);
// case "判断题" -> JUDGMENT_SCORE.set(JUDGMENT_SCORE.get() + score);
// case "计算题" -> NUMERICAL_SCORE.set(NUMERICAL_SCORE.get() + score);
// case "填空题" -> Fill_IN_THE_BLANKS.set(Fill_IN_THE_BLANKS.get() + score);
// case "简答题" -> SHORT_ANSWER_SCORE.set(SHORT_ANSWER_SCORE.get() + score);
// default -> throw new IllegalStateException("Unexpected value: " + type.get());
case "单选题" -> singleChoice(question);
case "多选题" -> multiChoice(question);
case "判断题" -> judgment(question);
case "计算题" -> numerical(question);
case "填空题" -> fillInBlank(question);
case "简答题" -> shortAnswer(question);
default -> throw new IllegalStateException("Unexpected value: " + type);
}
}
Label label = new Label("一. 选择题");
label.setFont(new Font("黑体", 18));
this.vBox.getChildren().add(label);
this.vBox.getChildren().addAll(singleChoiceComponents);
this.vBox.getChildren().addAll(multiChoiceComponents);
this.vBox.getChildren().addAll(judgmentComponents);
this.vBox.getChildren().addAll(fillInBlankComponents);
this.vBox.getChildren().addAll(shortAnswerComponents);
// this.getChildren().addAll(numericalComponents);
this.setContent(vBox);
this.setFitToWidth(true);
this.setFitToHeight(true);
}
//简答题
private void shortAnswer(JSONObject question) {
String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class);
List<Map<String, Object>> optionList = new ArrayList<>();
for (Map map : maps) {
boolean isAnswer = (boolean) map.get("isAnswer");
Map<String, Object> option = new HashMap<>();
option.put("id", map.get("id").toString());
option.put("text", map.get("name").toString() + map.get("content").toString());
option.put("isAnswer", isAnswer);
option.put("option", map.get("name").toString().trim().replace(".", ""));
option.put("content", map.get("content").toString());
optionList.add(option);
}
ShortAnswerComponent shortAnswerComponent = new ShortAnswerComponent(SINGLE_CHOICE_COUNT.get() + "." + question.getString("name"), optionList);
Map<String, Object> context = new HashMap<>();
context.put("question_id", question.getString("id"));
context.put("paper_id", 1);
shortAnswerComponent.setContext(context);
shortAnswerComponents.add(shortAnswerComponent);
SHORT_ANSWER_COUNT.set(SHORT_ANSWER_COUNT.get() + 1);
}
//填空题
private void fillInBlank(JSONObject question) {
}
//计算题
private void numerical(JSONObject question) {
}
//判断题
private void judgment(JSONObject question) {
String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class);
String correctAnswer = null;
List<Map<String, Object>> optionList = new ArrayList<>();
for (Map map : maps) {
boolean isAnswer = (boolean) map.get("isAnswer");
Map<String, Object> option = new HashMap<>();
option.put("id", map.get("id").toString());
option.put("text", map.get("name").toString() + map.get("content").toString());
option.put("isAnswer", isAnswer);
option.put("option", map.get("name").toString().trim().replace(".", ""));
option.put("content", map.get("content").toString());
optionList.add(option);
}
TrueFalseComponent trueFalseComponent = new TrueFalseComponent(SINGLE_CHOICE_COUNT.get() + "." + question.getString("name"), optionList);
Map<String, Object> context = new HashMap<>();
context.put("question_id", question.getString("id"));
context.put("paper_id", 1);
trueFalseComponent.setContext(context);
judgmentComponents.add(trueFalseComponent);
JUDGMENT_COUNT.set(JUDGMENT_COUNT.get() + 1);
}
//多选题
private void multiChoice(JSONObject question) {
}
//单选题
private void singleChoice(JSONObject question) {
String list = question.getString("options");
List<Map> maps = JSONArray.parseArray(list, Map.class);
String correctAnswer = null;
List<Map<String, Object>> optionList = new ArrayList<>();
for (Map map : maps) {
boolean isAnswer = (boolean) map.get("isAnswer");
Map<String, Object> option = new HashMap<>();
option.put("id", map.get("id").toString());
option.put("text", map.get("name").toString() + map.get("content").toString());
option.put("isAnswer", isAnswer);
option.put("option", map.get("name").toString().trim().replace(".", ""));
option.put("content", map.get("content").toString());
optionList.add(option);
}
SingleChoiceComponent singleChoiceComponent = new SingleChoiceComponent(SINGLE_CHOICE_COUNT.get() + "." + question.getString("name"), optionList);
singleChoiceComponent.setCorrectAnswer(correctAnswer);
Map<String, Object> context = new HashMap<>();
context.put("question_id", question.getString("id"));
context.put("paper_id", 1);
singleChoiceComponent.setContext(context);
singleChoiceComponents.add(singleChoiceComponent);
SINGLE_CHOICE_COUNT.set(SINGLE_CHOICE_COUNT.get() + 1);
}
}

View File

@ -1,10 +1,12 @@
package com.zhangmeng.online.exam.ui.components;
import com.zhangmeng.online.exam.ui.utils.OptionsUtils;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.ArrayList;
@ -25,6 +27,12 @@ public class MultiChoiceComponent extends VBox {
private List<CheckBox> checkBoxes = new ArrayList<>();
private HBox hBox = new HBox();
private int number_of_responses = 0;//作答次数
private final SimpleStringProperty result_answer = new SimpleStringProperty(); // 最终答案
public MultiChoiceComponent(String question, Set<String> options) {
Set<String> selected = new HashSet<>();

View File

@ -0,0 +1,11 @@
package com.zhangmeng.online.exam.ui.components;
import javafx.scene.layout.VBox;
/**
* @author zm
* @date 2025/3/21 9:51
* @version: 1.0
*/
public class NumericalComponent extends VBox {
}

View File

@ -359,7 +359,12 @@ public class PaperViewComponent extends SplitPane {
private void reloadData(List<Map<String, SimpleStringProperty>> changeList) {
for (Map<String, SimpleStringProperty> chooseTableDatum : changeList) {
int score = Integer.parseInt(chooseTableDatum.get("score").get());
int score = 0;
if (chooseTableDatum.get("score").get() != null){
score = Integer.parseInt(chooseTableDatum.get("score").get());
}
SimpleStringProperty type = chooseTableDatum.get("type");
switch (type.get()) {

View File

@ -1,13 +1,16 @@
package com.zhangmeng.online.exam.ui.components;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
import java.util.*;
/**
* @Description:
@ -16,30 +19,55 @@ import java.util.List;
* @version: 1.0
*/
public class ShortAnswerComponent extends VBox {
// 题目数据模型
private static class Question {
String content; // 题目内容
List<String> keywords; // 参考答案关键词(如 ["封装","继承","多态"]
private Map<String,Object> context = new HashMap<>();
public Map<String, Object> getContext() {
return context;
}
// 示例题目
private final Question currentQuestion = new Question();
{
currentQuestion.content = "简述面向对象编程的三大特性";
currentQuestion.keywords = Arrays.asList("封装", "继承", "多态");
public void setContext(Map<String, Object> context) {
this.context = context;
}
public ShortAnswerComponent() {
private String correctAnswer = null; // 预设正确答案
private int number_of_responses = 0;//作答次数
private final SimpleStringProperty result_answer = new SimpleStringProperty(); // 最终答案
private HBox hBox = new HBox();
private Label questionLabel;
private TextArea answerArea;
private List<String> keywords = new ArrayList<>(); // 参考答案关键词(如 ["封装","继承","多态"]
public ShortAnswerComponent(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;");
for (Map<String, Object> option : options) {
keywords.add( option.get("content").toString());
}
// 题目展示
Label questionLabel = new Label(currentQuestion.content);
questionLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
questionLabel = new Label(question);
hBox.getChildren().add(questionLabel);
this.getChildren().add(hBox);
this.setPrefHeight(240);
// 输入组件
TextArea answerArea = new TextArea();
answerArea = new TextArea();
answerArea.setMinHeight(200);
answerArea.setPromptText("在此输入答案建议不少于50字");
answerArea.setPrefRowCount(8);
answerArea.setWrapText(true); // 自动换行‌:ml-citation{ref="1" data="citationList"}
this.getChildren().add(answerArea);
// 功能组件
Button submitBtn = new Button("提交答案");
@ -57,7 +85,7 @@ public class ShortAnswerComponent extends VBox {
// 关键词匹配验证
StringBuilder missingKeys = new StringBuilder();
int matchCount = 0;
for (String keyword : currentQuestion.keywords) {
for (String keyword : keywords) {
if (userAnswer.contains(keyword)) {
matchCount++;
} else {
@ -66,22 +94,27 @@ public class ShortAnswerComponent extends VBox {
}
// 反馈逻辑
if (matchCount == currentQuestion.keywords.size()) {
if (matchCount == keywords.size()) {
resultLabel.setText("✅ 答案完整,包含所有关键词!");
resultLabel.setStyle("-fx-text-fill: #2ecc71;");
} else if (matchCount > 0) {
resultLabel.setText("⚠ 部分正确(缺失:" + missingKeys.substring(1) + "");
resultLabel.setStyle("-fx-text-fill: #f1c40f;");
} else {
resultLabel.setText("❌ 参考答案关键词:" + String.join(",", currentQuestion.keywords));
resultLabel.setText("❌ 参考答案关键词:" + String.join(",", keywords));
resultLabel.setStyle("-fx-text-fill: #e74c3c;");
}
});
// 布局容器
this.setSpacing(20);
this.setPadding(new Insets(20));
this.getChildren().addAll(questionLabel, answerArea, submitBtn, resultLabel);
// this.setSpacing(20);
// this.setPadding(new Insets(20));
this.getChildren().addAll(submitBtn, resultLabel);
// 布局容器
this.setSpacing(10);
this.setPadding(new Insets(15));
this.hBox.getChildren().add(1,resultLabel);
}
}

View File

@ -3,10 +3,8 @@ package com.zhangmeng.online.exam.ui.components;
import com.zhangmeng.online.exam.ui.api.ApiUtils;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.HashMap;
@ -45,11 +43,21 @@ public class SingleChoiceComponent extends VBox {
private final SimpleStringProperty result_answer = new SimpleStringProperty(); // 最终答案
private HBox hBox = new HBox();
private Label questionLabel;
public SingleChoiceComponent(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;");
// 创建题目组件
Label questionLabel = new Label(question);
this.getChildren().add(questionLabel);
questionLabel = new Label(question);
hBox.getChildren().add(questionLabel);
this.getChildren().add(hBox);
ToggleGroup answerGroup = new ToggleGroup(); // 单选按钮组‌:ml-citation{ref="1,2" data="citationList"}
// 创建选项按钮
for (Map<String, Object> option : options) {
@ -105,15 +113,15 @@ public class SingleChoiceComponent extends VBox {
answerGroup.selectedToggleProperty().addListener((ov, old_value, new_value) -> {
RadioButton selected = (RadioButton) new_value;
if (selected != null) {
String answer = selected.getText().substring(0, 1);
result_answer.set(answer);
Map<String, Object> option = (Map<String, Object>) selected.getUserData();
number_of_responses++;
// resultLabel.setText("你的选择:"+ result_answer.get());
// resultLabel.setStyle("-fx-text-fill: #2ecc71;");
String id = (String) selected.getUserData();
context.put("option_id", id);
context.put("option_id", option.get("id"));
result_answer.set(option.get("id").toString());
//提交数据
ApiUtils.submitAnswer(context, result_answer.get());
ApiUtils.submitAnswer(context);
resultLabel.setText("你的选择:"+ option.get("option"));
resultLabel.setStyle("-fx-text-fill: #2ecc71;");
} else {
resultLabel.setText("⚠ 请先选择答案");
@ -123,7 +131,8 @@ public class SingleChoiceComponent extends VBox {
// 布局容器
this.setSpacing(10);
this.setPadding(new Insets(15));
this.getChildren().addAll(resultLabel);
this.hBox.getChildren().add(1,resultLabel);
}
// 封装单选按钮创建方法‌:ml-citation{ref="2,3" data="citationList"}
@ -131,7 +140,7 @@ public class SingleChoiceComponent extends VBox {
RadioButton rb = new RadioButton(option.get("text").toString());
rb.setToggleGroup(group);
rb.setPadding(new Insets(5));
rb.setUserData(option.get("id"));
rb.setUserData(option);
return rb;
}

View File

@ -1,69 +1,134 @@
package com.zhangmeng.online.exam.ui.components;
import com.zhangmeng.online.exam.ui.api.ApiUtils;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
*
* @author zm
* @date 2025/3/4 15:06
* @version: 1.0
*/
public class TrueFalseComponent extends VBox {
// 预设正确答案
private final boolean correctAnswer = true;
private static final String TRUE_TEXT = "✔ 正确";
private static final String FALSE_TEXT = "✘ 错误";
private Map<String, Object> context = new HashMap<>();
public Map<String, Object> getContext() {
return context;
}
public void setContext(Map<String, Object> context) {
this.context = context;
}
private String correctAnswer = null; // 预设正确答案
private int number_of_responses = 0;//作答次数
private final SimpleStringProperty result_answer = new SimpleStringProperty(); // 最终答案
private HBox hBox = new HBox();
private Label questionLabel;
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;");
public TrueFalseComponent() {
// 题目组件
Label questionLabel = new Label("1. JavaFX 是 Oracle 官方提供的 GUI 框架吗?");
questionLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
questionLabel = new Label(question);
hBox.getChildren().add(questionLabel);
this.getChildren().add(hBox);
// 单选按钮组
ToggleGroup answerGroup = new ToggleGroup();
RadioButton trueButton = createRadioButton("✔ 正确", answerGroup);
RadioButton falseButton = createRadioButton("✘ 错误", answerGroup);
// 创建选项按钮
for (Map<String, Object> option : options) {
boolean isAnswer = (boolean) option.get("isAnswer");
if (isAnswer) {
RadioButton trueButton = createRadioButton(TRUE_TEXT, answerGroup,option);
this.getChildren().add(trueButton);
} else {
RadioButton falseButton = createRadioButton(FALSE_TEXT, answerGroup,option);
this.getChildren().add(falseButton);
}
}
// 功能组件
Button submitBtn = new Button("提交答案");
// Button submitBtn = new Button("提交答案");
Label resultLabel = new Label();
// 提交事件处理
submitBtn.setOnAction(e -> {
if (answerGroup.getSelectedToggle() == null) {
resultLabel.setText("⚠ 请先选择答案");
resultLabel.setStyle("-fx-text-fill: #e67e22;");
return;
}
// submitBtn.setOnAction(e -> {
// if (answerGroup.getSelectedToggle() == null) {
// resultLabel.setText("⚠ 请先选择答案");
// resultLabel.setStyle("-fx-text-fill: #e67e22;");
// return;
// }
//
// boolean userAnswer = ((RadioButton) answerGroup.getSelectedToggle()).getText().contains("正确");
// if (userAnswer == correctAnswer) {
// resultLabel.setText("✅ 回答正确!");
// resultLabel.setStyle("-fx-text-fill: #2ecc71;");
// } else {
// resultLabel.setText("❌ 正确答案:" + (correctAnswer ? "正确" : "错误"));
// resultLabel.setStyle("-fx-text-fill: #e74c3c;");
// }
// });
boolean userAnswer = ((RadioButton) answerGroup.getSelectedToggle()).getText().contains("正确");
if (userAnswer == correctAnswer) {
resultLabel.setText("✅ 回答正确!");
answerGroup.selectedToggleProperty().addListener((ov, old_value, new_value) -> {
RadioButton selected = (RadioButton) new_value;
if (selected != null) {
Map<String, Object> option = (Map<String, Object>) selected.getUserData();
number_of_responses++;
context.put("option_id", option.get("id"));
result_answer.set(option.get("id").toString());
//提交数据
ApiUtils.submitAnswer(context);
boolean isAnswer = (boolean) option.get("isAnswer");
if (isAnswer){
resultLabel.setText("你的选择:"+ TRUE_TEXT);
}else {
resultLabel.setText("你的选择:"+ FALSE_TEXT);
}
resultLabel.setStyle("-fx-text-fill: #2ecc71;");
} else {
resultLabel.setText("❌ 正确答案:" + (correctAnswer ? "正确" : "错误"));
resultLabel.setStyle("-fx-text-fill: #e74c3c;");
resultLabel.setText("⚠ 请先选择答案");
}
});
// 布局容器
this.setSpacing(15);
this.setPadding(new Insets(20));
this.getChildren().addAll(questionLabel, trueButton, falseButton, submitBtn, resultLabel);
this.setSpacing(10);
this.setPadding(new Insets(15));
this.hBox.getChildren().add(1, resultLabel);
}
// 创建带样式的单选按钮‌:ml-citation{ref="1,3" data="citationList"}
private RadioButton createRadioButton(String text, ToggleGroup group) {
private RadioButton createRadioButton(String text, ToggleGroup group,Map<String, Object> option) {
RadioButton rb = new RadioButton(text);
rb.setToggleGroup(group);
rb.setStyle("-fx-font-size: 14px; -fx-padding: 5px;");
rb.setUserData(option);
return rb;
}
}