feat:孙中山对话前3个场景

This commit is contained in:
zhongyidian committed 2026-01-09 01:20:13 +08:00
1 parent 3129e01257
commit 786f29a2f7
5 files changed
+830 -57

No files matched your search

+1 -1
View File
@@ -146,7 +146,7 @@ const LuxunPlace1 = () => {
}} }}
> >
{`同学你好,欢迎来到走进故居-对话学习模块! {`同学你好,欢迎来到走进故居-对话学习模块!
进入鲁迅故居后,我们一起来了解鲁迅先生经历的5个阶段:"风雨前夕的思想淬炼"->"杂文集里的匕首与投枪"->"历史新编中的现实呐喊"->"为亡友编纂的精神丰碑"->"6平方米的红色庇护所";`} 进入鲁迅故居后,我们一起来了解鲁迅先生经历的4个阶段:"风雨前夕的思想淬炼"->"杂文集里的匕首与投枪"->"历史新编中的现实呐喊&为亡友编纂的精神丰碑"->"6平方米的红色庇护所";`}
</StudyGuideModal> </StudyGuideModal>
{/* 引导 */} {/* 引导 */}
+393 -25
View File
@@ -4,12 +4,17 @@ import {
GuideStepsItem, GuideStepsItem,
Image, Image,
Title, Title,
Overlay, StudyGuideModal,
Video,
type VideoRef,
type DragFillResult,
} from "@/components"; } from "@/components";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useState } from "react"; import { useState, useRef } from "react";
import Cursor from "@/components/cursor"; import Cursor from "@/components/cursor";
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
// starName 固定为 sunzhongshan // starName 固定为 sunzhongshan
const starName = "sunzhongshan"; const starName = "sunzhongshan";
@@ -29,10 +34,13 @@ const SunzhongshanPlace1 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习说明对话框显示状态 // 学习引导弹窗
const [showIntroDialog, setShowIntroDialog] = useState(false); const [showGuideModal, setShowGuideModal] = useState(true);
// 对话结束后的学习引导弹窗
const [showAfterDialogGuide, setShowAfterDialogGuide] = useState(false);
const [afterDialogGuideShown, setAfterDialogGuideShown] = useState(false);
// 引导对话显示状态 // 引导对话显示状态
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(false);
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识) // 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
const [activeExhibitId, setActiveExhibitId] = useState< const [activeExhibitId, setActiveExhibitId] = useState<
1 | 2 | 3 | 4 | 5 | null 1 | 2 | 3 | 4 | 5 | null
@@ -40,13 +48,55 @@ const SunzhongshanPlace1 = () => {
// 已浏览展品的 ID 集合 // 已浏览展品的 ID 集合
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
// 光标2的对话状态
const [guide2Visible, setGuide2Visible] = useState(false);
const [guide2Step, setGuide2Step] = useState(0);
// 光标5的对话状态
const [guide5Visible, setGuide5Visible] = useState(false);
const [guide5Step, setGuide5Step] = useState(0);
// 光标4的对话状态
const [guide4Visible, setGuide4Visible] = useState(false);
const [guide4Step, setGuide4Step] = useState(0);
// 光标4的视频状态
const [showVideo4, setShowVideo4] = useState(false);
const video4Ref = useRef<VideoRef>(null);
// 光标4的拖拽题状态
const [dragQuiz4Visible, setDragQuiz4Visible] = useState(false);
const [dragQuiz4Value, setDragQuiz4Value] = useState<DragFillResult[]>([]);
const [dragQuiz4Error, setDragQuiz4Error] = useState("");
const [successAlert4Visible, setSuccessAlert4Visible] = useState(false);
// 光标4的第二个视频状态
const [showVideo4Second, setShowVideo4Second] = useState(false);
const video4SecondRef = useRef<VideoRef>(null);
// 光标3的对话状态
const [guide3Visible, setGuide3Visible] = useState(false);
const [guide3Step, setGuide3Step] = useState(0);
// 光标1的对话状态
const [guide1Visible, setGuide1Visible] = useState(false);
const [guide1Step, setGuide1Step] = useState(0);
// 光标1的多选题状态
const [quiz1Visible, setQuiz1Visible] = useState(false);
const [successAlert1Visible, setSuccessAlert1Visible] = useState(false);
const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => { const handleOpenExhibit = (index: 1 | 2 | 3 | 4 | 5) => {
setActiveExhibitId(index); setActiveExhibitId(index);
}; // 根据不同的展品显示对应的对话,并重置进度
if (index === 2) {
const handleStartLearning = () => { setGuide2Step(0);
setShowIntroDialog(false); setGuide2Visible(true);
setVisible(true); } else if (index === 5) {
setGuide5Step(0);
setGuide5Visible(true);
} else if (index === 4) {
setGuide4Step(0);
setGuide4Visible(true);
} else if (index === 3) {
setGuide3Step(0);
setGuide3Visible(true);
} else if (index === 1) {
setGuide1Step(0);
setGuide1Visible(true);
}
}; };
const progressCount = visited.size; const progressCount = visited.size;
@@ -85,31 +135,31 @@ const SunzhongshanPlace1 = () => {
}} }}
/> />
{/* 展品1 */} {/* 光标1 */}
<Cursor <Cursor
className="absolute left-75 bottom-80" className="absolute left-75 bottom-80"
onClick={() => handleOpenExhibit(1)} onClick={() => handleOpenExhibit(1)}
read={visited.has(1)} read={visited.has(1)}
/> />
{/* 展品2 */} {/* 光标2 */}
<Cursor <Cursor
className="absolute left-108 bottom-118" className="absolute left-108 bottom-118"
onClick={() => handleOpenExhibit(2)} onClick={() => handleOpenExhibit(2)}
read={visited.has(2)} read={visited.has(2)}
/> />
{/* 展品3 */} {/* 光标3 */}
<Cursor <Cursor
className="absolute left-165 bottom-90" className="absolute left-165 bottom-90"
onClick={() => handleOpenExhibit(3)} onClick={() => handleOpenExhibit(3)}
read={visited.has(3)} read={visited.has(3)}
/> />
{/* 展品4 */} {/* 光标4 */}
<Cursor <Cursor
className="absolute right-192 bottom-118" className="absolute right-192 bottom-118"
onClick={() => handleOpenExhibit(4)} onClick={() => handleOpenExhibit(4)}
read={visited.has(4)} read={visited.has(4)}
/> />
{/* 展品5 */} {/* 光标5 */}
<Cursor <Cursor
className="absolute right-188 bottom-88" className="absolute right-188 bottom-88"
onClick={() => handleOpenExhibit(5)} onClick={() => handleOpenExhibit(5)}
@@ -133,10 +183,36 @@ const SunzhongshanPlace1 = () => {
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { const currentId = activeExhibitId;
// 关闭展品框时,重置对应对话的进度并隐藏对话
if (currentId === 2) {
setGuide2Step(0);
setGuide2Visible(false);
} else if (currentId === 5) {
setGuide5Step(0);
setGuide5Visible(false);
} else if (currentId === 4) {
setGuide4Step(0);
setGuide4Visible(false);
// 光标4:关闭窗口后播放视频
setActiveExhibitId(null);
setShowVideo4(true);
return;
} else if (currentId === 3) {
setGuide3Step(0);
setGuide3Visible(false);
} else if (currentId === 1) {
setGuide1Step(0);
setGuide1Visible(false);
// 光标1:关闭窗口后显示题目
setActiveExhibitId(null);
setQuiz1Visible(true);
return;
}
if (currentId !== null) {
setVisited((prev) => { setVisited((prev) => {
const next = new Set(prev); const next = new Set(prev);
next.add(activeExhibitId); next.add(currentId);
return next; return next;
}); });
} }
@@ -144,23 +220,315 @@ const SunzhongshanPlace1 = () => {
}} }}
/> />
{/* 学习引导词 */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => {
setShowGuideModal(false);
setVisible(true);
}}
>
{`同学你好,欢迎来到走进故居-对话学习模块!
进入孙中山故居纪念馆后,我们一起来了解孙中山同志经历的7个阶段:"立志救国 推翻帝制"、"讨袁护法 捍卫共和"、"与时俱进探索新路"、"抱病北上鞠躬尽瘁"、"革命先驱 永垂不朽"、"思想传承 影响深远"、"精神财富 激励后人";`}
</StudyGuideModal>
{/* 引导 */} {/* 引导 */}
<GuideSteps <GuideSteps
visible={visible} visible={visible}
onVisibleChange={setVisible} onVisibleChange={(nextVisible) => {
setVisible(nextVisible);
if (!nextVisible && !afterDialogGuideShown) {
setAfterDialogGuideShown(true);
setShowAfterDialogGuide(true);
}
}}
showMask={true} showMask={true}
highlightSelectors={[]} highlightSelectors={[]}
> >
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="student">
同学你好,欢迎来到走进故居-对话学习模块! 小沪,老师说过,孙中山先生早年学习的是医学。从治病救人到革命救国,他为什么会做出这种转变呢?
进入孙中山故居纪念馆后,我们一起来了解孙中山同志经历的7个阶段:“水乡少年
艰辛成长”、“商务启航 投身革命” ...
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
“苏区淬炼 崭露头角”、“延安担当 经济破局”、“恢复时期 经济奠基” 来看看这张孙中山先生青年时期的相片吧。
、“建设时期 务实兴邦”、“改革浪潮 擘画实业”
</GuideStepsItem> </GuideStepsItem>
</GuideSteps> </GuideSteps>
{/* 对话结束后的学习引导 */}
<StudyGuideModal
visible={showAfterDialogGuide}
onContinue={() => setShowAfterDialogGuide(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点开查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
{/* 光标2的对话 */}
<GuideSteps
visible={guide2Visible}
currentStep={guide2Step}
onStepChange={setGuide2Step}
onVisibleChange={(nextVisible) => {
setGuide2Visible(nextVisible);
if (!nextVisible) {
setVisited((prev) => {
const next = new Set(prev);
next.add(2);
return next;
});
}
}}
showMask={true}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">"革命四杰"?</GuideStepsItem>
<GuideStepsItem speaker="guide">
是呀,这是孙中山先生在香港西医书院就读之时与同学的留影。孙先生1887年入学,其间广泛阅读西方自然科学和社会政治学说书籍。常与同学陈少白、尤列、杨鹤龄一起畅谈革命,抨击时政,被戏称为"四大寇"。
</GuideStepsItem>
</GuideSteps>
{/* 光标5的对话 */}
<GuideSteps
visible={guide5Visible}
currentStep={guide5Step}
onStepChange={setGuide5Step}
onVisibleChange={(nextVisible) => {
setGuide5Visible(nextVisible);
if (!nextVisible) {
setVisited((prev) => {
const next = new Set(prev);
next.add(5);
return next;
});
}
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
这是郑观应写给盛宣怀的信。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
是的。1894年,孙中山先生回乡起草了"上李鸿章书",希望劝谏清政府改良救国。他多方寻找上书门径,这就是作为同乡和前辈的郑观应帮忙写给盛宣怀,希望他向李鸿章推荐孙先生的书信。
</GuideStepsItem>
</GuideSteps>
{/* 光标4的对话 */}
<GuideSteps
visible={guide4Visible}
currentStep={guide4Step}
onStepChange={setGuide4Step}
onVisibleChange={(nextVisible) => {
setGuide4Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
哇!这张《申报》好震撼,"中华民国万岁""孙大总统万岁",就连发行日期写的都是"中华民国元年元月二日"!
</GuideStepsItem>
<GuideStepsItem speaker="guide">
没错!资产阶级民主革命的尝试走过了风雨兼程的十余年,这一过程中孙中山先生一直在探寻救国道路,1905年孙先生在亲自撰写的《民报》发刊词中明确提出了三民主义,即民族、民权、民生这一资产阶级民主革命的政纲。
</GuideStepsItem>
</GuideSteps>
{/* 光标4的第一个视频 */}
{showVideo4 && (
<Video
ref={video4Ref}
src={getPublicPath("video/sunzhongshan/sunzhongshan_02.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setShowVideo4(false);
setDragQuiz4Visible(true);
}}
/>
)}
{/* 光标4的拖拽题 */}
<DragQuizPanel
visible={dragQuiz4Visible}
zIndex={10000}
title="题目 1:"
questionPrefix="三民主义是孙中山所倡导的民主革命纲领,是其民主思想的精髓和高度概括,具体包括("
questionSuffix=")。(拖拽题)"
options={[
{ value: "民族", label: "民族" },
{ value: "民权", label: "民权" },
{ value: "民生", label: "民生" },
{ value: "民主", label: "民主" },
]}
blanks={[
{ id: "blank1", label: "____" },
{ id: "blank2", label: "____" },
{ id: "blank3", label: "____" },
]}
value={dragQuiz4Value}
error={dragQuiz4Error}
onChange={(value) => {
setDragQuiz4Value(value);
setDragQuiz4Error("");
if (value.length < 3) return;
// 检查答案:需要三个空白都填满,且答案正确
const correctAnswers = ["民族", "民权", "民生"];
const filledAnswers = value.map((v) => v.optionValue);
const answerSet = new Set(correctAnswers);
const filledSet = new Set(filledAnswers);
const isCorrect =
answerSet.size === filledSet.size &&
[...answerSet].every((answer) => filledSet.has(answer));
if (isCorrect) {
setDragQuiz4Visible(false);
setSuccessAlert4Visible(true);
} else {
setDragQuiz4Error(
'三民主义是"民族主义""民权主义""民生主义"的合称,是孙中山在进行了一系列挽救民族危亡、探索国家近代化建设的尝试后,从经验中概括提炼而成的理论成果。三民主义形成后,又指导了进一步的革命实践并得到了新的发展。它是中国革命的宝贵精神遗产。'
);
}
}}
/>
{/* 光标4的成功提示 */}
<SuccessAlert
visible={successAlert4Visible}
onContinue={() => {
setSuccessAlert4Visible(false);
setShowVideo4Second(true);
}}
onClose={() => {
setSuccessAlert4Visible(false);
setShowVideo4Second(true);
}}
/>
{/* 光标4的第二个视频 */}
{showVideo4Second && (
<Video
ref={video4SecondRef}
src={getPublicPath("video/sunzhongshan/sunzhongshan_03.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setShowVideo4Second(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(4);
return next;
});
}}
/>
)}
{/* 光标3的对话 */}
<GuideSteps
visible={guide3Visible}
currentStep={guide3Step}
onStepChange={setGuide3Step}
onVisibleChange={(nextVisible) => {
setGuide3Visible(nextVisible);
if (!nextVisible) {
setVisited((prev) => {
const next = new Set(prev);
next.add(3);
return next;
});
}
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="guide">
这些尝试终于在武昌起义过后迎来了回响。1911年年末,湖南、广东等14个省和上海纷纷宣布脱离清政府独立。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
1912年1月1日上午,上海有一万多人聚集在沪宁车站,欢送孙先生前去南京就任临时大总统。当天夜里11点,孙先生在原清朝两江总督府宣誓就职,新的共和政体就此诞生。彼时仍属农历辛亥年,因此历史上将这一系列事件称为"辛亥革命"。
</GuideStepsItem>
<GuideStepsItem speaker="student">
孙中山先生领导的辛亥革命推动了中国社会的变革,可是这张《民立报》让我有点不明白,孙先生刚当上临时大总统,为什么又很快宣布"解职"了呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
中华民国的建立有个重要背景——"南北议和"。辛亥革命中宣告独立的省份大多集中在中国南方,负隅顽抗的清政府将最后希望寄托在主导北洋军阀势力的袁世凯身上。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
在列强支持下,袁世凯率军南下,一边以武力威胁革命势力,一边又迎合革命党人急于完成统一的愿望,诱使其开展和议。当时,革命党人力量还不够强。为避免国家陷入前途未卜的内战,孙中山先生发表声明:只要清帝退位,袁世凯赞成共和,就推举他做大总统。
</GuideStepsItem>
</GuideSteps>
{/* 光标1的对话 */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={(nextVisible) => {
setGuide1Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">这算不算一种"妥协"呀?</GuideStepsItem>
<GuideStepsItem speaker="guide">
可以说,这是当时客观条件限制下的一种无奈妥协。因为革命党人的首要目标是推翻帝制,在武装力量不足以对抗北洋军阀的情况下,只能暂时信任袁世凯,希望他能维护共和。你看旁边的"南北议和"纪念章和纪念墨盒,就是当时这段历史的见证。
</GuideStepsItem>
</GuideSteps>
{/* 光标1的多选题 */}
<MultipleChoiceQuizPanel
visible={quiz1Visible}
zIndex={10000}
title="题目2:关于辛亥革命的意义,下列表述正确的有()"
options={[
{ value: "1", label: "结束了中国两千多年的君主专制制度" },
{ value: "2", label: "传播了民主共和理念,推动了中华民族思想解放" },
{ value: "3", label: "冲破了封建主义藩篱,打击了帝国主义在华势力" },
{ value: "4", label: "完成了民族独立、人民解放的历史任务" },
]}
correctAnswer={["1", "2", "3"]}
errorMessage="辛亥革命未能改变旧中国半殖民地半封建的社会性质和中国人民的悲惨境遇,因此也未能完成实现民族独立、人民解放的历史任务。但它拉开了中国完全意义上的近代民族民主革命的序幕。"
onSubmit={(values: string[], isCorrect: boolean) => {
if (isCorrect) {
setQuiz1Visible(false);
setSuccessAlert1Visible(true);
}
}}
/>
{/* 光标1的成功提示 */}
<SuccessAlert
visible={successAlert1Visible}
title="恭喜你, 选择正确!"
onContinue={() => {
setSuccessAlert1Visible(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
}}
onClose={() => {
setSuccessAlert1Visible(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
}}
/>
</div> </div>
); );
}; };
+149 -25
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, useRef } from "react";
import { import {
ExhibitOverlay, ExhibitOverlay,
Title, Title,
@@ -6,8 +6,11 @@ import {
Cursor, Cursor,
GuideSteps, GuideSteps,
GuideStepsItem, GuideStepsItem,
StudyGuideModal,
Video,
type VideoRef,
} from "@/components"; } from "@/components";
import { getBackgroundImage, 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";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
@@ -25,25 +28,38 @@ const SunzhongshanPlace2 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗(进入场景即显示)
const [showGuideModal, setShowGuideModal] = useState(true);
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>( const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
null null
); );
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
const [guideVisible, setGuideVisible] = useState(false);
const [guideStep, setGuideStep] = useState(0); // 光标1的对话状态
const [guide1Visible, setGuide1Visible] = useState(false);
const [guide1Step, setGuide1Step] = useState(0);
// 光标1的视频状态
const [showVideo1, setShowVideo1] = useState(false);
const video1Ref = useRef<VideoRef>(null);
// 光标4的对话状态
const [guide4Visible, setGuide4Visible] = useState(false);
const [guide4Step, setGuide4Step] = useState(0);
// 光标3的视频状态
const [showVideo3, setShowVideo3] = useState(false);
const video3Ref = useRef<VideoRef>(null);
const handleOpenExhibit = (index: 1 | 2 | 3 | 4) => { const handleOpenExhibit = (index: 1 | 2 | 3 | 4) => {
// if (index === 1) {
// setActiveExhibitId(1);
// setGuideVisible(true);
// setGuideStep(0);
// } else {
setActiveExhibitId(index); setActiveExhibitId(index);
// } // 根据不同的展品显示对应的对话,并重置进度
}; if (index === 1) {
setGuide1Step(0);
const handleGuideStepChange = (step: number) => { setGuide1Visible(true);
setGuideStep(step); } else if (index === 4) {
setGuide4Step(0);
setGuide4Visible(true);
}
}; };
const progressCount = visited.size; const progressCount = visited.size;
@@ -80,22 +96,25 @@ const SunzhongshanPlace2 = () => {
} }
}} }}
/> />
{/* 光标1-铁路计划图 */}
<Cursor <Cursor
className="absolute left-160 top-114" className="absolute left-160 top-114"
onClick={() => handleOpenExhibit(1)} onClick={() => handleOpenExhibit(1)}
read={visited.has(1)} read={visited.has(1)}
/> />
{/* 光标2-陆军大元帅时佩带的指挥刀 */}
<Cursor <Cursor
className="absolute left-235 bottom-95" className="absolute left-235 bottom-95"
onClick={() => handleOpenExhibit(2)} onClick={() => handleOpenExhibit(2)}
read={visited.has(2)} read={visited.has(2)}
/> />
{/* 光标3-917.9.10孙中山就任中华民国军政府海陆军大元帅留影 */}
<Cursor <Cursor
className="absolute left-241.5 top-119" className="absolute left-241.5 top-119"
onClick={() => handleOpenExhibit(3)} onClick={() => handleOpenExhibit(3)}
read={visited.has(3)} read={visited.has(3)}
/> />
{/* 光标4-实业计划 */}
<Cursor <Cursor
className="absolute right-120 bottom-78" className="absolute right-120 bottom-78"
onClick={() => handleOpenExhibit(4)} onClick={() => handleOpenExhibit(4)}
@@ -117,10 +136,31 @@ const SunzhongshanPlace2 = () => {
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { const currentId = activeExhibitId;
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
setGuide1Step(0);
setGuide1Visible(false);
setGuide4Step(0);
setGuide4Visible(false);
// 关闭展品框时,重置对应对话的进度并隐藏对话
if (currentId === 1) {
// 光标1:关闭窗口后播放视频
setActiveExhibitId(null);
setShowVideo1(true);
return;
} else if (currentId === 4) {
// 光标4:正常关闭,更新visited状态
} else if (currentId === 3) {
// 光标3:关闭窗口后播放视频
setActiveExhibitId(null);
setShowVideo3(true);
return;
}
if (currentId !== null) {
setVisited((prev) => { setVisited((prev) => {
const next = new Set(prev); const next = new Set(prev);
next.add(activeExhibitId); next.add(currentId);
return next; return next;
}); });
} }
@@ -128,22 +168,106 @@ const SunzhongshanPlace2 = () => {
}} }}
/> />
{/* <GuideSteps {/* 光标1的对话 */}
visible={guideVisible} <GuideSteps
onVisibleChange={setGuideVisible} visible={guide1Visible}
currentStep={guideStep} currentStep={guide1Step}
onStepChange={handleGuideStepChange} onStepChange={setGuide1Step}
onVisibleChange={(nextVisible) => {
setGuide1Visible(nextVisible);
}}
showMask={false} showMask={false}
highlightSelectors={[]} highlightSelectors={[]}
zIndex={10000} zIndex={10000}
> >
<GuideStepsItem speaker="student"> <GuideStepsItem speaker="student">
孙中山先生是如何讨袁护法、捍卫共和的? 这张大大的路线图好复杂,上面画满了铁路线,这是孙中山先生规划的吗?
</GuideStepsItem> </GuideStepsItem>
<GuideStepsItem speaker="guide"> <GuideStepsItem speaker="guide">
让我们继续探索孙中山先生讨袁护法、捍卫共和的历程... 对!这是中国铁路总公司的路线图。孙中山先生辞去临时大总统后,致力于国家的实业建设。他认为"交通为实业之母,铁路又为交通之母"。要让国家强大,首先要发展实业,投身铁路规划与建设就是其中一项重要实践。
</GuideStepsItem> </GuideStepsItem>
</GuideSteps> */} <GuideStepsItem speaker="guide">
旁边这本《实业计划》中译稿手稿,就是他对中国实业发展的整体构想,里面不仅有铁路,还有港口、工厂等规划。这个计划也是孙先生在上海居住期间完成的。
</GuideStepsItem>
</GuideSteps>
{/* 光标1的视频 */}
{showVideo1 && (
<Video
ref={video1Ref}
src={getPublicPath("video/sunzhongshan/sunzhongshan_05.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setShowVideo1(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
}}
/>
)}
{/* 光标4的对话 */}
<GuideSteps
visible={guide4Visible}
currentStep={guide4Step}
onStepChange={setGuide4Step}
onVisibleChange={(nextVisible) => {
setGuide4Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
真是一个宏伟的计划,可是在内忧外患的时局下,实现起来一定很艰难吧?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
是的,1913年3月"宋教仁案"将国民党和袁世凯方面的矛盾推向高峰。不愿坐视袁世凯窃取辛亥革命果实后独揽大权,革命党人发动了"二次革命"。可惜革命失败,孙中山先生被迫流亡日本。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
袁世凯趁机撤销了他"筹办全国铁路全权"的职务,把铁路公司的事权交给了北京政府交通部,公司就这样解散了。
</GuideStepsItem>
</GuideSteps>
{/* 光标3的视频 */}
{showVideo3 && (
<Video
ref={video3Ref}
src={getPublicPath("video/sunzhongshan/sunzhongshan_05.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setShowVideo3(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(3);
return next;
});
}}
/>
)}
{/* 学习引导(进入场景即显示,与 place1 保持一致) */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => setShowGuideModal(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点击查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };
+265 -5
View File
@@ -1,7 +1,19 @@
import { useState } from "react"; import { useState, useRef } from "react";
import { ExhibitOverlay, Title, Image, Cursor } from "@/components"; import {
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; ExhibitOverlay,
Title,
Image,
Cursor,
StudyGuideModal,
GuideSteps,
GuideStepsItem,
Video,
type VideoRef,
type DragFillResult,
} from "@/components";
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
@@ -18,13 +30,50 @@ const SunzhongshanPlace3 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗(进入场景即显示)
const [showGuideModal, setShowGuideModal] = useState(true);
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>( const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
null null
); );
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
// 光标1的对话状态
const [guide1Visible, setGuide1Visible] = useState(false);
const [guide1Step, setGuide1Step] = useState(0);
// 光标2的对话状态
const [guide2Visible, setGuide2Visible] = useState(false);
const [guide2Step, setGuide2Step] = useState(0);
// 光标2的视频状态
const [showVideo2, setShowVideo2] = useState(false);
const video2Ref = useRef<VideoRef>(null);
// 光标2的拖拽题状态
const [dragQuiz2Visible, setDragQuiz2Visible] = useState(false);
const [dragQuiz2Value, setDragQuiz2Value] = useState<DragFillResult[]>([]);
const [dragQuiz2Error, setDragQuiz2Error] = useState("");
const [successAlert2Visible, setSuccessAlert2Visible] = useState(false);
// 光标3的对话状态
const [guide3Visible, setGuide3Visible] = useState(false);
const [guide3Step, setGuide3Step] = useState(0);
// 光标4的对话状态
const [guide4Visible, setGuide4Visible] = useState(false);
const [guide4Step, setGuide4Step] = useState(0);
const handleOpenExhibit = (index: 1 | 2 | 3 | 4) => { const handleOpenExhibit = (index: 1 | 2 | 3 | 4) => {
setActiveExhibitId(index); setActiveExhibitId(index);
// 根据不同的展品显示对应的对话,并重置进度
if (index === 1) {
setGuide1Step(0);
setGuide1Visible(true);
} else if (index === 2) {
setGuide2Step(0);
setGuide2Visible(true);
} else if (index === 3) {
setGuide3Step(0);
setGuide3Visible(true);
} else if (index === 4) {
setGuide4Step(0);
setGuide4Visible(true);
}
}; };
const progressCount = visited.size; const progressCount = visited.size;
@@ -99,16 +148,227 @@ const SunzhongshanPlace3 = () => {
visible={activeExhibitId !== null} visible={activeExhibitId !== null}
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""} src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
onClose={() => { onClose={() => {
if (activeExhibitId !== null) { const currentId = activeExhibitId;
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
setGuide1Step(0);
setGuide1Visible(false);
setGuide2Step(0);
setGuide2Visible(false);
setGuide3Step(0);
setGuide3Visible(false);
setGuide4Step(0);
setGuide4Visible(false);
if (currentId === 2) {
// 光标2:关闭窗口后播放视频
setActiveExhibitId(null);
setShowVideo2(true);
return;
}
if (currentId !== null) {
setVisited((prev) => { setVisited((prev) => {
const next = new Set(prev); const next = new Set(prev);
next.add(activeExhibitId); next.add(currentId);
return next; return next;
}); });
} }
setActiveExhibitId(null); setActiveExhibitId(null);
}} }}
/> />
{/* 光标1的对话 */}
<GuideSteps
visible={guide1Visible}
currentStep={guide1Step}
onStepChange={setGuide1Step}
onVisibleChange={(nextVisible) => {
setGuide1Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
这张是李大钊先生的手迹啊,和孙中山先生有什么关系呢?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这封信的收件人是胡适。信里提到,孙中山先生经历了陈炯明叛变事件回到上海后,态度变得很冷静,不再坚持之前的护法主张,反而提出"收军权于中央""结合民主的联合战线,与反动派决裂"等。
</GuideStepsItem>
<GuideStepsItem speaker="student">
孙先生与李大钊先生进行了深入的交谈。他在寻找新的合作力量吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这个力量就是新生的中国共产党。孙中山先生向共产党人释放了巨大的诚意。除了与李大钊的频繁接触,他还在上海会见了共产国际代表马林,明确表示愿意和苏俄合作;邀请鲍罗庭来中国担任国民党组织教练员;
</GuideStepsItem>
<GuideStepsItem speaker="guide">
与陈独秀多次沟通,在共产国际与中共的建议和帮助下改组国民党,为国民党一大的召开做准备。
</GuideStepsItem>
</GuideSteps>
{/* 光标2的对话 */}
<GuideSteps
visible={guide2Visible}
currentStep={guide2Step}
onStepChange={setGuide2Step}
onVisibleChange={(nextVisible) => {
setGuide2Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
我记得国共第一次合作正式形成的标志就是国民党一大,这就是当时的大会宣言吗?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
完全正确!1924年1月20日,中国国民党第一次全国代表大会在广东高师礼堂开幕,孙中山先生以国民党总理的身份担任大会主席,主持开幕式。此次大会共有165名代表出席,其中共产党员代表超过20人,包括李大钊、毛泽东等中共早期领导人。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
会议上,孙先生重新解释了三民主义,事实上确立了联俄、联共、扶助农工的三大政策。第一次国共合作正式形成后,轰轰烈烈的国民革命也就此展开。
</GuideStepsItem>
</GuideSteps>
{/* 光标2的视频 */}
{showVideo2 && (
<Video
ref={video2Ref}
src={getPublicPath("video/sunzhongshan/sunzhongshan_08.mp4")}
className="fixed inset-0 z-[20000]"
autoPlay
playsInline
showSkipButton
onEnded={() => {
setShowVideo2(false);
setDragQuiz2Visible(true);
}}
/>
)}
{/* 光标2的拖拽题 */}
<DragQuizPanel
visible={dragQuiz2Visible}
zIndex={10000}
title="题目1 (拖拽题):"
questionPrefix="3. 中国国民党第一次全国代表大会事实上确立了("
questionSuffix=")三大政策。"
options={[
{ value: "联俄", label: "联俄" },
{ value: "联共", label: "联共" },
{ value: "扶助农工", label: "扶助农工" },
{ value: "反俄", label: "反俄" },
]}
blanks={[
{ id: "blank1", label: "____" },
{ id: "blank2", label: "____" },
{ id: "blank3", label: "____" },
]}
value={dragQuiz2Value}
error={dragQuiz2Error}
onChange={(value) => {
setDragQuiz2Value(value);
setDragQuiz2Error("");
if (value.length < 3) return;
// 检查答案:需要三个空白都填满,且答案正确
const correctAnswers = ["联俄", "联共", "扶助农工"];
const filledAnswers = value.map((v) => v.optionValue);
const answerSet = new Set(correctAnswers);
const filledSet = new Set(filledAnswers);
const isCorrect =
answerSet.size === filledSet.size &&
[...answerSet].every((answer) => filledSet.has(answer));
if (isCorrect) {
setDragQuiz2Visible(false);
setSuccessAlert2Visible(true);
} else {
setDragQuiz2Error(
"中国国民党第一次全国代表大会上,孙中山重新解释了三民主义,事实上确立了联俄、联共、扶助农工的三大政策。三大政策是孙中山的重要政治主张,使他倡导的民族民主革命从屡受挫折转向成功进而走上了正确道路。"
);
}
}}
/>
{/* 光标2的成功提示 */}
<SuccessAlert
visible={successAlert2Visible}
onContinue={() => {
setSuccessAlert2Visible(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(2);
return next;
});
}}
onClose={() => {
setSuccessAlert2Visible(false);
// 修改cursor状态
setVisited((prev) => {
const next = new Set(prev);
next.add(2);
return next;
});
}}
/>
{/* 光标3的对话 */}
<GuideSteps
visible={guide3Visible}
currentStep={guide3Step}
onStepChange={setGuide3Step}
onVisibleChange={(nextVisible) => {
setGuide3Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
这时候,工农群众的力量还没有正式登上历史舞台呢。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
旁边这本《星期评论》创刊于五四运动后。孙中山先生在五四运动中看到了群众的力量和工人、学生们的热情。更觉顺应时代潮流,传播新文化、介绍马克思主义、鼓励工人运动的重要性。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
这份进步刊物成为一扇窗,让国人,也让孙先生自己看到了民族、民主革命的新转机。
</GuideStepsItem>
</GuideSteps>
{/* 光标4的对话 */}
<GuideSteps
visible={guide4Visible}
currentStep={guide4Step}
onStepChange={setGuide4Step}
onVisibleChange={(nextVisible) => {
setGuide4Visible(nextVisible);
}}
showMask={false}
highlightSelectors={[]}
zIndex={10000}
>
<GuideStepsItem speaker="student">
这本书看起来很特别,《社会主义的精义》是孙中山先生写的吗?他也关注社会主义学说呀?
</GuideStepsItem>
<GuideStepsItem speaker="guide">
不仅关注,而且还很早。1912年10月14日-16日,孙中山先生连续三日在上海三马路(今汉口路)中华大戏院发表关于社会主义学说的演讲,吸引了众多听众。这就是演说后经由他本人修订出版的小册子。
</GuideStepsItem>
<GuideStepsItem speaker="guide">
后来俄国十月革命胜利,上海《民国日报》最先在中国报道了这个消息。
</GuideStepsItem>
</GuideSteps>
{/* 学习引导(进入场景即显示,与 place1 保持一致) */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => setShowGuideModal(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点击查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };
+22 -1
View File
@@ -1,5 +1,11 @@
import { useState } from "react"; import { useState } from "react";
import { ExhibitOverlay, Title, Image, Cursor } from "@/components"; import {
ExhibitOverlay,
Title,
Image,
Cursor,
StudyGuideModal,
} from "@/components";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
@@ -16,6 +22,8 @@ const SunzhongshanPlace4 = () => {
moduleName: string; moduleName: string;
}>(); }>();
// 学习引导弹窗(进入场景即显示)
const [showGuideModal, setShowGuideModal] = useState(true);
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null); const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
const [visited, setVisited] = useState<Set<number>>(new Set()); const [visited, setVisited] = useState<Set<number>>(new Set());
@@ -94,6 +102,19 @@ const SunzhongshanPlace4 = () => {
setActiveExhibitId(null); setActiveExhibitId(null);
}} }}
/> />
{/* 学习引导(进入场景即显示,与 place1 保持一致) */}
<StudyGuideModal
visible={showGuideModal}
onContinue={() => setShowGuideModal(false)}
ailgn="left"
>
1、场景中出现动态光点的展品表示未浏览,可通过点击光点来了解展品背后的故事,通过滚轮可以放大展品,浏览展品后蓝色动态光点会变为已浏览状态;
{"\n"}
2、在学习过程中会掉落拼图碎片,拼图碎片会自动收入拼图盒中,拼图盒可以点击查看当前拼图收集情况,点击单个拼图可以回到获得拼图的场景再次学习;
{"\n"}
3、所有动态光点的展品全部学习后才能进入下一个场景学习;
</StudyGuideModal>
</div> </div>
); );
}; };