Files
shanghai-residences/src/pages/huangyanpei/reflection/scene4.tsx
T
zhongyidian a365964f54 feat: 添加全局键盘快捷键和更新故居数据,提升用户体验
- 在 main.tsx 中添加全局键盘快捷键功能,允许用户通过 Ctrl+Shift+R 重置所有缓存。
- 更新多个故居的拼图数据,增加黄炎培和鲁迅的相关信息,丰富学习内容。
- 调整 ResidenceLayout 组件以支持新的故居,确保数据一致性。
- 清理不再使用的拼图布局配置文件,提升代码整洁性。
2026-01-28 15:08:28 +08:00

242 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useCallback, useRef } from "react";
import { Title, Image, Cursor, Video, StudyGuideModal } from "@/components";
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
import { SuccessAlert, ErrorAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom";
import TowelSalesDialog from "./scene4/TowelSalesDialog";
import type { VideoRef } from "@/components/video";
import { QuizDialog } from "./components";
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
const starName = "huangyanpei";
const HuangyanpeiReflectionScene4 = () => {
const navigate = useNavigate();
const { moduleName } = useParams<{
moduleName: string;
}>();
const { triggerPuzzleAnimation } = usePuzzleAnimation();
const [_activeExhibitId, _setActiveExhibitId] = useState<1 | 2 | null>(null);
const [showQuiz1, setShowQuiz1] = useState(false);
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const [showTowelDialog, setShowTowelDialog] = useState(false);
const [showVideo, setShowVideo] = useState(false);
const videoRef = useRef<VideoRef>(null);
const [showGuide1, setShowGuide1] = useState(true);
const [showErrorAlertForQuiz, setShowErrorAlertForQuiz] = useState(false);
// 使用持久化的reflection cursor状态
const scene = "scene4";
const { markReflectionCursorVisited, getVisitedReflectionCursors } =
useHuangyanpeiStore();
// 从 store 获取已完成的cursor,转换为 Set 用于兼容现有代码
const visitedCursors = getVisitedReflectionCursors(scene);
const visited = new Set(visitedCursors);
//喇叭考题弹窗
const [showQuizDialog, setShowQuizDialog] = useState(false);
// 处理成功提示的继续和关闭
const handleSuccessAlertContinue = useCallback(() => {
setShowSuccessAlert(false);
// 更新visited状态
markReflectionCursorVisited(scene, 1);
}, [markReflectionCursorVisited, scene]);
const handleSuccessAlertClose = useCallback(() => {
setShowSuccessAlert(false);
// 更新visited状态
markReflectionCursorVisited(scene, 1);
}, [markReflectionCursorVisited, scene]);
if (!moduleName) {
return null;
}
const progressCount = visited.size;
const total = 3;
const completed = progressCount >= total;
const subtitle = completed
? "探索中华职业学院·立业之本·商科(已完成)"
: `探索中华职业学院·立业之本·商科(${progressCount}/${total})`;
const subtitleClassName = completed
? "text-[#fff998] [text-shadow:_0_8px_8px_rgba(0,0,0,0.35),_0_0_16px_rgba(0,0,0,0.25)]"
: undefined;
return (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: getBackgroundImage(
`huangyanpei/reflection/scene4/12_001`
),
}}
>
<Title
title="跨越时空"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
/>
<Image
className="absolute left-5 top-5"
src={getStaticImage("huangyanpei/reflection/components/喇叭2")}
button
onClick={() => {
// 检查是否完成前两个任务(visited.has(1) && visited.has(2))
if (visited.has(1) && visited.has(2)) {
setShowQuizDialog(true);
} else {
setShowErrorAlertForQuiz(true);
}
}}
/>
<Image
className="absolute right-5 top-5"
src={getStaticImage("components/离开")}
button
onClick={() => {
navigate(`/${moduleName}/${starName}/reflection2`);
}}
/>
{/* 互动展品光标1 */}
<Cursor
className="absolute left-100 bottom-88"
onClick={() => {
setShowQuiz1(true);
}}
read={visited.has(1)}
/>
{/* 互动展品光标2 */}
<Cursor
className="absolute right-82 bottom-88"
onClick={() => {
setShowTowelDialog(true);
}}
read={visited.has(2)}
/>
{/* 选择题1:中华职校商科的核心教学目标 */}
<MultipleChoiceQuizPanel
visible={showQuiz1}
title={`【选题】中华职校商科的核心教学目标是什么? `}
options={[
{
value: "A",
label: "A. 教会学生投机倒把",
},
{
value: "B",
label: "B. 培养诚信经营人才",
},
{
value: "C",
label: "C. 推广国货支援实业",
},
{
value: "D",
label: "D. 只为个人发财致富",
},
]}
correctAnswer={["B", "C"]}
errorMessage="不对哦!黄先生倡导“商科要为民族工商业服务”,商科是要培养诚信经营、助力国货发展的人才,可不能想着投机发财呀,再选一次吧!"
minSelections={1}
onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) {
// 答题成功:关闭选择题,显示成功提示
setShowQuiz1(false);
setShowSuccessAlert(true);
}
}}
/>
{/* 答题成功提示 */}
<SuccessAlert
visible={showSuccessAlert}
title="满分!"
content="诚信是经商之本, 推广国货是商科学生的责任, 这正是黄先生的办学初衷。"
onContinue={handleSuccessAlertContinue}
onClose={handleSuccessAlertClose}
/>
{/* 毛巾推销语拖拽对话框 */}
<TowelSalesDialog
visible={showTowelDialog}
onClose={() => setShowTowelDialog(false)}
onComplete={() => {
// 完成拖拽后更新visited状态
markReflectionCursorVisited(scene, 2);
triggerPuzzleAnimation(15);
}}
/>
{completed && (
<Image
className="absolute bottom-0 right-4"
src={getStaticImage("components/place/继续前进")}
button
onClick={() => {
setShowVideo(true);
}}
/>
)}
{/* 第一段引导 */}
<StudyGuideModal
visible={showGuide1}
onContinue={() => {
setShowGuide1(false);
}}
audioSrc={getPublicPath("audio/huangyanpei/mp3_huangyanpei_26.mp3")}
>
终于到中华职业学校啦!这里是中国近代职教的摇篮,“免学费、包分配”,还能学真手艺,快来练就一技之长。
{"\n"}
现在要完成你的第一个任务:推销中华职校生产的“国货精品”——手工纽扣和纯棉毛巾!当年学生们就是带着这些国货,走街串巷做推销呢!
</StudyGuideModal>
{showVideo && (
<Video
ref={videoRef}
src={getPublicPath("video/huangyanpei/huangyanpei_09.mp4")}
className="h-full w-full"
autoPlay
showSkipButton
playsInline
onEnded={() => {
setShowVideo(false);
navigate(`/${moduleName}/${starName}/home`);
}}
/>
)}
<QuizDialog
visible={showQuizDialog}
onClose={() => setShowQuizDialog(false)}
onComplete={() => {
setShowQuizDialog(false);
markReflectionCursorVisited(scene, 3);
}}
/>
{/* 错误提示:请先完成前两个任务 */}
<ErrorAlert
visible={showErrorAlertForQuiz}
text="请先完成前两个任务吧!"
onClose={() => setShowErrorAlertForQuiz(false)}
/>
</div>
);
};
export default HuangyanpeiReflectionScene4;