feat:添加孙中山场景
This commit is contained in:
1 parent
bf4a98bf9f
commit
cad38e43df
55 files changed
+1704
-44
No files matched your search
@@ -0,0 +1,275 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
Image,
|
||||
ClueHintModal,
|
||||
ClueBookModal,
|
||||
flyToAnimation,
|
||||
} from "@/components";
|
||||
import { SuccessAlert } from "@/components/layout";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
import { useClueStore } from "@/store/clueStore";
|
||||
import type { ClueHint } from "@/components/clue";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
const sceneId = `${starName}/reflection/scene3`;
|
||||
|
||||
// 线索配置 - 根据实际图片调整位置
|
||||
const clueConfigs: ClueHint[] = [
|
||||
{
|
||||
id: `${sceneId}/clue1`,
|
||||
name: "线索1",
|
||||
position: {
|
||||
top: "20%",
|
||||
left: "65%",
|
||||
width: "0%",
|
||||
height: "0%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue2`,
|
||||
name: "线索2",
|
||||
position: {
|
||||
top: "26%",
|
||||
left: "43%",
|
||||
width: "40%",
|
||||
height: "26%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue3`,
|
||||
name: "线索3",
|
||||
position: {
|
||||
top: "44%",
|
||||
left: "10%",
|
||||
width: "85%",
|
||||
height: "50%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue4`,
|
||||
name: "线索4",
|
||||
position: {
|
||||
top: "30%",
|
||||
left: "40%",
|
||||
width: "8%",
|
||||
height: "12%",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const SunzhongshanReflectionScene3 = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [showClueHint, setShowClueHint] = useState(false);
|
||||
const [showClueBook, setShowClueBook] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
const [successAlertContent, setSuccessAlertContent] = useState("");
|
||||
|
||||
const { collectClue, isClueCollected } = useClueStore();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const clueBookButtonRef = useRef<HTMLDivElement>(null);
|
||||
// 为每个线索创建 ref
|
||||
const clueRefs = useRef<Map<string, React.RefObject<HTMLDivElement | null>>>(
|
||||
new Map()
|
||||
);
|
||||
|
||||
// 获取或创建线索的 ref
|
||||
const getClueRef = (
|
||||
clueId: string
|
||||
): React.RefObject<HTMLDivElement | null> => {
|
||||
if (!clueRefs.current.has(clueId)) {
|
||||
clueRefs.current.set(clueId, React.createRef<HTMLDivElement>());
|
||||
}
|
||||
return clueRefs.current.get(clueId)!;
|
||||
};
|
||||
|
||||
// 处理线索点击
|
||||
const handleClueClick = (clueId: string, clueName: string) => {
|
||||
// 如果已收集,不重复收集
|
||||
if (isClueCollected(clueId)) {
|
||||
setSuccessAlertContent("该线索已收集");
|
||||
setShowSuccessAlert(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集线索
|
||||
collectClue(clueId, clueName);
|
||||
|
||||
// 获取线索元素的 ref
|
||||
const clueRef = getClueRef(clueId);
|
||||
|
||||
// 使用函数式 API 触发飞行动画
|
||||
if (clueRef.current && clueBookButtonRef.current) {
|
||||
flyToAnimation({
|
||||
fromRef: clueRef as React.RefObject<HTMLElement>,
|
||||
toRef: clueBookButtonRef as React.RefObject<HTMLElement>,
|
||||
targetPosition: "center",
|
||||
duration: 1200,
|
||||
onComplete: () => {
|
||||
// 动画完成后的回调
|
||||
setSuccessAlertContent("恭喜您,获得该线索!");
|
||||
setShowSuccessAlert(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 获取线索图片
|
||||
const getClueImage = (clueId: string) => {
|
||||
const clueNumber = clueId.split("clue")[1];
|
||||
return getStaticImage(`${sceneId}/线索${clueNumber}`);
|
||||
};
|
||||
|
||||
// 获取线索详情图片
|
||||
const getClueDetailImage = (clueId: string) => {
|
||||
const clueNumber = clueId.split("clue")[1];
|
||||
return getStaticImage(`${sceneId}/线索${clueNumber}-详情`);
|
||||
};
|
||||
|
||||
// 初始化线索(如果还没有)
|
||||
useEffect(() => {
|
||||
clueConfigs.forEach((clue) => {
|
||||
if (!isClueCollected(clue.id)) {
|
||||
// 不自动收集,只是确保线索存在
|
||||
}
|
||||
});
|
||||
}, [isClueCollected]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-full bg-cover bg-center bg-no-repeat relative"
|
||||
style={{
|
||||
backgroundImage: getBackgroundImage(`${starName}/reflection/scene3/bg`),
|
||||
}}
|
||||
>
|
||||
{/* 关闭按钮 */}
|
||||
<Image
|
||||
className="absolute right-0 top-0 z-10"
|
||||
src={getStaticImage(`${starName}/reflection/关闭`)}
|
||||
button
|
||||
onClick={() => {
|
||||
if (moduleName && starName) {
|
||||
navigate(`/${moduleName}/${starName}/reflection`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 线索点击区域 */}
|
||||
{clueConfigs.map((clue) => {
|
||||
const isCollected = isClueCollected(clue.id);
|
||||
const clueRef = getClueRef(clue.id);
|
||||
return (
|
||||
<div key={clue.id}>
|
||||
{/* 线索元素(用于克隆和飞行) */}
|
||||
<div
|
||||
ref={clueRef}
|
||||
className="absolute pointer-events-none opacity-0"
|
||||
style={{
|
||||
top: clue.position.top,
|
||||
left: clue.position.left,
|
||||
width: clue.position.width,
|
||||
height: clue.position.height,
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
<Image
|
||||
src={getClueImage(clue.id)}
|
||||
className="w-20 h-20 border-2 border-yellow-400 rounded shadow-lg"
|
||||
/>
|
||||
<div className="absolute -bottom-6 left-1/2 -translate-x-1/2 bg-yellow-400 text-black px-2 py-1 rounded text-xs font-bold whitespace-nowrap">
|
||||
{clue.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 点击区域 */}
|
||||
<div
|
||||
className={`
|
||||
absolute cursor-pointer transition-all duration-200
|
||||
${
|
||||
isCollected
|
||||
? "opacity-30 hover:opacity-50"
|
||||
: "opacity-0 hover:opacity-20"
|
||||
}
|
||||
hover:bg-yellow-400/20
|
||||
`}
|
||||
style={{
|
||||
top: clue.position.top,
|
||||
left: clue.position.left,
|
||||
width: clue.position.width,
|
||||
height: clue.position.height,
|
||||
}}
|
||||
onClick={() => handleClueClick(clue.id, clue.name)}
|
||||
title={
|
||||
isCollected ? `${clue.name}(已收集)` : `点击收集${clue.name}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 右下角-功能处 */}
|
||||
<div className="absolute right-6 bottom-12 flex flex-col items-center gap-4 z-10">
|
||||
<Image
|
||||
src={getStaticImage(`${starName}/reflection/背包`)}
|
||||
button
|
||||
onClick={() => setShowClueHint(true)}
|
||||
/>
|
||||
<div
|
||||
ref={clueBookButtonRef}
|
||||
className="relative"
|
||||
data-clue-book-button="true"
|
||||
>
|
||||
<Image
|
||||
src={getStaticImage(`${starName}/reflection/线索簿`)}
|
||||
button
|
||||
onClick={() => setShowClueBook(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 线索提示窗 */}
|
||||
<ClueHintModal
|
||||
visible={showClueHint}
|
||||
backgroundImage={getStaticImage(`${starName}/reflection/scene3/bg`)}
|
||||
clues={clueConfigs}
|
||||
onClose={() => setShowClueHint(false)}
|
||||
sourceContainerRef={containerRef}
|
||||
/>
|
||||
|
||||
{/* 线索簿窗口 */}
|
||||
<ClueBookModal
|
||||
visible={showClueBook}
|
||||
clues={clueConfigs.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
collected: isClueCollected(c.id),
|
||||
}))}
|
||||
onClose={() => setShowClueBook(false)}
|
||||
getClueImage={getClueImage}
|
||||
getClueDetailImage={getClueDetailImage}
|
||||
/>
|
||||
|
||||
{/* 成功提示弹窗 */}
|
||||
<SuccessAlert
|
||||
visible={showSuccessAlert}
|
||||
title="恭喜你!"
|
||||
content={successAlertContent}
|
||||
onContinue={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setSuccessAlertContent("");
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowSuccessAlert(false);
|
||||
setSuccessAlertContent("");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SunzhongshanReflectionScene3;
|
||||
Reference in new issue
Block a user