feat: 优化敏感词处理和更新拖拽题功能,提升用户体验

- 清理敏感词处理函数中的多余空行,提升代码整洁性。
- 在 HuangyanpeiPlace3 组件中引入拖拽题功能,增强互动性。
- 添加拖拽题的成功和错误提示逻辑,优化用户填写体验。
- 使用 useMemo 优化已访问展品的状态管理,提升性能。
This commit is contained in:
zhongyidian committed 2026-02-02 00:03:09 +08:00
1 parent 45dd648d21
commit 1dd8afcce7
2 files changed
+142 -25

No files matched your search

+1 -2
View File
@@ -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;
} }
+141 -23
View File
@@ -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的引导对话 */}