feat: 添加全局键盘快捷键和更新故居数据,提升用户体验
- 在 main.tsx 中添加全局键盘快捷键功能,允许用户通过 Ctrl+Shift+R 重置所有缓存。 - 更新多个故居的拼图数据,增加黄炎培和鲁迅的相关信息,丰富学习内容。 - 调整 ResidenceLayout 组件以支持新的故居,确保数据一致性。 - 清理不再使用的拼图布局配置文件,提升代码整洁性。
This commit is contained in:
1 parent
8ba36a4a1a
commit
a365964f54
155 files changed
+1387
-348
No files matched your search
@@ -14,6 +14,7 @@ import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { GuideSteps, GuideStepsItem } from "@/components";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
type QuizOption = {
|
||||
value: string;
|
||||
@@ -100,6 +101,10 @@ const QUIZ_ANCHORS: QuizAnchor[] = [
|
||||
const ChenyunInternalization = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{ moduleName: string }>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeSuccess, setActiveSuccess] = useState<string | null>(null);
|
||||
const [successContent, setSuccessContent] = useState<string>("");
|
||||
const [resumeCallback, setResumeCallback] = useState<(() => void) | null>(
|
||||
@@ -117,8 +122,10 @@ const ChenyunInternalization = () => {
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [postGuideVisible, setPostGuideVisible] = useState(false);
|
||||
const [showLetter, setShowLetter] = useState(false);
|
||||
const [showLetterVideo, setShowLetterVideo] = useState(false);
|
||||
const [showStudyGuide, setShowStudyGuide] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const letterVideoRef = useRef<VideoRef>(null);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
// 旁白关闭后显示对话引导
|
||||
@@ -146,6 +153,15 @@ const ChenyunInternalization = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 信件视频加载完成后自动播放
|
||||
const handleLetterVideoCanPlay = () => {
|
||||
if (showLetterVideo && letterVideoRef.current) {
|
||||
letterVideoRef.current.play().catch((error) => {
|
||||
console.warn("信件视频播放失败:", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
const anchors: VideoAnchor[] = useMemo(
|
||||
() =>
|
||||
QUIZ_ANCHORS.map((item) => {
|
||||
@@ -246,6 +262,7 @@ const ChenyunInternalization = () => {
|
||||
});
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
triggerPuzzleAnimation(15);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
@@ -293,6 +310,7 @@ const ChenyunInternalization = () => {
|
||||
setSuccessContent("");
|
||||
const cb = resumeCallback;
|
||||
setResumeCallback(null);
|
||||
triggerPuzzleAnimation(16);
|
||||
cb?.();
|
||||
}}
|
||||
/>
|
||||
@@ -316,6 +334,7 @@ const ChenyunInternalization = () => {
|
||||
successContent,
|
||||
dragQuizValues,
|
||||
dragQuizErrors,
|
||||
triggerPuzzleAnimation,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -429,10 +448,25 @@ const ChenyunInternalization = () => {
|
||||
// 提交信件的逻辑(values 为多个填空的内容)
|
||||
console.log("提交信件", values);
|
||||
setShowLetter(false);
|
||||
setShowStudyGuide(true);
|
||||
setShowLetterVideo(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 信件关闭后的视频 */}
|
||||
{showLetterVideo && (
|
||||
<Video
|
||||
ref={letterVideoRef}
|
||||
src={getPublicPath("video/letter/neihua_jixin.mp4")}
|
||||
className="h-full w-full"
|
||||
playsInline
|
||||
onCanPlay={handleLetterVideoCanPlay}
|
||||
onEnded={() => {
|
||||
setShowLetterVideo(false);
|
||||
setShowStudyGuide(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EndingOverlay
|
||||
visible={showStudyGuide}
|
||||
text="恭喜你已经顺利完成了孙中山故居的学习,请继续前往下一个故居吧!"
|
||||
|
||||
@@ -16,6 +16,7 @@ import Cursor from "@/components/cursor";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { MultipleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
// starName 固定为 sunzhongshan
|
||||
const starName = "sunzhongshan";
|
||||
@@ -35,6 +36,9 @@ const SunzhongshanPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -204,6 +208,20 @@ const SunzhongshanPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(1);
|
||||
}
|
||||
if( activeExhibitId === 5){
|
||||
triggerPuzzleAnimation(2);
|
||||
}
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置对应对话的进度并隐藏对话
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -28,6 +29,9 @@ const SunzhongshanPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||
null
|
||||
);
|
||||
@@ -148,6 +152,17 @@ const SunzhongshanPlace2 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(5);
|
||||
}
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
if( activeExhibitId === 3){
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { SuccessAlert, DragQuizPanel } from "@/components/layout";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -30,6 +31,9 @@ const SunzhongshanPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | 4 | null>(
|
||||
null
|
||||
);
|
||||
@@ -160,6 +164,17 @@ const SunzhongshanPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={()=>{
|
||||
if( activeExhibitId === 4){
|
||||
triggerPuzzleAnimation(8);
|
||||
}
|
||||
if( activeExhibitId === 1){
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
if( activeExhibitId === 2){
|
||||
triggerPuzzleAnimation(10);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
const currentId = activeExhibitId;
|
||||
// 关闭展品框时,重置所有对话的进度并隐藏对话,确保遮罩层正确清理
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
@@ -31,6 +32,9 @@ const SunzhongshanPlace4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 关闭学习引导后显示的对话(图1)
|
||||
const [showInitialDialog, setShowInitialDialog] = useState(true);
|
||||
const [initialDialogStep, setInitialDialogStep] = useState(0);
|
||||
@@ -134,6 +138,7 @@ const SunzhongshanPlace4 = () => {
|
||||
// 光标1:关闭窗口后播放视频
|
||||
setActiveExhibitId(null);
|
||||
setShowVideo1(true);
|
||||
triggerPuzzleAnimation(11);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene1`;
|
||||
@@ -68,6 +69,9 @@ const SunzhongshanReflectionScene1 = () => {
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
@@ -417,6 +421,7 @@ const SunzhongshanReflectionScene1 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(12);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene2`;
|
||||
@@ -79,6 +80,9 @@ const SunzhongshanReflectionScene2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
@@ -442,6 +446,7 @@ const SunzhongshanReflectionScene2 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(13);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SingleChoiceQuizPanel } from "@/components/layout/quiz";
|
||||
import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene3`;
|
||||
@@ -70,6 +71,9 @@ const SunzhongshanReflectionScene3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
// 获取碎片动画触发方法
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
@@ -488,6 +492,7 @@ const SunzhongshanReflectionScene3 = () => {
|
||||
setShowQuiz3(true);
|
||||
setPendingQuiz(null);
|
||||
} else if (pendingQuiz === "complete") {
|
||||
triggerPuzzleAnimation(14);
|
||||
setShowCompleteGuide(true);
|
||||
setPendingQuiz(null);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user