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
@@ -10,6 +10,8 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import Cursor from "@/components/cursor";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
// starName 固定为 huangyanpei
|
||||
const starName = "huangyanpei";
|
||||
@@ -25,6 +27,8 @@ const HuangyanpeiPlace1 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
// 学习引导弹窗
|
||||
const [showGuideModal, setShowGuideModal] = useState(true);
|
||||
// 对话结束后的学习引导弹窗
|
||||
@@ -34,8 +38,14 @@ const HuangyanpeiPlace1 = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
// 当前激活的展品 ID(只存 ID,不直接存路径,便于作为唯一标识)
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
// 已浏览展品的 ID 集合
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place1";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const handleOpenExhibit = (index: 1 | null) => {
|
||||
setActiveExhibitId(index);
|
||||
@@ -100,13 +110,14 @@ const HuangyanpeiPlace1 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(1);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -24,12 +26,22 @@ const HuangyanpeiPlace2 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place2";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [1],
|
||||
dependencies: { 2: [1] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// GuideSteps 当前步骤
|
||||
@@ -116,13 +128,14 @@ const HuangyanpeiPlace2 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(2);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { SuccessAlert } from "@/components/layout";
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -27,14 +29,24 @@ const HuangyanpeiPlace3 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | 3 | null>(
|
||||
null
|
||||
);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place3";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [3],
|
||||
dependencies: { 2: [3], 1: [2] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// 光标3的引导对话状态
|
||||
@@ -158,6 +170,17 @@ const HuangyanpeiPlace3 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 3) {
|
||||
triggerPuzzleAnimation(3);
|
||||
}
|
||||
if (activeExhibitId === 2) {
|
||||
triggerPuzzleAnimation(4);
|
||||
}
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(5);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setActiveExhibitId(null);
|
||||
@@ -165,11 +188,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
setShowQuiz3(true);
|
||||
return;
|
||||
}
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
} else {
|
||||
setActiveExhibitId(null);
|
||||
}
|
||||
@@ -229,11 +248,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
visible={showSuccessAlert}
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(3);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 3);
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
@@ -244,7 +259,7 @@ const HuangyanpeiPlace3 = () => {
|
||||
<GuideSteps
|
||||
visible={guide12Visible}
|
||||
onVisibleChange={setGuide12Visible}
|
||||
showMask={true}
|
||||
showMask={false}
|
||||
zIndex={10000}
|
||||
>
|
||||
<GuideStepsItem
|
||||
|
||||
@@ -14,11 +14,13 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
const exhibitSrcMap: Record<1 | 2 | 3 | 4 | 5, string> = {
|
||||
1: "huangyanpei/place4/6_006",
|
||||
1: "huangyanpei/place4/6_6&7",
|
||||
2: "huangyanpei/place4/6_007",
|
||||
3: "huangyanpei/place4/6_008",
|
||||
4: "huangyanpei/place4/6_009",
|
||||
@@ -31,10 +33,19 @@ const HuangyanpeiPlace4 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 3 | 4 | 5 | null>(
|
||||
null
|
||||
);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place4";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [3, 5],
|
||||
dependencies: {
|
||||
@@ -42,7 +53,7 @@ const HuangyanpeiPlace4 = () => {
|
||||
4: [1],
|
||||
// 2 已注释,不计入顺序
|
||||
},
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
// 光标1的引导对话状态
|
||||
@@ -187,13 +198,23 @@ const HuangyanpeiPlace4 = () => {
|
||||
// : undefined
|
||||
// }
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 3) {
|
||||
triggerPuzzleAnimation(7);
|
||||
}
|
||||
if (activeExhibitId === 5) {
|
||||
triggerPuzzleAnimation(6);
|
||||
}
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(8);
|
||||
}
|
||||
if (activeExhibitId === 4) {
|
||||
triggerPuzzleAnimation(10);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
@@ -205,6 +226,9 @@ const HuangyanpeiPlace4 = () => {
|
||||
currentStep={guide1Step}
|
||||
onStepChange={setGuide1Step}
|
||||
onVisibleChange={(visible) => {
|
||||
if (!visible) {
|
||||
triggerPuzzleAnimation(9);
|
||||
}
|
||||
setGuide1Visible(visible);
|
||||
}}
|
||||
showMask={false}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { getBackgroundImage, getStaticImage, getPublicPath } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import type { VideoRef } from "@/components/video";
|
||||
import { useCursorController } from "@/hooks/useCursorController";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -28,8 +30,9 @@ const HuangyanpeiPlace5 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | 2 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
const [videoVisible, setVideoVisible] = useState(false);
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
// 光标1的引导对话状态
|
||||
@@ -43,11 +46,19 @@ const HuangyanpeiPlace5 = () => {
|
||||
// 成功提示状态
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place5";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
// 光标顺序:1 -> 2(需完成1后解锁2)
|
||||
const { isEnabled } = useCursorController({
|
||||
initialEnabled: [1],
|
||||
dependencies: { 2: [1] },
|
||||
visited,
|
||||
visited: visitedExhibits, // 传入数组,而不是 Set
|
||||
});
|
||||
|
||||
const handleOpenExhibit = (index: 1 | 2) => {
|
||||
@@ -159,13 +170,14 @@ const HuangyanpeiPlace5 = () => {
|
||||
// : undefined
|
||||
// }
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(11);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
}}
|
||||
@@ -268,11 +280,8 @@ const HuangyanpeiPlace5 = () => {
|
||||
visible={showSuccessAlert}
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(2);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, 2);
|
||||
triggerPuzzleAnimation(12);
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage } from "@/lib/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { EndingOverlay } from "@/components/overlay/EndingOverlay";
|
||||
import { usePuzzleAnimation } from "@/contexts/PuzzleAnimationContext";
|
||||
import { useHuangyanpeiStore } from "@/store/huangyanpeiStore";
|
||||
|
||||
const starName = "huangyanpei";
|
||||
|
||||
@@ -23,8 +25,9 @@ const HuangyanpeiPlace6 = () => {
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation();
|
||||
|
||||
const [activeExhibitId, setActiveExhibitId] = useState<1 | null>(null);
|
||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
||||
// 对话引导状态
|
||||
const [guideVisible, setGuideVisible] = useState(false);
|
||||
// 结束窗口状态
|
||||
@@ -32,6 +35,14 @@ const HuangyanpeiPlace6 = () => {
|
||||
// 是否已经显示过结束窗口(防止重复弹出)
|
||||
const [hasShownEnding, setHasShownEnding] = useState(false);
|
||||
|
||||
// 使用持久化的访问状态
|
||||
const place = "place6";
|
||||
const { markExhibitVisited, getVisitedExhibits } = useHuangyanpeiStore();
|
||||
|
||||
// 从 store 获取已访问的展品,转换为 Set 用于兼容现有代码
|
||||
const visitedExhibits = getVisitedExhibits(place);
|
||||
const visited = new Set(visitedExhibits);
|
||||
|
||||
const [Leave, setLeave] = useState(false);
|
||||
|
||||
const handleOpenExhibit = (index: 1) => {
|
||||
@@ -41,16 +52,14 @@ const HuangyanpeiPlace6 = () => {
|
||||
};
|
||||
|
||||
// 当 cursor 已浏览后,显示结束窗口(只显示一次)
|
||||
useEffect(() => {
|
||||
if (visited.has(1) && !hasShownEnding && !endingVisible) {
|
||||
// 延迟一下显示,确保展品关闭动画完成
|
||||
const timer = setTimeout(() => {
|
||||
setEndingVisible(true);
|
||||
setHasShownEnding(true);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [visited, hasShownEnding, endingVisible]);
|
||||
// useEffect(() => {
|
||||
// if (visited.has(1) && !hasShownEnding && !endingVisible) {
|
||||
// // 延迟一下显示,确保展品关闭动画完成
|
||||
// const timer = setTimeout(() => {
|
||||
// }, 500);
|
||||
// return () => clearTimeout(timer);
|
||||
// }
|
||||
// }, [visited, hasShownEnding, endingVisible]);
|
||||
|
||||
const progressCount = visited.size;
|
||||
const total = 1;
|
||||
@@ -107,15 +116,19 @@ const HuangyanpeiPlace6 = () => {
|
||||
<ExhibitOverlay
|
||||
visible={activeExhibitId !== null}
|
||||
src={activeExhibitId ? exhibitSrcMap[activeExhibitId] : ""}
|
||||
onOpen={() => {
|
||||
if (activeExhibitId === 1) {
|
||||
triggerPuzzleAnimation(13);
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
if (activeExhibitId !== null) {
|
||||
setVisited((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(activeExhibitId);
|
||||
return next;
|
||||
});
|
||||
markExhibitVisited(place, activeExhibitId);
|
||||
}
|
||||
setActiveExhibitId(null);
|
||||
|
||||
setEndingVisible(true);
|
||||
setHasShownEnding(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in new issue
Block a user