- 删除多个无用的 MacOS 生成文件,减少项目冗余。 - 在 layout 组件中添加 ScaledPortal 和相关类型,增强组件功能。 - 更新 puzzleData 中的学校名称,确保信息准确性。 - 调整 internalization 组件中的提示文本,提升用户体验。 - 在多个场景中引入新的选择题和成功提示,增强学习互动性。
415 lines
14 KiB
TypeScript
415 lines
14 KiB
TypeScript
import { useState, useRef } from "react";
|
|
import {
|
|
ExhibitOverlay,
|
|
Title,
|
|
Image,
|
|
Cursor,
|
|
Video,
|
|
GuideSteps,
|
|
GuideStepsItem,
|
|
type SingleChoiceOption,
|
|
type DragFillResult,
|
|
SingleChoiceQuizPanel,
|
|
} from "@/components";
|
|
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
|
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import type { VideoRef } from "@/components/video";
|
|
import { useChenyunStore } from "@/store/chenyunStore";
|
|
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
|
|
|
// starName 固定为 chenyun
|
|
const starName = "chenyun";
|
|
|
|
// 展品 ID 与资源路径映射
|
|
const exhibitSrcMap: Record<1, string> = {
|
|
1: "chenyun/place5/场景5展品1",
|
|
};
|
|
|
|
// 题目1选项
|
|
const QUIZ1_OPTIONS: SingleChoiceOption[] = [
|
|
{
|
|
value: "A",
|
|
label: "A.推动了淮海战役的胜利",
|
|
},
|
|
{
|
|
value: "B",
|
|
label: "B.有利于稳定人民币的发行和地位",
|
|
},
|
|
{
|
|
value: "C",
|
|
label: "C.巩固了新中国人民政权",
|
|
},
|
|
];
|
|
|
|
// 题目1各选项对应的错误提示(原来写在选项括号中的内容)
|
|
const QUIZ1_ERROR_HINTS: Record<string, string> = {
|
|
A: "淮海战役于1949年1月胜利结束,发生在这次“银元之战”前,不存在因果关系。请你再想想吧!",
|
|
C: "1949年6月新中国政权尚未建立。请你再想想吧",
|
|
};
|
|
|
|
const ChenyunPlace7 = () => {
|
|
const navigate = useNavigate();
|
|
const { moduleName } = useParams<{
|
|
moduleName: string;
|
|
}>();
|
|
|
|
// 获取碎片动画触发方法
|
|
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
|
|
|
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
|
|
|
// 使用持久化的访问状态
|
|
const place = "place7";
|
|
const { isExhibitVisited, markExhibitVisited, getVisitedExhibits } =
|
|
useChenyunStore();
|
|
|
|
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
|
const visitedExhibits = getVisitedExhibits(place);
|
|
const visited = new Set(visitedExhibits);
|
|
const [videoVisible1, setVideoVisible1] = useState(false);
|
|
const [videoPlaying, setVideoPlaying] = useState(false);
|
|
const videoRef = useRef<VideoRef>(null);
|
|
// 单选题
|
|
const [quizVisible, setQuizVisible] = useState(false);
|
|
const [quizAnswer, setQuizAnswer] = useState<string>("");
|
|
const [quizError, setQuizError] = useState<string>("");
|
|
// 展品1引导对话
|
|
const [guideVisible, setGuideVisible] = useState(false);
|
|
const [guideStep, setGuideStep] = useState(0);
|
|
// 第二阶段:对话2 + 视频2 + 题目2
|
|
const [videoVisible2, setVideoVisible2] = useState(false);
|
|
const [videoPlaying2, setVideoPlaying2] = useState(false);
|
|
const videoRef2 = useRef<VideoRef>(null);
|
|
const [guideVisible2, setGuideVisible2] = useState(false);
|
|
const [guideStep2, setGuideStep2] = useState(0);
|
|
const [quiz2Visible, setQuiz2Visible] = useState(false);
|
|
const [quiz2Error, setQuiz2Error] = useState<string>("");
|
|
const [quiz2Value, setQuiz2Value] = useState<DragFillResult[]>([]);
|
|
// 成功提示
|
|
const [successAlertVisible, setSuccessAlertVisible] = useState(false);
|
|
const [successAlertContent, setSuccessAlertContent] = useState<string>("");
|
|
// 标记拖拽题是否已完成
|
|
const [quiz2Completed, setQuiz2Completed] = useState(false);
|
|
|
|
const handleOpenExhibit = (_index: 1) => {
|
|
// 如果本场景学习已完成(拖拽题已答对),不再响应点击,避免重复播放
|
|
if (quiz2Completed) return;
|
|
|
|
// 展品1:点击时显示视频1场景和引导对话1
|
|
setVideoVisible1(true);
|
|
setVideoPlaying(false); // 视频不自动播放
|
|
setGuideVisible(true);
|
|
setGuideStep(0);
|
|
};
|
|
|
|
// 监听GuideSteps步骤变化
|
|
const handleGuideStepChange = (step: number) => {
|
|
setGuideStep(step);
|
|
};
|
|
|
|
// 监听对话引导完成
|
|
const handleGuideVisibleChange = (visible: boolean) => {
|
|
setGuideVisible(visible);
|
|
// 对话引导完成后,开始播放视频
|
|
if (!visible && videoVisible1) {
|
|
setVideoPlaying(true);
|
|
videoRef.current?.play();
|
|
}
|
|
};
|
|
|
|
// 对话2步骤变化
|
|
const handleGuideStepChange2 = (step: number) => {
|
|
setGuideStep2(step);
|
|
};
|
|
|
|
// 对话2完成 → 播放视频2
|
|
const handleGuideVisibleChange2 = (visible: boolean) => {
|
|
setGuideVisible2(visible);
|
|
if (!visible && videoVisible2) {
|
|
setVideoPlaying2(true);
|
|
videoRef2.current?.play();
|
|
}
|
|
};
|
|
|
|
// 拖拽题配置
|
|
const QUIZ2_BLANKS = [
|
|
{ id: "blank-1", label: "____" },
|
|
{ id: "blank-2", label: "____" },
|
|
{ id: "blank-3", label: "____" },
|
|
];
|
|
const QUIZ2_OPTIONS = [
|
|
{ value: "棉纱", label: "棉纱" },
|
|
{ value: "大米", label: "大米" },
|
|
{ value: "煤炭", label: "煤炭" },
|
|
{ value: "石油", label: "石油" },
|
|
];
|
|
const QUIZ2_ANSWER = ["棉纱", "大米", "煤炭"];
|
|
|
|
const handleQuiz2Change = (val: DragFillResult[]) => {
|
|
setQuiz2Value(val);
|
|
setQuiz2Error("");
|
|
if (val.length < QUIZ2_ANSWER.length) return;
|
|
// 获取所有已填入的答案(不要求顺序)
|
|
const filledAnswers = val.map((v) => v.optionValue);
|
|
// 检查是否包含了所有正确答案(使用 Set 比较,不要求顺序)
|
|
const answerSet = new Set(QUIZ2_ANSWER);
|
|
const filledSet = new Set(filledAnswers);
|
|
const isCorrect =
|
|
answerSet.size === filledSet.size &&
|
|
[...answerSet].every((answer) => filledSet.has(answer));
|
|
if (isCorrect) {
|
|
setQuiz2Visible(false);
|
|
setVideoVisible1(false);
|
|
setVideoPlaying(false);
|
|
setVideoVisible2(false);
|
|
setVideoPlaying2(false);
|
|
// 标记拖拽题已完成
|
|
setQuiz2Completed(true);
|
|
// 显示成功提示
|
|
setSuccessAlertVisible(true);
|
|
} else {
|
|
setQuiz2Error("再想一想,石油当时不是主要调控对象。请你再想想吧!");
|
|
}
|
|
};
|
|
|
|
const progressCount = visited.size;
|
|
const total = 1;
|
|
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(`chenyun/place7/场景7`),
|
|
}}
|
|
>
|
|
{/* 标题 */}
|
|
<Title
|
|
title="恢复时期·经济奠基"
|
|
subtitle={subtitle}
|
|
subtitleClassName={subtitleClassName}
|
|
className="absolute top-10 left-1/2 -translate-x-1/2"
|
|
/>
|
|
|
|
{/* 返回上一章 */}
|
|
<Image
|
|
className="absolute right-5 top-5"
|
|
src={getStaticImage("components/离开")}
|
|
button
|
|
onClick={() => {
|
|
if (moduleName && starName) {
|
|
navigate(`/${moduleName}/${starName}/place6`);
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* 展品1 */}
|
|
<Cursor
|
|
className="absolute left-230 top-88"
|
|
onClick={() => handleOpenExhibit(1)}
|
|
read={visited.has(1)}
|
|
/>
|
|
|
|
{/* 继续前进 - 仅在完成所有展品后显示 */}
|
|
{completed && (
|
|
<Image
|
|
className="absolute bottom-0 right-0"
|
|
src={getStaticImage("chenyun/place7/继续前进")}
|
|
button
|
|
onClick={() => {
|
|
navigate(`/${moduleName}/${starName}/place8`);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* 展品1视频播放(全屏) */}
|
|
{videoVisible1 && (
|
|
<div className="fixed inset-0 z-[9999] bg-black">
|
|
<Video
|
|
ref={videoRef}
|
|
src={getPublicPath("video/chenyun/chenyun_10.mp4")}
|
|
className="w-full h-full"
|
|
autoPlay={videoPlaying}
|
|
playsInline
|
|
preload="auto"
|
|
muted={!videoPlaying}
|
|
showSkipButton
|
|
onEnded={() => {
|
|
setVideoPlaying(false);
|
|
markExhibitVisited(place, 1);
|
|
triggerPuzzleAnimation(10);
|
|
// 视频播放完成后,出现单选题
|
|
setQuizVisible(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 展品1的引导对话 - 在视频上方显示 */}
|
|
<GuideSteps
|
|
visible={guideVisible}
|
|
onVisibleChange={handleGuideVisibleChange}
|
|
currentStep={guideStep}
|
|
onStepChange={handleGuideStepChange}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10001}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/chenyun/mp3_chenyun_19.mp3")}
|
|
>
|
|
那"银元之战"是如何取得胜利的呢?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/chenyun/mp3_chenyun_20.mp3")}
|
|
>
|
|
由于通货膨胀,国民党发行的法币、金圆券变成了一堆废纸。当时坊间流传"大街过三道,物价跳三跳"。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
有人计算,一百元法币,在一九三七年可买两头牛,一九四五年只能买两个鸡蛋,一九四七年只能买一个煤球,到一九四九年五月连一粒米也买不到。可见,当时通货膨胀多么严重。
|
|
</GuideStepsItem>
|
|
<GuideStepsItem speaker="guide">
|
|
打击投机资本,稳定金融物价,陈云代中央起草《中共中央关于打击银元使人民币占领阵地的指示》,提出以强硬手段打击投机势力。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 单选题1:在视频1结束后出现(点击即判定) */}
|
|
<SingleChoiceQuizPanel
|
|
visible={quizVisible}
|
|
zIndex={10002}
|
|
title={`【单选】1949年6月,中共中央指示打击银元投机活动,金条、银元一律由人民银行挂牌收兑,铁路交通事业,税收一律收人民币。同时,上海军管会查封了证券大楼。这次“银元之战”:`}
|
|
options={QUIZ1_OPTIONS}
|
|
correctAnswer={"B"}
|
|
errorMessage={quizError}
|
|
onSubmit={(val) => {
|
|
setQuizAnswer(val);
|
|
setQuizVisible(false);
|
|
setQuizError("");
|
|
// 显示成功提示
|
|
setSuccessAlertVisible(true);
|
|
setSuccessAlertContent(
|
|
"“银元之战”属于当时人民政府整顿金融的重要举措,黄金、银元投机不利于人民币币值稳定和流通,因此“银元之战”打击贵金属投机活动,有利于稳定人民币的发行和地位。"
|
|
);
|
|
}}
|
|
onWrongSelect={(val) => {
|
|
return (
|
|
QUIZ1_ERROR_HINTS[val] || "再想一想,注意题干中的时间和提示信息哦~"
|
|
);
|
|
}}
|
|
/>
|
|
|
|
{/* 视频2播放(全屏) */}
|
|
{videoVisible2 && (
|
|
<div className="fixed inset-0 z-[10010] bg-black">
|
|
<Video
|
|
ref={videoRef2}
|
|
src={getPublicPath("video/chenyun/chenyun_11.mp4")}
|
|
className="w-full h-full"
|
|
autoPlay={videoPlaying2}
|
|
playsInline
|
|
preload="auto"
|
|
muted={!videoPlaying2}
|
|
showSkipButton
|
|
skipText="跳过"
|
|
onEnded={() => {
|
|
setVideoPlaying2(false);
|
|
// 视频2结束后,停在最后一帧并出现题目2
|
|
setQuiz2Visible(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 对话2:在视频2背景上显示 */}
|
|
<GuideSteps
|
|
visible={guideVisible2}
|
|
onVisibleChange={handleGuideVisibleChange2}
|
|
currentStep={guideStep2}
|
|
onStepChange={handleGuideStepChange2}
|
|
showMask={false}
|
|
highlightSelectors={[]}
|
|
zIndex={10011}
|
|
>
|
|
<GuideStepsItem
|
|
speaker="student"
|
|
audioSrc={getPublicPath("audio/chenyun/mp3_chenyun_21.mp3")}
|
|
>
|
|
那“两白一黑”战役是如何打赢的呢?
|
|
</GuideStepsItem>
|
|
<GuideStepsItem
|
|
speaker="guide"
|
|
audioSrc={getPublicPath("audio/chenyun/mp3_chenyun_22.mp3")}
|
|
>
|
|
“银元之战”的胜利并没有遏制住投机者的嚣张气焰,物价稳定仅十几天后,投机者就由银元投机转向“两白一黑”的投机,即:棉纱、大米和煤炭,引发了又一次全国性涨价狂潮。
|
|
</GuideStepsItem>
|
|
</GuideSteps>
|
|
|
|
{/* 拖拽题2:在视频2结束后出现 */}
|
|
<DragQuizPanel
|
|
visible={quiz2Visible}
|
|
zIndex={10012}
|
|
questionPrefix='【拖拽】新中国成立后,为了稳定物价,中央政府组织了"两白一黑"的集中调运,"两白一黑"指的是'
|
|
questionSuffix="。"
|
|
options={QUIZ2_OPTIONS}
|
|
blanks={QUIZ2_BLANKS}
|
|
value={quiz2Value}
|
|
error={quiz2Error}
|
|
onChange={handleQuiz2Change}
|
|
/>
|
|
|
|
{/* 遮罩层 */}
|
|
<ExhibitOverlay
|
|
visible={activeExhibitId !== null}
|
|
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
|
onClose={() => {
|
|
if (activeExhibitId !== null) {
|
|
markExhibitVisited(place, activeExhibitId);
|
|
}
|
|
setActiveExhibitId(null);
|
|
}}
|
|
/>
|
|
|
|
{/* 成功提示 */}
|
|
{successAlertVisible && (
|
|
<div className="fixed inset-0" style={{ zIndex: 20000 }}>
|
|
<SuccessAlert
|
|
visible={successAlertVisible}
|
|
title="恭喜你, 选择正确!"
|
|
content={successAlertContent}
|
|
onClose={() => {
|
|
setSuccessAlertVisible(false);
|
|
// 如果是单选题答对且拖拽题还没开始,继续进入第二阶段
|
|
if (quizAnswer === "B" && !quiz2Visible && !quiz2Completed) {
|
|
setVideoVisible2(true);
|
|
setVideoPlaying2(false);
|
|
setGuideVisible2(true);
|
|
setGuideStep2(0);
|
|
}
|
|
}}
|
|
onContinue={() => {
|
|
setSuccessAlertVisible(false);
|
|
// 如果是单选题答对且拖拽题还没开始,继续进入第二阶段
|
|
if (quizAnswer === "B" && !quiz2Visible && !quiz2Completed) {
|
|
setVideoVisible2(true);
|
|
setVideoPlaying2(false);
|
|
setGuideVisible2(true);
|
|
setGuideStep2(0);
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ChenyunPlace7;
|