feat: 优化敏感词处理和更新拖拽题功能,提升用户体验
- 清理敏感词处理函数中的多余空行,提升代码整洁性。 - 在 HuangyanpeiPlace3 组件中引入拖拽题功能,增强互动性。 - 添加拖拽题的成功和错误提示逻辑,优化用户填写体验。 - 使用 useMemo 优化已访问展品的状态管理,提升性能。
This commit is contained in:
1 parent
45dd648d21
commit
1dd8afcce7
2 files changed
+142
-25
No files matched your search
@@ -54,8 +54,7 @@ export function filterInput(input: string): string {
|
|||||||
if (!input) return input;
|
if (!input) return input;
|
||||||
|
|
||||||
// 先进行敏感词过滤
|
// 先进行敏感词过滤
|
||||||
let filtered = filterSensitiveWords(input);
|
const filtered = filterSensitiveWords(input);
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
ExhibitOverlay,
|
ExhibitOverlay,
|
||||||
Title,
|
Title,
|
||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
GuideSteps,
|
GuideSteps,
|
||||||
GuideStepsItem,
|
GuideStepsItem,
|
||||||
SingleChoiceQuizPanel,
|
SingleChoiceQuizPanel,
|
||||||
|
type DragFillResult,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { SuccessAlert } from "@/components/layout";
|
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useCursorController } from "@/hooks/useCursorController";
|
import { useCursorController } from "@/hooks/useCursorController";
|
||||||
@@ -16,6 +17,7 @@ import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
|||||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||||
|
|
||||||
const starName = "huangyanpei";
|
const starName = "huangyanpei";
|
||||||
|
const place = "place3";
|
||||||
|
|
||||||
const exhibitSrcMap: Record<1 | 2 | 3, string> = {
|
const exhibitSrcMap: Record<1 | 2 | 3, string> = {
|
||||||
1: "huangyanpei/place3/5_005",
|
1: "huangyanpei/place3/5_005",
|
||||||
@@ -23,6 +25,31 @@ const exhibitSrcMap: Record<1 | 2 | 3, string> = {
|
|||||||
3: "huangyanpei/place3/5_007",
|
3: "huangyanpei/place3/5_007",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
const FIRST_QUIZ_SUCCESS_CONTENT =
|
||||||
|
'1903年,黄炎培将"观澜书院"改制为"川沙小学堂",自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行"半日授课、半日实习"的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。';
|
||||||
|
|
||||||
|
const DRAG_QUIZ_SUCCESS_CONTENT = "恭喜你,选择正确!";
|
||||||
|
|
||||||
|
const DRAG_QUIZ_ERROR_MESSAGE =
|
||||||
|
"艺术不是黄炎培先生在川沙小学堂办学期间强调的重点。请你再想想吧!";
|
||||||
|
|
||||||
|
const DRAG_QUIZ_OPTIONS = [
|
||||||
|
{ value: "读书", label: "读书" },
|
||||||
|
{ value: "做手工", label: "做手工" },
|
||||||
|
{ value: "学艺术", label: "学艺术" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DRAG_QUIZ_BLANKS = [
|
||||||
|
{ id: "blank-1", label: "" },
|
||||||
|
{ id: "blank-2", label: "" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const CORRECT_ANSWERS = {
|
||||||
|
blank1: "读书",
|
||||||
|
blank2: "做手工",
|
||||||
|
} as const;
|
||||||
|
|
||||||
const HuangyanpeiPlace3 = () => {
|
const HuangyanpeiPlace3 = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { moduleName } = useParams<{
|
const { moduleName } = useParams<{
|
||||||
@@ -36,12 +63,13 @@ const HuangyanpeiPlace3 = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 使用持久化的访问状态
|
// 使用持久化的访问状态
|
||||||
const place = "place3";
|
|
||||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||||
|
|
||||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
// 从 store 获取已访问的展品
|
||||||
const visitedExhibits = getVisitedExhibits(place);
|
const visitedExhibits = getVisitedExhibits(place);
|
||||||
const visited = new Set(visitedExhibits);
|
|
||||||
|
// 使用 useMemo 优化 visited Set,避免每次渲染都重新创建
|
||||||
|
const visited = useMemo(() => new Set(visitedExhibits), [visitedExhibits]);
|
||||||
|
|
||||||
const { isEnabled } = useCursorController({
|
const { isEnabled } = useCursorController({
|
||||||
initialEnabled: [3],
|
initialEnabled: [3],
|
||||||
@@ -54,8 +82,16 @@ const HuangyanpeiPlace3 = () => {
|
|||||||
const [guide3Step, setGuide3Step] = useState(0);
|
const [guide3Step, setGuide3Step] = useState(0);
|
||||||
// 光标3的题目显示状态
|
// 光标3的题目显示状态
|
||||||
const [showQuiz3, setShowQuiz3] = useState(false);
|
const [showQuiz3, setShowQuiz3] = useState(false);
|
||||||
|
// 拖拽题显示状态
|
||||||
|
const [showDragQuiz, setShowDragQuiz] = useState(false);
|
||||||
|
// 拖拽题的值
|
||||||
|
const [dragQuizValue, setDragQuizValue] = useState<DragFillResult[]>([]);
|
||||||
|
// 拖拽题错误提示
|
||||||
|
const [dragQuizError, setDragQuizError] = useState<string>("");
|
||||||
// 成功提示状态
|
// 成功提示状态
|
||||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||||
|
// 成功提示内容
|
||||||
|
const [successAlertContent, setSuccessAlertContent] = useState<string>("");
|
||||||
|
|
||||||
// 光标1、2的引导对话状态(在两者都浏览后显示)
|
// 光标1、2的引导对话状态(在两者都浏览后显示)
|
||||||
const [guide12Visible, setGuide12Visible] = useState(false);
|
const [guide12Visible, setGuide12Visible] = useState(false);
|
||||||
@@ -91,6 +127,58 @@ const HuangyanpeiPlace3 = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理第一题成功提示继续
|
||||||
|
const handleFirstQuizSuccessContinue = () => {
|
||||||
|
setShowSuccessAlert(false);
|
||||||
|
// 显示拖拽题
|
||||||
|
setShowDragQuiz(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理拖拽题变化
|
||||||
|
const handleDragQuizChange = (value: DragFillResult[]) => {
|
||||||
|
setDragQuizValue(value);
|
||||||
|
setDragQuizError("");
|
||||||
|
|
||||||
|
// 检查是否有错误选项(学艺术)- 立即检查并显示错误
|
||||||
|
const hasInvalidOption = value.some((v) => v.optionValue === "学艺术");
|
||||||
|
if (hasInvalidOption) {
|
||||||
|
setDragQuizError(DRAG_QUIZ_ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否所有空白都已填充
|
||||||
|
if (value.length < 2) return;
|
||||||
|
|
||||||
|
// 验证答案:第一个空白应该是"读书",第二个空白应该是"做手工"
|
||||||
|
const blank1 = value.find((v) => v.blankId === "blank-1");
|
||||||
|
const blank2 = value.find((v) => v.blankId === "blank-2");
|
||||||
|
|
||||||
|
// 验证正确答案
|
||||||
|
const isCorrect =
|
||||||
|
blank1?.optionValue === CORRECT_ANSWERS.blank1 &&
|
||||||
|
blank2?.optionValue === CORRECT_ANSWERS.blank2;
|
||||||
|
|
||||||
|
if (isCorrect) {
|
||||||
|
// 答题成功
|
||||||
|
setShowDragQuiz(false);
|
||||||
|
setSuccessAlertContent(DRAG_QUIZ_SUCCESS_CONTENT);
|
||||||
|
setShowSuccessAlert(true);
|
||||||
|
} else {
|
||||||
|
// 答题失败
|
||||||
|
setDragQuizError("请再想想,确保选择正确的答案。");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理拖拽题成功提示继续
|
||||||
|
const handleDragQuizSuccessContinue = () => {
|
||||||
|
setShowSuccessAlert(false);
|
||||||
|
setSuccessAlertContent("");
|
||||||
|
setDragQuizValue([]);
|
||||||
|
setDragQuizError("");
|
||||||
|
// 标记展品已访问
|
||||||
|
markExhibitVisited(place, 3);
|
||||||
|
};
|
||||||
|
|
||||||
// 监听visited变化,当光标1、2都浏览后显示引导对话
|
// 监听visited变化,当光标1、2都浏览后显示引导对话
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visited.has(1) && visited.has(2) && !guide12Shown) {
|
if (visited.has(1) && visited.has(2) && !guide12Shown) {
|
||||||
@@ -171,14 +259,13 @@ const HuangyanpeiPlace3 = () => {
|
|||||||
visible={activeExhibitId !== null}
|
visible={activeExhibitId !== null}
|
||||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
if (activeExhibitId === 3) {
|
const animationMap: Record<1 | 2 | 3, number> = {
|
||||||
triggerPuzzleAnimation(3);
|
3: 3,
|
||||||
}
|
2: 4,
|
||||||
if (activeExhibitId === 2) {
|
1: 5,
|
||||||
triggerPuzzleAnimation(4);
|
};
|
||||||
}
|
if (activeExhibitId) {
|
||||||
if (activeExhibitId === 1) {
|
triggerPuzzleAnimation(animationMap[activeExhibitId]);
|
||||||
triggerPuzzleAnimation(5);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -239,20 +326,51 @@ const HuangyanpeiPlace3 = () => {
|
|||||||
{ value: "D", label: "D. 绘画、声乐、舞蹈、戏剧" },
|
{ value: "D", label: "D. 绘画、声乐、舞蹈、戏剧" },
|
||||||
]}
|
]}
|
||||||
correctAnswer="B"
|
correctAnswer="B"
|
||||||
errorMessage="1903年,黄炎培将“观澜书院”改制为“川沙小学堂”,自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行“半日授课、半日实习”的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。"
|
errorMessage='1903年,黄炎培将"观澜书院"改制为"川沙小学堂",自任校长,率先打破传统经学模式,在课程表中首次列入算术、格致(近代自然科学)、体操和乡土调查四门新学科,并实行"半日授课、半日实习"的教学方式,开创了近代中国小学融入科学、体育与实践教育的新风。'
|
||||||
|
onWrongSelect={(value) => {
|
||||||
|
switch (value) {
|
||||||
|
case "A":
|
||||||
|
return "经学、策论、八股文是传统书院的旧式课程,而川沙小学堂正是要打破这种传统模式。请你再想想吧!";
|
||||||
|
case "C":
|
||||||
|
return "外语、逻辑、演讲、社交这些科目虽然也很重要,但不是川沙小学堂首次引入的突破性学科。请你再想想吧!";
|
||||||
|
case "D":
|
||||||
|
return "绘画、声乐、舞蹈、戏剧属于艺术类课程,不是川沙小学堂在1903年首次开设的突破性学科。请你再想想吧!";
|
||||||
|
default:
|
||||||
|
return "这不是川沙小学堂开设的具有突破性的学科。请你再想想吧!";
|
||||||
|
}
|
||||||
|
}}
|
||||||
onSubmit={handleQuiz3Submit}
|
onSubmit={handleQuiz3Submit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 成功提示 */}
|
{/* 第一题成功提示 */}
|
||||||
<SuccessAlert
|
<SuccessAlert
|
||||||
visible={showSuccessAlert}
|
visible={showSuccessAlert && !showDragQuiz && !successAlertContent}
|
||||||
onContinue={() => {
|
content={FIRST_QUIZ_SUCCESS_CONTENT}
|
||||||
setShowSuccessAlert(false);
|
onContinue={handleFirstQuizSuccessContinue}
|
||||||
markExhibitVisited(place, 3);
|
onClose={handleFirstQuizSuccessContinue}
|
||||||
}}
|
/>
|
||||||
onClose={() => {
|
|
||||||
setShowSuccessAlert(false);
|
{/* 拖拽题 */}
|
||||||
}}
|
<DragQuizPanel
|
||||||
|
visible={showDragQuiz}
|
||||||
|
zIndex={10000}
|
||||||
|
title=""
|
||||||
|
questionPrefix='【拖拽】黄炎培在"川沙小学堂"实行"半天'
|
||||||
|
questionSuffix="的模式,特别注重实践。"
|
||||||
|
options={DRAG_QUIZ_OPTIONS}
|
||||||
|
blanks={DRAG_QUIZ_BLANKS}
|
||||||
|
blankSeparators={["、"]}
|
||||||
|
value={dragQuizValue}
|
||||||
|
error={dragQuizError}
|
||||||
|
onChange={handleDragQuizChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 拖拽题成功提示 */}
|
||||||
|
<SuccessAlert
|
||||||
|
visible={showSuccessAlert && !showDragQuiz && !!successAlertContent}
|
||||||
|
content={successAlertContent}
|
||||||
|
onContinue={handleDragQuizSuccessContinue}
|
||||||
|
onClose={handleDragQuizSuccessContinue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 光标1、2的引导对话 */}
|
{/* 光标1、2的引导对话 */}
|
||||||
|
|||||||
Reference in new issue
Block a user