feat:完善孙中山

This commit is contained in:
zhongyidian committed 2026-01-10 21:09:26 +08:00
1 parent b3d30100da
commit ec7e8e2bd2
14 files changed
+836 -125

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+2 -2
View File
@@ -74,7 +74,7 @@ export const StudyGuideModal = ({
{/* 文本内容 */} {/* 文本内容 */}
<div <div
className={cn( className={cn(
"relative z-10 w-[1400px] flex items-center justify-center mx-auto text-white pointer-events-none", "relative z-10 w-[1480px] flex items-center justify-center mx-auto text-white pointer-events-auto",
ailgn === "left" && "justify-start", ailgn === "left" && "justify-start",
ailgn === "right" && "justify-end" ailgn === "right" && "justify-end"
)} )}
@@ -87,7 +87,7 @@ export const StudyGuideModal = ({
{children && ( {children && (
<div <div
className={cn( className={cn(
"text-4xl leading-relaxed whitespace-pre-line", "text-4xl leading-relaxed whitespace-pre-line overflow-y-auto custom-scrollbar max-h-[300px]",
"[text-shadow:_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4)]" "[text-shadow:_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4),_0px_0px_10px_rgba(193,85,64,0.4)]"
)} )}
style={{ style={{
@@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { MultipleChoice } from "@/components"; import { MultipleChoice } from "@/components";
import { getBackgroundImage } from "@/lib/utils";
import type { MultipleChoiceQuizPanelProps } from "./types"; import type { MultipleChoiceQuizPanelProps } from "./types";
/** /**
@@ -110,14 +109,21 @@ const MultipleChoiceQuizPanel = ({
className="fixed inset-0 flex items-center justify-center bg-black/40" className="fixed inset-0 flex items-center justify-center bg-black/40"
style={{ zIndex }} style={{ zIndex }}
> >
<div className="relative w-[1728px] h-[972px] flex flex-col items-center py-8"> <div className="relative w-[1728px] flex flex-col items-center py-6">
{/* 题干卡片 */} {/* 题干卡片 */}
<div className="w-[1600px] min-h-[200px] max-h-[280px] rounded-[32px] bg-black px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9] flex-shrink-0"> <div className="w-[1700px] rounded-[32px] bg-black px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9] flex-shrink-0">
<div className="text-[34px] leading-relaxed text-white">{title}</div> <div className="text-[34px] leading-relaxed text-white">{title}</div>
{/* 答案反馈 */}
{error && (
<div className="mt-5 text-center text-[26px] text-[#fcbe53]">
{error}
</div>
)}
</div> </div>
{/* 选项区域 - 可滚动 */} {/* 选项区域 - 可滚动 */}
<div className="mt-8 w-[1500px] flex-1 overflow-y-auto overflow-x-hidden px-4 min-h-0 quiz-scrollbar"> <div className="mt-8 overflow-y-auto overflow-x-hidden custom-scrollbar">
<MultipleChoice <MultipleChoice
options={options} options={options}
value={selectedValues} value={selectedValues}
@@ -166,36 +172,16 @@ const MultipleChoiceQuizPanel = ({
return ( return (
<div key={option.value} className="mb-4"> <div key={option.value} className="mb-4">
<div <div
className="w-full max-w-[1431px] min-h-[120px] h-auto py-4 flex items-center px-16 text-[28px] leading-relaxed text-[#fdf5e6] bg-cover bg-center bg-no-repeat transition-all duration-150" className={`w-[1500px] flex items-center p-4 text-[28px] leading-relaxed rounded-tr-[50px] rounded-bl-4xl transition-all duration-150 ${
style={{ isDisabled
backgroundImage: getBackgroundImage( ? "opacity-60 cursor-not-allowed"
: "cursor-pointer"
} ${
isSelected isSelected
? "components/quiz/选中" ? "bg-[#d4a574] text-[#5c3e2e] bg-gradient-to-r from-[#ddf3e1] to-[#fbdd97]"
: "components/quiz/未选择" : "bg-[#d7b888] text-[#5c3e2e] hover:brightness-110"
), }`}
filter: isSelected ? undefined : "brightness(0.96)",
opacity: isDisabled ? 0.6 : 1,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
onClick={handleClick} onClick={handleClick}
onMouseEnter={(e) => {
if (isSelected || isDisabled) return;
(
e.currentTarget as HTMLDivElement
).style.backgroundImage =
getBackgroundImage("components/quiz/选中");
(e.currentTarget as HTMLDivElement).style.filter = "";
}}
onMouseLeave={(e) => {
if (isSelected || isDisabled) return;
(
e.currentTarget as HTMLDivElement
).style.backgroundImage = getBackgroundImage(
"components/quiz/未选择"
);
(e.currentTarget as HTMLDivElement).style.filter =
"brightness(0.96)";
}}
> >
{option.label} {option.label}
</div> </div>
@@ -206,14 +192,7 @@ const MultipleChoiceQuizPanel = ({
</div> </div>
{/* 提交按钮和错误信息 - 固定在底部,不滚动 */} {/* 提交按钮和错误信息 - 固定在底部,不滚动 */}
<div className="w-[1500px] flex-shrink-0 mt-4"> <div className="flex-shrink-0 mt-4">
{/* 答案反馈 */}
{error && (
<div className="mb-4 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
{/* 提交按钮 */} {/* 提交按钮 */}
<div className="flex justify-center"> <div className="flex justify-center">
<button <button
@@ -224,7 +203,7 @@ const MultipleChoiceQuizPanel = ({
(minSelections !== undefined && (minSelections !== undefined &&
selectedValues.length < minSelections) selectedValues.length < minSelections)
} }
className="px-30 py-4 text-[28px] font-medium text-white bg-[#b4915a] rounded-[40px] transition-all duration-200 hover:brightness-110 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg" className="px-30 py-4 text-[28px] font-medium text-white bg-[#c2995f] rounded-[40px] transition-all duration-200 hover:brightness-110 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg"
> >
{submitText} {submitText}
</button> </button>
@@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { SingleChoice } from "@/components"; import { SingleChoice } from "@/components";
import { getBackgroundImage } from "@/lib/utils";
import type { SingleChoiceQuizPanelProps } from "./types"; import type { SingleChoiceQuizPanelProps } from "./types";
/** /**
@@ -87,12 +86,19 @@ const SingleChoiceQuizPanel = ({
> >
<div className="relative w-[1728px] h-[972px] flex flex-col items-center justify-center"> <div className="relative w-[1728px] h-[972px] flex flex-col items-center justify-center">
{/* 题干卡片 */} {/* 题干卡片 */}
<div className="w-[1600px] rounded-[32px] bg-black px-20 py-14 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]"> <div className="w-[1700px] rounded-[32px] bg-black px-20 py-8 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
<div className="text-[34px] leading-relaxed text-white">{title}</div> <div className="text-[34px] leading-relaxed text-white">{title}</div>
{/* 答案反馈 */}
{error && (
<div className="mt-5 text-center text-[26px] text-[#fcbe53]">
{error}
</div>
)}
</div> </div>
{/* 选项区域 */} {/* 选项区域 */}
<div className="mt-16 w-[1500px]"> <div className="mt-16">
<SingleChoice <SingleChoice
options={options} options={options}
value={selectedValue} value={selectedValue}
@@ -104,43 +110,21 @@ const SingleChoiceQuizPanel = ({
onClick={() => handleOptionClick(option.value)} onClick={() => handleOptionClick(option.value)}
> >
<div <div
className="w-[1431px] max-h-[156px] min-h-[100px] flex items-center px-16 text-[28px] leading-snug text-[#fdf5e6] bg-cover bg-center bg-no-repeat transition-all duration-150" className={`w-[1500px] flex items-center p-4 text-[28px] leading-relaxed rounded-tr-[50px] rounded-bl-4xl transition-all duration-150 ${
style={{ disabled
backgroundImage: getBackgroundImage( ? "opacity-60 cursor-not-allowed"
: "cursor-pointer"
} ${
isSelected isSelected
? "components/quiz/选中" ? "bg-[#d4a574] text-[#5c3e2e]"
: "components/quiz/未选择" : "bg-[#d7b888] text-[#5c3e2e] hover:bg-gradient-to-r from-[#ddf3e1] to-[#fbdd97]"
), }`}
filter: isSelected ? undefined : "brightness(0.96)",
opacity: disabled ? 0.6 : 1,
cursor: disabled ? "not-allowed" : "pointer",
}}
onMouseEnter={(e) => {
if (isSelected || disabled) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/选中");
(e.currentTarget as HTMLDivElement).style.filter = "";
}}
onMouseLeave={(e) => {
if (isSelected || disabled) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/未选择");
(e.currentTarget as HTMLDivElement).style.filter =
"brightness(0.96)";
}}
> >
{option.label} {option.label}
</div> </div>
</div> </div>
)} )}
/> />
{/* 答案反馈 */}
{error && (
<div className="mt-6 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -140,7 +140,7 @@ const HuangyanpeiReflectionScene1 = () => {
<SuccessAlert <SuccessAlert
visible={showSuccessAlert} visible={showSuccessAlert}
title="拼接毛竹成功!" title="拼接毛竹单杠成功,同学们可以在课间之余强身健体!"
onContinue={useCallback(() => { onContinue={useCallback(() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setGameCompleted(true); setGameCompleted(true);
@@ -76,9 +76,9 @@ const ViseAssemblyDialog = ({
// 转换为 DragFill 的 options // 转换为 DragFill 的 options
const options = useMemo( const options = useMemo(
() => [ () => [
{ value: "A", label: "A" }, { value: "A", label: "丝杠和丝杆" },
{ value: "B", label: "B" }, { value: "B", label: "活动钳口" },
{ value: "C", label: "C" }, { value: "C", label: "固定钳口和基座" },
], ],
[] []
); );
@@ -339,6 +339,8 @@ const ViseAssemblyDialog = ({
: "hover:scale-110 opacity-100" : "hover:scale-110 opacity-100"
)} )}
> >
<div className="flex flex-col gap-y-4">
<div className="flex items-center justify-center">
<div className="mr-16 text-8xl font-bold text-[#cf9e51] [text-shadow:_3px_3px_0_#fff,_-3px_-3px_0_#fff,_3px_-3px_0_#fff,_-3px_3px_0_#fff]"> <div className="mr-16 text-8xl font-bold text-[#cf9e51] [text-shadow:_3px_3px_0_#fff,_-3px_-3px_0_#fff,_3px_-3px_0_#fff,_-3px_3px_0_#fff]">
{option.value} {option.value}
</div> </div>
@@ -349,6 +351,13 @@ const ViseAssemblyDialog = ({
draggable={false} draggable={false}
/> />
</div> </div>
<div className="flex items-center justify-center">
<div className="mr-16 text-3xl font-bold text-white">
{option.label}
</div>
</div>
</div>
</div>
); );
}} }}
renderDragging={(option) => { renderDragging={(option) => {
+14
View File
@@ -22,6 +22,7 @@ const LuxunReflectionScene3 = () => {
const [completedQuiz2, setCompletedQuiz2] = useState(false); // 题目2是否完成 const [completedQuiz2, setCompletedQuiz2] = useState(false); // 题目2是否完成
const [showSummary, setShowSummary] = useState(false); // 是否显示总结 const [showSummary, setShowSummary] = useState(false); // 是否显示总结
const [completionGuideVisible, setCompletionGuideVisible] = useState(false); // 通关提示对话是否显示 const [completionGuideVisible, setCompletionGuideVisible] = useState(false); // 通关提示对话是否显示
const [completionGuideFinished, setCompletionGuideFinished] = useState(false); // 通关提示对话是否已完成
const allCompleted = completedQuiz1 && completedQuiz2; const allCompleted = completedQuiz1 && completedQuiz2;
const progress = (completedQuiz1 ? 1 : 0) + (completedQuiz2 ? 1 : 0); const progress = (completedQuiz1 ? 1 : 0) + (completedQuiz2 ? 1 : 0);
@@ -263,6 +264,18 @@ const LuxunReflectionScene3 = () => {
</div> </div>
</div> </div>
{/* 继续前进按钮 - 只在通关提示对话结束后显示 */}
{completionGuideFinished && (
<Image
className="absolute bottom-45 right-0"
src={getStaticImage("components/place/继续前进")}
button
onClick={() => {
navigate(`/${moduleName}/${starName}/home`);
}}
/>
)}
{/* 题目1:家国情怀 */} {/* 题目1:家国情怀 */}
<SingleChoiceQuizPanel <SingleChoiceQuizPanel
visible={showQuiz1} visible={showQuiz1}
@@ -295,6 +308,7 @@ const LuxunReflectionScene3 = () => {
onVisibleChange={(visible) => { onVisibleChange={(visible) => {
if (!visible) { if (!visible) {
setCompletionGuideVisible(false); setCompletionGuideVisible(false);
setCompletionGuideFinished(true); // 对话关闭后标记为已完成
} }
}} }}
showMask={false} showMask={false}
+3 -3
View File
@@ -358,9 +358,9 @@ const SunzhongshanPlace1 = () => {
<DragQuizPanel <DragQuizPanel
visible={dragQuiz4Visible} visible={dragQuiz4Visible}
zIndex={10000} zIndex={10000}
title="题目 1:" title="题目 1(拖拽题):"
questionPrefix="三民主义是孙中山所倡导的民主革命纲领,是其民主思想的精髓和高度概括,具体包括(" questionPrefix="三民主义是孙中山所倡导的民主革命纲领,是其民主思想的精髓和高度概括,具体包括("
questionSuffix=")。(拖拽题)" questionSuffix=")。"
options={[ options={[
{ value: "民族", label: "民族" }, { value: "民族", label: "民族" },
{ value: "民权", label: "民权" }, { value: "民权", label: "民权" },
@@ -489,7 +489,7 @@ const SunzhongshanPlace1 = () => {
<MultipleChoiceQuizPanel <MultipleChoiceQuizPanel
visible={quiz1Visible} visible={quiz1Visible}
zIndex={10000} zIndex={10000}
title="题目2:关于辛亥革命的意义,下列表述正确的有()" title="题目 2(多选题):关于辛亥革命的意义,下列表述正确的有()"
options={[ options={[
{ value: "1", label: "结束了中国两千多年的君主专制制度" }, { value: "1", label: "结束了中国两千多年的君主专制制度" },
{ value: "2", label: "传播了民主共和理念,推动了中华民族思想解放" }, { value: "2", label: "传播了民主共和理念,推动了中华民族思想解放" },
+1 -1
View File
@@ -239,7 +239,7 @@ const SunzhongshanPlace2 = () => {
{showVideo3 && ( {showVideo3 && (
<Video <Video
ref={video3Ref} ref={video3Ref}
src={getPublicPath("video/sunzhongshan/sunzhongshan_05.mp4")} src={getPublicPath("video/sunzhongshan/sunzhongshan_06.mp4")}
className="fixed inset-0 z-[20000]" className="fixed inset-0 z-[20000]"
autoPlay autoPlay
playsInline playsInline
+24 -2
View File
@@ -1,9 +1,10 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Image } from "@/components"; import { Image, StudyGuideModal } from "@/components";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { getBackgroundImage, getStaticImage } from "@/lib/utils";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
const GUIDE_STORAGE_KEY = `${starName}-reflection-guide-shown`;
const SunzhongshanReflection = () => { const SunzhongshanReflection = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -12,11 +13,25 @@ const SunzhongshanReflection = () => {
}>(); }>();
const [selectedScene, setSelectedScene] = useState<number | null>(null); const [selectedScene, setSelectedScene] = useState<number | null>(null);
const [showGuide, setShowGuide] = useState(false);
// 检查是否是第一次进入,显示学习引导
useEffect(() => {
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
if (!hasShownGuide) {
setShowGuide(true);
}
}, []);
const handleSceneClick = (sceneNumber: number) => { const handleSceneClick = (sceneNumber: number) => {
setSelectedScene(sceneNumber); setSelectedScene(sceneNumber);
}; };
const handleGuideClose = () => {
setShowGuide(false);
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
};
const handleStartExplore = () => { const handleStartExplore = () => {
if (selectedScene && moduleName && starName) { if (selectedScene && moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection${selectedScene}`); navigate(`/${moduleName}/${starName}/reflection${selectedScene}`);
@@ -112,6 +127,13 @@ const SunzhongshanReflection = () => {
/> />
</div> </div>
</div> </div>
{/* 学习引导弹窗 */}
<StudyGuideModal visible={showGuide} onContinue={handleGuideClose}>
同学你好,欢迎来到跨越时空 - 体悟学习模块!
{"\n\n"}
本模块包含三个推理小游戏,场景学习顺序不分先后,三个场景全部学习完后方能完成本模块的学习,那么现在就请你开始体验吧。
</StudyGuideModal>
</div> </div>
); );
}; };
+212 -11
View File
@@ -5,14 +5,18 @@ import {
ClueHintModal, ClueHintModal,
ClueBookModal, ClueBookModal,
flyToAnimation, flyToAnimation,
Video,
StudyGuideModal,
} from "@/components"; } from "@/components";
import { SuccessAlert } from "@/components/layout"; import { SuccessAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
import { useClueStore } from "@/store/clueStore"; import { useClueStore } from "@/store/clueStore";
import type { ClueHint } from "@/components/clue"; import type { ClueHint } from "@/components/clue";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
const sceneId = `${starName}/reflection/scene1`; const sceneId = `${starName}/reflection/scene1`;
const GUIDE_STORAGE_KEY = `${sceneId}-guide-shown`;
// 线索配置 - 根据实际图片调整位置 // 线索配置 - 根据实际图片调整位置
const clueConfigs: ClueHint[] = [ const clueConfigs: ClueHint[] = [
@@ -68,6 +72,15 @@ const SunzhongshanReflectionScene1 = () => {
const [showClueBook, setShowClueBook] = useState(false); const [showClueBook, setShowClueBook] = useState(false);
const [showSuccessAlert, setShowSuccessAlert] = useState(false); const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const [successAlertContent, setSuccessAlertContent] = useState(""); const [successAlertContent, setSuccessAlertContent] = useState("");
const [showVideo, setShowVideo] = useState(true);
const [showGuide, setShowGuide] = useState(false);
const [showQuiz1, setShowQuiz1] = useState(false);
const [showQuiz2, setShowQuiz2] = useState(false);
const [showQuiz3, setShowQuiz3] = useState(false);
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
const [pendingQuiz, setPendingQuiz] = useState<
"quiz2" | "quiz3" | "complete" | null
>(null);
const { collectClue, isClueCollected } = useClueStore(); const { collectClue, isClueCollected } = useClueStore();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -111,21 +124,22 @@ const SunzhongshanReflectionScene1 = () => {
duration: 1200, duration: 1200,
onComplete: () => { onComplete: () => {
// 动画完成后的回调 // 动画完成后的回调
setSuccessAlertContent("恭喜您,获得该线索!"); setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true); setShowSuccessAlert(true);
}, },
}); });
} }
}; };
// 处理背包点击 - 先收集线索1,然后打开线索提示框 // 处理背包点击 - 只收集线索1,不打开线索提示框
const handleBackpackClick = () => { const handleBackpackClick = () => {
const clue1Id = `${sceneId}/clue1`; const clue1Id = `${sceneId}/clue1`;
const clue1Name = "线索1"; const clue1Name = "线索1";
// 如果线索1已收集,直接打开线索提示框 // 如果线索1已收集,显示提示
if (isClueCollected(clue1Id)) { if (isClueCollected(clue1Id)) {
setShowClueHint(true); setSuccessAlertContent("该线索已收集");
setShowSuccessAlert(true);
return; return;
} }
@@ -143,16 +157,85 @@ const SunzhongshanReflectionScene1 = () => {
targetPosition: "center", targetPosition: "center",
duration: 1200, duration: 1200,
onComplete: () => { onComplete: () => {
// 动画完成后,打开线索提示框 // 动画完成后显示成功提示
// setShowClueHint(true); setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true);
}, },
}); });
} else {
// 如果 ref 不可用,直接打开线索提示框
setShowClueHint(true);
} }
}; };
// 处理线索提示按钮点击 - 打开线索提示框
const handleClueHintClick = () => {
setShowClueHint(true);
};
// 处理视频播放结束
const handleVideoEnd = () => {
setShowVideo(false);
// 检查是否需要显示学习引导
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
if (!hasShownGuide) {
setShowGuide(true);
}
};
// 处理学习引导关闭
const handleGuideClose = () => {
setShowGuide(false);
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
};
// 检查是否所有线索都已收集
const areAllCluesCollected = () => {
return clueConfigs.every((clue) => isClueCollected(clue.id));
};
// 处理线索簿关闭
const handleClueBookClose = () => {
setShowClueBook(false);
// 如果所有线索都已收集,显示题目1
if (areAllCluesCollected()) {
setShowQuiz1(true);
}
};
// 处理题目1提交
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz2");
}
};
// 处理题目2提交
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz3");
}
};
// 处理题目3提交
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
// 设置待显示的完成提示
setPendingQuiz("complete");
}
};
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false);
};
// 获取线索图片 // 获取线索图片
const getClueImage = (clueId: string) => { const getClueImage = (clueId: string) => {
const clueNumber = clueId.split("clue")[1]; const clueNumber = clueId.split("clue")[1];
@@ -182,6 +265,19 @@ const SunzhongshanReflectionScene1 = () => {
backgroundImage: getBackgroundImage(`${starName}/reflection/scene1/bg`), backgroundImage: getBackgroundImage(`${starName}/reflection/scene1/bg`),
}} }}
> >
{/* 视频播放 - 覆盖在页面内容之上 */}
{showVideo && (
<div className="fixed inset-0 z-[9999] bg-black">
<Video
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
className="w-full h-full"
onEnded={handleVideoEnd}
autoPlay={true}
showSkipButton={true}
onSkip={handleVideoEnd}
/>
</div>
)}
{/* 关闭按钮 */} {/* 关闭按钮 */}
<Image <Image
className="absolute right-0 top-0 z-10" className="absolute right-0 top-0 z-10"
@@ -249,6 +345,11 @@ const SunzhongshanReflectionScene1 = () => {
{/* 右下角-功能处 */} {/* 右下角-功能处 */}
<div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10"> <div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10">
<Image
src={getStaticImage(`${starName}/reflection/线索提示`)}
button
onClick={handleClueHintClick}
/>
<Image <Image
src={getStaticImage(`${starName}/reflection/背包`)} src={getStaticImage(`${starName}/reflection/背包`)}
button button
@@ -284,7 +385,7 @@ const SunzhongshanReflectionScene1 = () => {
name: c.name, name: c.name,
collected: isClueCollected(c.id), collected: isClueCollected(c.id),
}))} }))}
onClose={() => setShowClueBook(false)} onClose={handleClueBookClose}
getClueImage={getClueImage} getClueImage={getClueImage}
getClueDetailImage={getClueDetailImage} getClueDetailImage={getClueDetailImage}
/> />
@@ -297,12 +398,112 @@ const SunzhongshanReflectionScene1 = () => {
onContinue={() => { onContinue={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
onClose={() => { onClose={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
/> />
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
onContinue={handleGuideClose}
zIndex={10000}
>
同学你好,欢迎来到二十世纪初的上海。辛亥革命之后,民主共和的观念深入人心。
面对中外反动势力,社会各界更广泛地行动起来,以各自的方式争取着民主自由和国家尊严。
{"\n"}
请探索周围环境,拨开时局迷雾,与奋进求索的仁人志士们一同探寻中国革命的前路。本游戏需要在场景中找出4条线索,并回答正确所有问题后才能完成本游戏探索。
</StudyGuideModal>
{/* 题目1:今天的日期是? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="今天的日期是?"
options={[
{ value: "A", label: "A: 1919年5月5日" },
{ value: "B", label: "B: 1921年5月5日" },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz1Submit}
zIndex={10001}
/>
{/* 题目2:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="今天的日记写什么呢?"
options={[
{
value: "A",
label:
"今天路过莫利爱路29号,旁观了中国国民党党部举行的庆祝活动。得知孙中山先生重整旗鼓,预备再度联合西南军阀进行二次护法。思及1918年第一次护法运动的失败,隐隐为孙先生此番努力感到忧虑。",
},
{
value: "B",
label:
"参加了中华职业学校同学为庆祝广州军政府成立、孙中山在广州就任非常大总统而组织的活动,深感革命成功有望。",
},
]}
correctAnswer="A"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz2Submit}
zIndex={10001}
/>
{/* 题目3:作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现?"
options={[
{
value: "A",
label: "A: 团结强大的军阀,共举民主共和旗帜",
},
{
value: "B",
label: "B: 团结各族各界群众,建立广泛的革命统一战线",
},
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz3Submit}
zIndex={10001}
/>
{/* 完成提示弹窗 */}
<StudyGuideModal
visible={showCompleteGuide}
onContinue={handleCompleteGuideClose}
zIndex={10002}
verticalAlign="center"
>
完成本游戏探索!快去体验其他故事吧!
</StudyGuideModal>
</div> </div>
); );
}; };
+211 -11
View File
@@ -5,14 +5,18 @@ import {
ClueHintModal, ClueHintModal,
ClueBookModal, ClueBookModal,
flyToAnimation, flyToAnimation,
Video,
StudyGuideModal,
} from "@/components"; } from "@/components";
import { SuccessAlert } from "@/components/layout"; import { SuccessAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
import { useClueStore } from "@/store/clueStore"; import { useClueStore } from "@/store/clueStore";
import type { ClueHint } from "@/components/clue"; import type { ClueHint } from "@/components/clue";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
const sceneId = `${starName}/reflection/scene2`; const sceneId = `${starName}/reflection/scene2`;
const GUIDE_STORAGE_KEY = `${sceneId}-guide-shown`;
// 线索配置 - 根据实际图片调整位置 // 线索配置 - 根据实际图片调整位置
const clueConfigs: ClueHint[] = [ const clueConfigs: ClueHint[] = [
@@ -78,6 +82,15 @@ const SunzhongshanReflectionScene2 = () => {
const [showClueBook, setShowClueBook] = useState(false); const [showClueBook, setShowClueBook] = useState(false);
const [showSuccessAlert, setShowSuccessAlert] = useState(false); const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const [successAlertContent, setSuccessAlertContent] = useState(""); const [successAlertContent, setSuccessAlertContent] = useState("");
const [showVideo, setShowVideo] = useState(true);
const [showGuide, setShowGuide] = useState(false);
const [showQuiz1, setShowQuiz1] = useState(false);
const [showQuiz2, setShowQuiz2] = useState(false);
const [showQuiz3, setShowQuiz3] = useState(false);
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
const [pendingQuiz, setPendingQuiz] = useState<
"quiz2" | "quiz3" | "complete" | null
>(null);
const { collectClue, isClueCollected } = useClueStore(); const { collectClue, isClueCollected } = useClueStore();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -121,21 +134,22 @@ const SunzhongshanReflectionScene2 = () => {
duration: 1200, duration: 1200,
onComplete: () => { onComplete: () => {
// 动画完成后的回调 // 动画完成后的回调
setSuccessAlertContent("恭喜您,获得该线索!"); setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true); setShowSuccessAlert(true);
}, },
}); });
} }
}; };
// 处理背包点击 - 先收集线索1,然后打开线索提示框 // 处理背包点击 - 只收集线索1,不打开线索提示框
const handleBackpackClick = () => { const handleBackpackClick = () => {
const clue1Id = `${sceneId}/clue1`; const clue1Id = `${sceneId}/clue1`;
const clue1Name = "线索1"; const clue1Name = "线索1";
// 如果线索1已收集,直接打开线索提示框 // 如果线索1已收集,显示提示
if (isClueCollected(clue1Id)) { if (isClueCollected(clue1Id)) {
setShowClueHint(true); setSuccessAlertContent("该线索已收集");
setShowSuccessAlert(true);
return; return;
} }
@@ -153,16 +167,85 @@ const SunzhongshanReflectionScene2 = () => {
targetPosition: "center", targetPosition: "center",
duration: 1200, duration: 1200,
onComplete: () => { onComplete: () => {
// 动画完成后,打开线索提示框 // 动画完成后显示成功提示
setShowClueHint(true); setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true);
}, },
}); });
} else {
// 如果 ref 不可用,直接打开线索提示框
setShowClueHint(true);
} }
}; };
// 处理线索提示按钮点击 - 打开线索提示框
const handleClueHintClick = () => {
setShowClueHint(true);
};
// 处理视频播放结束
const handleVideoEnd = () => {
setShowVideo(false);
// 检查是否需要显示学习引导
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
if (!hasShownGuide) {
setShowGuide(true);
}
};
// 处理学习引导关闭
const handleGuideClose = () => {
setShowGuide(false);
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
};
// 检查是否所有线索都已收集
const areAllCluesCollected = () => {
return clueConfigs.every((clue) => isClueCollected(clue.id));
};
// 处理线索簿关闭
const handleClueBookClose = () => {
setShowClueBook(false);
// 如果所有线索都已收集,显示题目1
if (areAllCluesCollected()) {
setShowQuiz1(true);
}
};
// 处理题目1提交
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz2");
}
};
// 处理题目2提交
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz3");
}
};
// 处理题目3提交
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
// 设置待显示的完成提示
setPendingQuiz("complete");
}
};
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false);
};
// 获取线索图片 // 获取线索图片
const getClueImage = (clueId: string) => { const getClueImage = (clueId: string) => {
const clueNumber = clueId.split("clue")[1]; const clueNumber = clueId.split("clue")[1];
@@ -196,6 +279,19 @@ const SunzhongshanReflectionScene2 = () => {
backgroundImage: getBackgroundImage(`${starName}/reflection/scene2/bg`), backgroundImage: getBackgroundImage(`${starName}/reflection/scene2/bg`),
}} }}
> >
{/* 视频播放 - 覆盖在页面内容之上 */}
{showVideo && (
<div className="fixed inset-0 z-[9999] bg-black">
<Video
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
className="w-full h-full"
onEnded={handleVideoEnd}
autoPlay={true}
showSkipButton={true}
onSkip={handleVideoEnd}
/>
</div>
)}
{/* 关闭按钮 */} {/* 关闭按钮 */}
<Image <Image
className="absolute right-0 top-0 z-10" className="absolute right-0 top-0 z-10"
@@ -263,6 +359,11 @@ const SunzhongshanReflectionScene2 = () => {
{/* 右下角-功能处 */} {/* 右下角-功能处 */}
<div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10"> <div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10">
<Image
src={getStaticImage(`${starName}/reflection/线索提示`)}
button
onClick={handleClueHintClick}
/>
<Image <Image
src={getStaticImage(`${starName}/reflection/背包`)} src={getStaticImage(`${starName}/reflection/背包`)}
button button
@@ -298,7 +399,7 @@ const SunzhongshanReflectionScene2 = () => {
name: c.name, name: c.name,
collected: isClueCollected(c.id), collected: isClueCollected(c.id),
}))} }))}
onClose={() => setShowClueBook(false)} onClose={handleClueBookClose}
getClueImage={getClueImage} getClueImage={getClueImage}
getClueDetailImage={getClueDetailImage} getClueDetailImage={getClueDetailImage}
/> />
@@ -311,12 +412,111 @@ const SunzhongshanReflectionScene2 = () => {
onContinue={() => { onContinue={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
onClose={() => { onClose={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
/> />
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
onContinue={handleGuideClose}
zIndex={10000}
>
1921年7月,中国共产党第一次全国代表大会在上海召开,中国革命的面貌自此焕然一新。
{"\n"}
1921-1923的两年间,你顺利完成了一、二年级的在校学习任务,并在三年级进入工厂实习。身为学子,你专注于业务精进,同时亦非常关注时局的变化。一些同学和工友加入了党组织,你也对此心生向往。本游戏需要在场景中找出5条线索,并回答正确所有问题后才能完成本游戏探索。
</StudyGuideModal>
{/* 题目1:当下无产阶级工人运动面对的敌人主要有哪些? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="当下无产阶级工人运动面对的敌人主要有哪些?"
options={[
{ value: "A", label: "A: 地主和旧官僚" },
{ value: "B", label: "B: 军阀势力和帝国主义列强" },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz1Submit}
zIndex={10001}
/>
{/* 题目2:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="今天的日记写什么呢?"
options={[
{
value: "A",
label:
"A: 回想一年多来,全国已经组织起了大小罢工上百次。虽然取得过一些胜利,但现在工人们的力量毕竟弱小,我们愈是坚定,便愈将面对敌人疯狂的反扑。若为工人们找到其他方面的朋友,或许方可与荷枪实弹的军阀势力相抗衡。",
},
{
value: "B",
label:
"B: 回想一年多来,全国已经组织起了大小罢工上百次,多有捷报。此时虽面对一些挫折,但只要工人们始终坚定地团结在一起,敢于牺牲,就一定能战胜内外敌人!",
},
]}
correctAnswer="A"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz2Submit}
zIndex={10001}
/>
{/* 题目3:当下无产阶级工人运动面临困境,你觉得谁可以成为工人们的朋友? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="当下无产阶级工人运动面临困境,你觉得谁可以成为工人们的朋友?"
options={[
{
value: "A",
label: "A: 青帮、洪门等颇有势力的会党",
},
{
value: "B",
label: "B: 孙中山先生领导的中国国民党",
},
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz3Submit}
zIndex={10001}
/>
{/* 完成提示弹窗 */}
<StudyGuideModal
visible={showCompleteGuide}
onContinue={handleCompleteGuideClose}
zIndex={10002}
>
1922年夏到1924年初,李大钊来到上海,在宣传革命思想的同时与孙中山进行了数次会面,为改组国民党和促成第一次国共合作作出了巨大贡献。
1924年1月,中国国民党第一次全国代表大会在广州召开,孙中山重新解释了三民主义,事实上确立了联俄、联共、扶助农工三大政策。李大钊、毛泽东等10名共产党员当选为中国国民党第一届中央执行委员会委员或候补委员。国民党一大的召开标志着第一次国共合作的正式形成。
</StudyGuideModal>
</div> </div>
); );
}; };
+319 -17
View File
@@ -5,14 +5,19 @@ import {
ClueHintModal, ClueHintModal,
ClueBookModal, ClueBookModal,
flyToAnimation, flyToAnimation,
Video,
StudyGuideModal,
Cursor,
} from "@/components"; } from "@/components";
import { SuccessAlert } from "@/components/layout"; import { SuccessAlert, ErrorAlert } from "@/components/layout";
import { getBackgroundImage, getStaticImage } from "@/lib/utils"; import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
import { useClueStore } from "@/store/clueStore"; import { useClueStore } from "@/store/clueStore";
import type { ClueHint } from "@/components/clue"; import type { ClueHint } from "@/components/clue";
const starName = "sunzhongshan"; const starName = "sunzhongshan";
const sceneId = `${starName}/reflection/scene3`; const sceneId = `${starName}/reflection/scene3`;
const GUIDE_STORAGE_KEY = `${sceneId}-guide-shown`;
// 线索配置 - 根据实际图片调整位置 // 线索配置 - 根据实际图片调整位置
const clueConfigs: ClueHint[] = [ const clueConfigs: ClueHint[] = [
@@ -20,10 +25,10 @@ const clueConfigs: ClueHint[] = [
id: `${sceneId}/clue1`, id: `${sceneId}/clue1`,
name: "线索1", name: "线索1",
position: { position: {
top: "20%", top: "38%",
left: "65%", left: "8%",
width: "0%", width: "24%",
height: "0%", height: "60%",
}, },
}, },
{ {
@@ -50,10 +55,10 @@ const clueConfigs: ClueHint[] = [
id: `${sceneId}/clue4`, id: `${sceneId}/clue4`,
name: "线索4", name: "线索4",
position: { position: {
top: "30%", top: "75%",
left: "40%", left: "95.5%",
width: "8%", width: "0%",
height: "12%", height: "0%",
}, },
}, },
]; ];
@@ -68,6 +73,21 @@ const SunzhongshanReflectionScene3 = () => {
const [showClueBook, setShowClueBook] = useState(false); const [showClueBook, setShowClueBook] = useState(false);
const [showSuccessAlert, setShowSuccessAlert] = useState(false); const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const [successAlertContent, setSuccessAlertContent] = useState(""); const [successAlertContent, setSuccessAlertContent] = useState("");
const [showErrorAlert, setShowErrorAlert] = useState(false);
const [errorAlertContent, setErrorAlertContent] = useState("");
const [showVideo, setShowVideo] = useState(true);
const [showGuide, setShowGuide] = useState(false);
const [showQuiz1, setShowQuiz1] = useState(false);
const [showQuiz2, setShowQuiz2] = useState(false);
const [showQuiz3, setShowQuiz3] = useState(false);
const [showCompleteGuide, setShowCompleteGuide] = useState(false);
const [pendingQuiz, setPendingQuiz] = useState<
"quiz2" | "quiz3" | "complete" | null
>(null);
// 场景切换:true 表示 bg 场景,false 表示"窃窃私语"场景
const [isMainScene, setIsMainScene] = useState(false);
// 控制 Cursor 显示:获取线索1后显示,点击后进入主场景
const [showCursor, setShowCursor] = useState(false);
const { collectClue, isClueCollected } = useClueStore(); const { collectClue, isClueCollected } = useClueStore();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -111,13 +131,135 @@ const SunzhongshanReflectionScene3 = () => {
duration: 1200, duration: 1200,
onComplete: () => { onComplete: () => {
// 动画完成后的回调 // 动画完成后的回调
setSuccessAlertContent("恭喜您,获得该线索!"); setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true);
// 如果是在"窃窃私语"场景收集了线索1,显示 Cursor
if (!isMainScene && clueId === `${sceneId}/clue1`) {
setShowCursor(true);
}
},
});
}
};
// 处理背包点击 - 只收集线索4,不打开线索提示框
const handleBackpackClick = () => {
// 如果不在主场景,提示需要先获取线索1
if (!isMainScene) {
setErrorAlertContent(showCursor ? "该线索已收集" : "请先获取线索1");
setShowErrorAlert(true);
return;
}
const clue4Id = `${sceneId}/clue4`;
const clue4Name = "线索4";
// 如果线索4已收集,显示提示
if (isClueCollected(clue4Id)) {
setSuccessAlertContent("该线索已收集");
setShowSuccessAlert(true);
return;
}
// 收集线索4
collectClue(clue4Id, clue4Name);
// 获取线索4的 ref
const clue4Ref = getClueRef(clue4Id);
// 执行飞行动画
if (clue4Ref.current && clueBookButtonRef.current) {
flyToAnimation({
fromRef: clue4Ref as React.RefObject<HTMLElement>,
toRef: clueBookButtonRef as React.RefObject<HTMLElement>,
targetPosition: "center",
duration: 1200,
onComplete: () => {
// 动画完成后显示成功提示
setSuccessAlertContent("获得该线索!快去线索簿查看详细信息吧!");
setShowSuccessAlert(true); setShowSuccessAlert(true);
}, },
}); });
} }
}; };
// 处理线索提示按钮点击 - 打开线索提示框
const handleClueHintClick = () => {
setShowClueHint(true);
};
// 处理视频播放结束
const handleVideoEnd = () => {
setShowVideo(false);
// 检查是否需要显示学习引导
const hasShownGuide = localStorage.getItem(GUIDE_STORAGE_KEY);
if (!hasShownGuide) {
setShowGuide(true);
}
};
// 处理学习引导关闭
const handleGuideClose = () => {
setShowGuide(false);
localStorage.setItem(GUIDE_STORAGE_KEY, "true");
};
// 检查是否所有线索都已收集
const areAllCluesCollected = () => {
return clueConfigs.every((clue) => isClueCollected(clue.id));
};
// 处理线索簿关闭
const handleClueBookClose = () => {
setShowClueBook(false);
// 如果所有线索都已收集,显示题目1
if (areAllCluesCollected()) {
setShowQuiz1(true);
}
};
// 处理题目1提交
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz2");
}
};
// 处理题目2提交 - 两个选项都正确
const handleQuiz2Submit = (_value: string, _isCorrect: boolean) => {
// 题目2的两个选项都正确,所以总是成功
setShowQuiz2(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
setPendingQuiz("quiz3");
};
// 处理题目3提交
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false);
setSuccessAlertContent("回答正确!");
setShowSuccessAlert(true);
// 设置待显示的完成提示
setPendingQuiz("complete");
}
};
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false);
};
// 处理 Cursor 点击 - 切换到主场景
const handleCursorClick = () => {
setShowCursor(false);
setIsMainScene(true);
};
// 获取线索图片 // 获取线索图片
const getClueImage = (clueId: string) => { const getClueImage = (clueId: string) => {
const clueNumber = clueId.split("clue")[1]; const clueNumber = clueId.split("clue")[1];
@@ -139,14 +281,45 @@ const SunzhongshanReflectionScene3 = () => {
}); });
}, [isClueCollected]); }, [isClueCollected]);
// 如果线索1已收集但还在"窃窃私语"场景,显示 Cursor
useEffect(() => {
const clue1Id = `${sceneId}/clue1`;
if (!isMainScene && isClueCollected(clue1Id)) {
setShowCursor(true);
}
}, [isMainScene, isClueCollected]);
// 根据当前场景决定背景图片和显示的线索
const currentBackground = isMainScene
? getBackgroundImage(`${starName}/reflection/scene3/bg`)
: getBackgroundImage(`${starName}/reflection/scene3/窃窃私语`);
// 根据当前场景过滤线索:窃窃私语场景只显示线索1,主场景显示线索2、3、4
const visibleClues = isMainScene
? clueConfigs.filter((clue) => clue.id !== `${sceneId}/clue1`)
: clueConfigs.filter((clue) => clue.id === `${sceneId}/clue1`);
return ( return (
<div <div
ref={containerRef} ref={containerRef}
className="w-full h-full bg-cover bg-center bg-no-repeat relative" className="w-full h-full bg-cover bg-center bg-no-repeat relative"
style={{ style={{
backgroundImage: getBackgroundImage(`${starName}/reflection/scene3/bg`), backgroundImage: currentBackground,
}} }}
> >
{/* 视频播放 - 覆盖在页面内容之上 */}
{showVideo && (
<div className="fixed inset-0 z-[9999] bg-black">
<Video
src={getPublicPath(`video/${starName}/sunzhongshan_10.mp4`)}
className="w-full h-full"
onEnded={handleVideoEnd}
autoPlay={true}
showSkipButton={true}
onSkip={handleVideoEnd}
/>
</div>
)}
{/* 关闭按钮 */} {/* 关闭按钮 */}
<Image <Image
className="absolute right-0 top-0 z-10" className="absolute right-0 top-0 z-10"
@@ -159,8 +332,18 @@ const SunzhongshanReflectionScene3 = () => {
}} }}
/> />
{/* Cursor - 获取线索1后显示,点击进入主场景 */}
{showCursor && !isMainScene && (
<Cursor
className="absolute left-215 bottom-135"
size={0.8}
onClick={handleCursorClick}
read={false}
/>
)}
{/* 线索点击区域 */} {/* 线索点击区域 */}
{clueConfigs.map((clue) => { {visibleClues.map((clue) => {
const isCollected = isClueCollected(clue.id); const isCollected = isClueCollected(clue.id);
const clueRef = getClueRef(clue.id); const clueRef = getClueRef(clue.id);
return ( return (
@@ -214,10 +397,15 @@ const SunzhongshanReflectionScene3 = () => {
{/* 右下角-功能处 */} {/* 右下角-功能处 */}
<div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10"> <div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10">
<Image
src={getStaticImage(`${starName}/reflection/线索提示`)}
button
onClick={handleClueHintClick}
/>
<Image <Image
src={getStaticImage(`${starName}/reflection/背包`)} src={getStaticImage(`${starName}/reflection/背包`)}
button button
onClick={() => setShowClueHint(true)} onClick={handleBackpackClick}
/> />
<div <div
ref={clueBookButtonRef} ref={clueBookButtonRef}
@@ -235,8 +423,12 @@ const SunzhongshanReflectionScene3 = () => {
{/* 线索提示窗 */} {/* 线索提示窗 */}
<ClueHintModal <ClueHintModal
visible={showClueHint} visible={showClueHint}
backgroundImage={getStaticImage(`${starName}/reflection/scene3/bg`)} backgroundImage={getStaticImage(
clues={clueConfigs} isMainScene
? `${starName}/reflection/scene3/bg`
: `${starName}/reflection/scene3/窃窃私语`
)}
clues={visibleClues}
onClose={() => setShowClueHint(false)} onClose={() => setShowClueHint(false)}
sourceContainerRef={containerRef} sourceContainerRef={containerRef}
/> />
@@ -249,7 +441,7 @@ const SunzhongshanReflectionScene3 = () => {
name: c.name, name: c.name,
collected: isClueCollected(c.id), collected: isClueCollected(c.id),
}))} }))}
onClose={() => setShowClueBook(false)} onClose={handleClueBookClose}
getClueImage={getClueImage} getClueImage={getClueImage}
getClueDetailImage={getClueDetailImage} getClueDetailImage={getClueDetailImage}
/> />
@@ -262,12 +454,122 @@ const SunzhongshanReflectionScene3 = () => {
onContinue={() => { onContinue={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
onClose={() => { onClose={() => {
setShowSuccessAlert(false); setShowSuccessAlert(false);
setSuccessAlertContent(""); setSuccessAlertContent("");
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === "quiz2") {
setShowQuiz2(true);
setPendingQuiz(null);
} else if (pendingQuiz === "quiz3") {
setShowQuiz3(true);
setPendingQuiz(null);
} else if (pendingQuiz === "complete") {
setShowCompleteGuide(true);
setPendingQuiz(null);
}
}} }}
/> />
{/* 错误提示弹窗 */}
<ErrorAlert
visible={showErrorAlert}
text={errorAlertContent}
onClose={() => setShowErrorAlert(false)}
zIndex={10001}
onMaskClick={() => setShowErrorAlert(false)}
/>
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
onContinue={handleGuideClose}
zIndex={10000}
>
后来,学校建立了中国社会主义青年团团支部,你积极靠拢,成为一名光荣的团员。
国共合作大背景下,国民革命正在如火如荼地开展,北伐也在积极酝酿之中。你在自己的岗位上认真工作,也密切关注着时局和两党动态。
{"\n"}
本游戏需要在场景中找出4条线索,并回答正确所有问题后才能完成本游戏探索。
</StudyGuideModal>
{/* 题目1:冯玉祥发动"北京政变"后邀请孙中山北上共商国是,各界对此事的看法如何? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title='冯玉祥发动"北京政变"后邀请孙中山北上共商国是,各界对此事的看法如何?'
options={[
{ value: "A", label: "A: 意见高度一致,认为和平统一已经十拿九稳" },
{ value: "B", label: "B: 期待、信任为主,亦有担忧和谨慎的探讨" },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz1Submit}
zIndex={10001}
/>
{/* 题目2:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="今天的日记写什么呢?"
options={[
{
value: "A",
label:
"A: 当前情形,反对军阀势力进而反对列强压迫已成为国共两党乃至各界共识。只是这一目标应当如何达成呢?今年上半年就听说黄埔军校在全国招生的消息,大批青年踊跃报考。可见组建军队,武力北伐是一个公认的选项。然下半年直系军阀倒台,冯玉祥电邀孙先生北上主政,便又打开了和平统一的一线契机,如能实现,亦是应了国人的期盼。今日孙先生行程抵沪,马不停蹄接见各界人士,宣讲北上主张,我辈见之无不钦佩其鞠躬尽瘁之心。前路未明,探索不止,希望国民共同努力,早日迎来和平民主的新天地。",
},
{
value: "B",
label:
'B: 孙中山先生抵沪,来自百年后的我站在欢迎的人群中,终于亲眼见到了这位伟大的革命先行者。1924年的秋天,冯玉祥北京政变为国家的和平统一带来一线契机。回望历史,我们知道,这在中国近代的民族民主革命路途上,不过是一个插曲。但对于孙中山先生而言,抱病北上,不遗余力地宣传反帝反封建主张,几乎成为他为国家前途命运而作的最后争取。他在迷途中坚守,在变局中担当,穷极一生从未停下脚步。伟大复兴的新征程上,我们早已摆脱了屈辱与苦难,向着国家富强的光明前景、人民幸福的美好未来进发。先生"振兴中华"的呐喊犹在耳畔,"天下为公"的初心始终滚烫。今天我们所能做的,便是铭记这份坚守与担当,做跨越百年的追梦路上最坚定的继承者与前行者。',
},
]}
correctAnswer="A"
errorMessage=""
onSubmit={handleQuiz2Submit}
zIndex={10001}
/>
{/* 题目3:毅然北上赴会的孙中山先生,为争取国家和平统一作出了哪些努力? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="毅然北上赴会的孙中山先生,为争取国家和平统一作出了哪些努力?"
options={[
{
value: "A",
label:
"A: 重视舆论导向,动身前发表《北上宣言》明确态度,沿途不辞劳苦与各界会晤交流,反复重申主张。",
},
{
value: "B",
label: "B: 珍惜契机,沿途不做停顿,以最快的速度赶往北京。",
},
]}
correctAnswer="A"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz3Submit}
zIndex={10001}
/>
{/* 完成提示弹窗 */}
<StudyGuideModal
visible={showCompleteGuide}
onContinue={handleCompleteGuideClose}
zIndex={10002}
>
辛亥革命拉开了中国完全意义上的近代民族民主革命的序幕。北洋军阀首领袁世凯窃取革命成果后,各地军阀势力对内割据混战,对外出卖国家利益,在政治上谋求专制和独裁,俨然成为民族独立、民主政治、民生幸福之路上的头号绊脚石。
为了改变这一时期的困局,孙中山尝试过高举护法旗帜,两度领导护法运动;与新生的中国共产党展开合作,与无产阶级工农群众共建起统一战线;为了团结一切可以团结的力量,调动一切可以调动的因素,在生命最后的日子里抱病北上,一路不遗余力地向社会各界宣传反帝反封建主张。他经历了太多挫折,却始终以“行”求“知”。
</StudyGuideModal>
</div> </div>
); );
}; };