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

+
- -