From 05a4511c3c8997464b36e4389e8a04ed13234302 Mon Sep 17 00:00:00 2001
From: Eric050505 <1846688234@qq.com>
Date: Sun, 18 May 2025 16:15:33 +0800
Subject: [PATCH] feat: add fill-in-blank questions and flexible judging modes
This commit introduces the following features:
- Add support for fill-in-blank questions
- Add flexible judging modes (strict/relaxed)
- Implement question type selection (multiple choice/fill-in-blank/mixed)
- Add UI for fill-in-blank questions
- Implement answer validation logic for different modes
---
src/components/App/index.js | 16 ++-
src/components/Main/index.js | 57 +++++++---
src/components/Quiz/index.js | 201 ++++++++++++++++++++++------------
src/constants/judgingMode.js | 14 +++
src/constants/questionMode.js | 19 ++++
5 files changed, 219 insertions(+), 88 deletions(-)
create mode 100644 src/constants/judgingMode.js
create mode 100644 src/constants/questionMode.js
diff --git a/src/components/App/index.js b/src/components/App/index.js
index 922c3993..c8270c22 100644
--- a/src/components/App/index.js
+++ b/src/components/App/index.js
@@ -13,17 +13,21 @@ const App = () => {
const [loadingMessage, setLoadingMessage] = useState(null);
const [data, setData] = useState(null);
const [countdownTime, setCountdownTime] = useState(null);
+ const [questionMode, setQuestionMode] = useState(null);
+ const [judgingMode, setJudgingMode] = useState(null);
const [isQuizStarted, setIsQuizStarted] = useState(false);
const [isQuizCompleted, setIsQuizCompleted] = useState(false);
const [resultData, setResultData] = useState(null);
- const startQuiz = (data, countdownTime) => {
+ const startQuiz = (data, countdownTime, questionMode, judgingMode) => {
setLoading(true);
setLoadingMessage({
title: 'Loading your quiz...',
message: "It won't be long!",
});
setCountdownTime(countdownTime);
+ setQuestionMode(questionMode);
+ setJudgingMode(judgingMode);
setTimeout(() => {
setData(data);
@@ -79,6 +83,8 @@ const App = () => {
setTimeout(() => {
setData(null);
setCountdownTime(null);
+ setQuestionMode(null);
+ setJudgingMode(null);
setIsQuizStarted(false);
setIsQuizCompleted(false);
setResultData(null);
@@ -93,7 +99,13 @@ const App = () => {
)}
{!loading && isQuizStarted && (
-
+
)}
{!loading && isQuizCompleted && (
diff --git a/src/components/Main/index.js b/src/components/Main/index.js
index db93bc2f..0d1fd565 100644
--- a/src/components/Main/index.js
+++ b/src/components/Main/index.js
@@ -19,6 +19,8 @@ import {
QUESTIONS_TYPE,
COUNTDOWN_TIME,
} from '../../constants';
+import QUESTION_MODE from '../../constants/questionMode';
+import JUDGING_MODE from '../../constants/judgingMode';
import { shuffle } from '../../utils';
import Offline from '../Offline';
@@ -28,6 +30,8 @@ const Main = ({ startQuiz }) => {
const [numOfQuestions, setNumOfQuestions] = useState(5);
const [difficulty, setDifficulty] = useState('easy');
const [questionsType, setQuestionsType] = useState('0');
+ const [questionMode, setQuestionMode] = useState('mixed');
+ const [judgingMode, setJudgingMode] = useState('strict');
const [countdownTime, setCountdownTime] = useState({
hours: 0,
minutes: 120,
@@ -47,6 +51,8 @@ const Main = ({ startQuiz }) => {
numOfQuestions &&
difficulty &&
questionsType &&
+ questionMode &&
+ judgingMode &&
(countdownTime.hours || countdownTime.minutes || countdownTime.seconds)
) {
allFieldsSelected = true;
@@ -94,7 +100,9 @@ const Main = ({ startQuiz }) => {
setProcessing(false);
startQuiz(
results,
- countdownTime.hours + countdownTime.minutes + countdownTime.seconds
+ countdownTime.hours + countdownTime.minutes + countdownTime.seconds,
+ questionMode,
+ judgingMode
);
}, 1000)
)
@@ -182,7 +190,33 @@ const Main = ({ startQuiz }) => {
disabled={processing}
/>
-
Please select the countdown time for your quiz.
+ Select question display mode:
+ setQuestionMode(value)}
+ disabled={processing}
+ />
+
+ Select answer checking mode:
+ setJudgingMode(value)}
+ disabled={processing}
+ />
+
+ Please select the countdown time for your quiz:
{
onChange={handleTimeChange}
disabled={processing}
/>
-
-
-
+
-
+ onClick={fetchData}
+ >
+ Start Quiz
+
+
-
);
};
diff --git a/src/components/Quiz/index.js b/src/components/Quiz/index.js
index f909fdde..4fead762 100644
--- a/src/components/Quiz/index.js
+++ b/src/components/Quiz/index.js
@@ -9,6 +9,7 @@ import {
Icon,
Message,
Menu,
+ Input,
Header,
} from 'semantic-ui-react';
import he from 'he';
@@ -16,10 +17,10 @@ import he from 'he';
import Countdown from '../Countdown';
import { getLetter } from '../../utils';
-const Quiz = ({ data, countdownTime, endQuiz }) => {
+const Quiz = ({ data, countdownTime, questionMode, judgingMode, endQuiz }) => {
const [questionIndex, setQuestionIndex] = useState(0);
const [correctAnswers, setCorrectAnswers] = useState(0);
- const [userSlectedAns, setUserSlectedAns] = useState(null);
+ const [userAnswer, setUserAnswer] = useState(null);
const [questionsAndAnswers, setQuestionsAndAnswers] = useState([]);
const [timeTaken, setTimeTaken] = useState(null);
@@ -27,20 +28,41 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
if (questionIndex > 0) window.scrollTo({ top: 0, behavior: 'smooth' });
}, [questionIndex]);
- const handleItemClick = (e, { name }) => {
- setUserSlectedAns(name);
+ const handleMultipleChoiceAnswer = (e, { name }) => {
+ setUserAnswer(name);
+ };
+
+ const handleFillInBlankAnswer = (e, { value }) => {
+ setUserAnswer(value);
+ };
+
+ const isAnswerCorrect = (userAnswer, correctAnswer) => {
+ if (!userAnswer) return false;
+
+ if (questionMode === 'multipleChoice' ||
+ (questionMode === 'mixed' && data[questionIndex].type === 'multiple')) {
+ return userAnswer === he.decode(correctAnswer);
+ }
+
+ // 填空题判断逻辑
+ const decodedCorrectAnswer = he.decode(correctAnswer);
+ if (judgingMode === 'relaxed') {
+ return userAnswer.trim().toLowerCase() === decodedCorrectAnswer.trim().toLowerCase();
+ } else {
+ return userAnswer.trim() === decodedCorrectAnswer.trim();
+ }
};
const handleNext = () => {
let point = 0;
- if (userSlectedAns === he.decode(data[questionIndex].correct_answer)) {
+ if (isAnswerCorrect(userAnswer, data[questionIndex].correct_answer)) {
point = 1;
}
const qna = questionsAndAnswers;
qna.push({
question: he.decode(data[questionIndex].question),
- user_answer: userSlectedAns,
+ user_answer: userAnswer,
correct_answer: he.decode(data[questionIndex].correct_answer),
point,
});
@@ -56,7 +78,7 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
setCorrectAnswers(correctAnswers + point);
setQuestionIndex(questionIndex + 1);
- setUserSlectedAns(null);
+ setUserAnswer(null);
setQuestionsAndAnswers(qna);
};
@@ -69,81 +91,114 @@ const Quiz = ({ data, countdownTime, endQuiz }) => {
});
};
+ const renderAnswerOptions = () => {
+ const currentQuestion = data[questionIndex];
+ const shouldShowMultipleChoice =
+ questionMode === 'multipleChoice' ||
+ (questionMode === 'mixed' && currentQuestion.type === 'multiple');
+
+ if (shouldShowMultipleChoice) {
+ return (
+
+ {currentQuestion.options.map((option, i) => {
+ const letter = getLetter(i);
+ const decodedOption = he.decode(option);
+
+ return (
+
+ {letter}
+ {decodedOption}
+
+ );
+ })}
+
+ );
+ } else {
+ return (
+
+
+ {judgingMode === 'relaxed' && (
+
+
+ Relaxed Mode: Case insensitive, ignores extra spaces
+
+ )}
+
+ );
+ }
+ };
+
return (
-
-
-
-
- -
-
-
-
-
-
- {`Question No.${questionIndex + 1} of ${data.length}`}
-
-
-
-
+
+
+
+ -
+
+
+
+
+
+ {`Question ${questionIndex + 1} of ${data.length}`}
+
+
+
+
+
+
+
+ {`Q. ${he.decode(data[questionIndex].question)}`}
+
-
-
- {`Q. ${he.decode(data[questionIndex].question)}`}
-
-
-
- Please choose one of the following answers:
-
-
-
- {data[questionIndex].options.map((option, i) => {
- const letter = getLetter(i);
- const decodedOption = he.decode(option);
-
- return (
-
- {letter}
- {decodedOption}
-
- );
- })}
-
-
+
+
+ {questionMode === 'multipleChoice' ||
+ (questionMode === 'mixed' && data[questionIndex].type === 'multiple')
+ ? 'Please choose one of the following answers:'
+ : 'Please enter your answer below:'}
+
+
-
-
-
-
-
-
-
-
-
-
+ {renderAnswerOptions()}
+
+
+
+
+
+
+
+
);
};
Quiz.propTypes = {
data: PropTypes.array.isRequired,
countdownTime: PropTypes.number.isRequired,
+ questionMode: PropTypes.string.isRequired,
+ judgingMode: PropTypes.string.isRequired,
endQuiz: PropTypes.func.isRequired,
};
diff --git a/src/constants/judgingMode.js b/src/constants/judgingMode.js
new file mode 100644
index 00000000..c96973a2
--- /dev/null
+++ b/src/constants/judgingMode.js
@@ -0,0 +1,14 @@
+const JUDGING_MODE = [
+ {
+ key: 'strict',
+ text: 'Strict Mode',
+ value: 'strict',
+ },
+ {
+ key: 'relaxed',
+ text: 'Relaxed Mode',
+ value: 'relaxed',
+ },
+];
+
+export default JUDGING_MODE;
\ No newline at end of file
diff --git a/src/constants/questionMode.js b/src/constants/questionMode.js
new file mode 100644
index 00000000..5c7b83ab
--- /dev/null
+++ b/src/constants/questionMode.js
@@ -0,0 +1,19 @@
+const QUESTION_MODE = [
+ {
+ key: 'mixed',
+ text: 'Mixed Questions',
+ value: 'mixed',
+ },
+ {
+ key: 'multipleChoice',
+ text: 'Multiple Choice Only',
+ value: 'multipleChoice',
+ },
+ {
+ key: 'fillInBlank',
+ text: 'Fill in the Blank Only',
+ value: 'fillInBlank',
+ },
+];
+
+export default QUESTION_MODE;
\ No newline at end of file