feat:添加孙中山场景
This commit is contained in:
1 parent
bf4a98bf9f
commit
cad38e43df
55 files changed
+1704
-44
No files matched your search
@@ -1,38 +0,0 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Image } from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
const SunzhongshanReflection = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: getBackgroundImage(`${starName}/reflection/底图`),
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
className="absolute right-5 top-5"
|
||||
src={getStaticImage("components/离开")}
|
||||
button
|
||||
onClick={() => {
|
||||
if (moduleName && starName) {
|
||||
navigate(`/${moduleName}/${starName}/home`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-white text-4xl">孙中山故居-体悟模块(待完善)</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SunzhongshanReflection;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Image } from "@/components";
|
||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||
|
||||
const starName = "sunzhongshan";
|
||||
|
||||
const SunzhongshanReflection = () => {
|
||||
const navigate = useNavigate();
|
||||
const { moduleName } = useParams<{
|
||||
moduleName: string;
|
||||
}>();
|
||||
|
||||
const [selectedScene, setSelectedScene] = useState<number | null>(null);
|
||||
|
||||
const handleSceneClick = (sceneNumber: number) => {
|
||||
setSelectedScene(sceneNumber);
|
||||
};
|
||||
|
||||
const handleStartExplore = () => {
|
||||
if (selectedScene && moduleName && starName) {
|
||||
navigate(`/${moduleName}/${starName}/reflection${selectedScene}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-full bg-cover bg-center bg-no-repeat relative"
|
||||
style={{
|
||||
backgroundImage: getBackgroundImage(`${starName}/reflection/home/背景`),
|
||||
}}
|
||||
>
|
||||
{/* 关闭按钮 */}
|
||||
<Image
|
||||
className="absolute right-0 top-0 z-10"
|
||||
src={getStaticImage(`${starName}/reflection/关闭`)}
|
||||
button
|
||||
onClick={() => {
|
||||
if (moduleName && starName) {
|
||||
navigate(`/${moduleName}/${starName}/home`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 场景选择区域 */}
|
||||
<div className="absolute top-[46%] left-1/2 -translate-x-1/2 -translate-y-1/2 w-full flex items-center justify-center px-26">
|
||||
{/* 场景一 */}
|
||||
<div
|
||||
className={`relative cursor-pointer transition-all duration-300 ${
|
||||
selectedScene === 1 ? "z-30" : "z-10"
|
||||
}`}
|
||||
style={{
|
||||
transform: `rotate(0deg) ${
|
||||
selectedScene === 1 ? "scale(1.15)" : "scale(1)"
|
||||
}`,
|
||||
}}
|
||||
onClick={() => handleSceneClick(1)}
|
||||
>
|
||||
<div className="p-3">
|
||||
<Image src={getStaticImage(`${starName}/reflection/home/场景1`)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 场景二 */}
|
||||
<div
|
||||
className={`relative cursor-pointer transition-all duration-300 ${
|
||||
selectedScene === 2 ? "z-30" : "z-20"
|
||||
}`}
|
||||
style={{
|
||||
transform: `rotate(0deg) ${
|
||||
selectedScene === 2 ? "scale(1.15)" : "scale(1)"
|
||||
}`,
|
||||
}}
|
||||
onClick={() => handleSceneClick(2)}
|
||||
>
|
||||
<div className="p-3">
|
||||
<Image src={getStaticImage(`${starName}/reflection/home/场景2`)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 场景三 */}
|
||||
<div
|
||||
className={`relative cursor-pointer transition-all duration-300 ${
|
||||
selectedScene === 3 ? "z-30" : "z-10"
|
||||
}`}
|
||||
style={{
|
||||
transform: `rotate(0deg) ${
|
||||
selectedScene === 3 ? "scale(1.15)" : "scale(1)"
|
||||
}`,
|
||||
}}
|
||||
onClick={() => handleSceneClick(3)}
|
||||
>
|
||||
<div className="p-3">
|
||||
<Image src={getStaticImage(`${starName}/reflection/home/场景3`)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 开始探索按钮 */}
|
||||
<div className="absolute bottom-30 left-1/2 -translate-x-1/2 z-10">
|
||||
<div
|
||||
className={`transition-all duration-300 ${
|
||||
selectedScene
|
||||
? "opacity-100 cursor-pointer hover:scale-105"
|
||||
: "opacity-50 cursor-not-allowed"
|
||||
}`}
|
||||
onClick={selectedScene ? handleStartExplore : undefined}
|
||||
>
|
||||
<Image
|
||||
src={getStaticImage(`${starName}/reflection/home/按钮`)}
|
||||
button={!!selectedScene}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SunzhongshanReflection;
|
||||
@@ -0,0 +1,310 @@
|
||||
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/scene1`;
|
||||
|
||||
// 线索配置 - 根据实际图片调整位置
|
||||
const clueConfigs: ClueHint[] = [
|
||||
{
|
||||
id: `${sceneId}/clue1`,
|
||||
name: "线索1",
|
||||
position: {
|
||||
top: "75%",
|
||||
left: "95.5%",
|
||||
width: "0%",
|
||||
height: "0%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue2`,
|
||||
name: "线索2",
|
||||
position: {
|
||||
top: "2%",
|
||||
left: "34%",
|
||||
width: "56%",
|
||||
height: "50%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue3`,
|
||||
name: "线索3",
|
||||
position: {
|
||||
top: "54%",
|
||||
left: "20%",
|
||||
width: "7%",
|
||||
height: "34%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue4`,
|
||||
name: "线索4",
|
||||
position: {
|
||||
top: "53%",
|
||||
left: "48.5%",
|
||||
width: "7%",
|
||||
height: "34%",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const SunzhongshanReflectionScene1 = () => {
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理背包点击 - 先收集线索1,然后打开线索提示框
|
||||
const handleBackpackClick = () => {
|
||||
const clue1Id = `${sceneId}/clue1`;
|
||||
const clue1Name = "线索1";
|
||||
|
||||
// 如果线索1已收集,直接打开线索提示框
|
||||
if (isClueCollected(clue1Id)) {
|
||||
setShowClueHint(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集线索1
|
||||
collectClue(clue1Id, clue1Name);
|
||||
|
||||
// 获取线索1的 ref
|
||||
const clue1Ref = getClueRef(clue1Id);
|
||||
|
||||
// 执行飞行动画
|
||||
if (clue1Ref.current && clueBookButtonRef.current) {
|
||||
flyToAnimation({
|
||||
fromRef: clue1Ref as React.RefObject<HTMLElement>,
|
||||
toRef: clueBookButtonRef as React.RefObject<HTMLElement>,
|
||||
targetPosition: "center",
|
||||
duration: 1200,
|
||||
onComplete: () => {
|
||||
// 动画完成后,打开线索提示框
|
||||
// setShowClueHint(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 如果 ref 不可用,直接打开线索提示框
|
||||
setShowClueHint(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/scene1/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={handleBackpackClick}
|
||||
/>
|
||||
<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/scene1/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 SunzhongshanReflectionScene1;
|
||||
@@ -0,0 +1,324 @@
|
||||
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/scene2`;
|
||||
|
||||
// 线索配置 - 根据实际图片调整位置
|
||||
const clueConfigs: ClueHint[] = [
|
||||
{
|
||||
id: `${sceneId}/clue1`,
|
||||
name: "线索1",
|
||||
position: {
|
||||
top: "75%",
|
||||
left: "95.5%",
|
||||
width: "0%",
|
||||
height: "0%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue2`,
|
||||
name: "线索2",
|
||||
position: {
|
||||
top: "42%",
|
||||
left: "26%",
|
||||
width: "18%",
|
||||
height: "22%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue3`,
|
||||
name: "线索3",
|
||||
position: {
|
||||
top: "30%",
|
||||
left: "6%",
|
||||
width: "10%",
|
||||
height: "22%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue4`,
|
||||
name: "线索4",
|
||||
position: {
|
||||
top: "67%",
|
||||
left: "8%",
|
||||
width: "9%",
|
||||
height: "7%",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: `${sceneId}/clue5`,
|
||||
name: "线索5",
|
||||
position: {
|
||||
top: "60%",
|
||||
left: "44%",
|
||||
width: "42%",
|
||||
height: "30%",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const SunzhongshanReflectionScene2 = () => {
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理背包点击 - 先收集线索1,然后打开线索提示框
|
||||
const handleBackpackClick = () => {
|
||||
const clue1Id = `${sceneId}/clue1`;
|
||||
const clue1Name = "线索1";
|
||||
|
||||
// 如果线索1已收集,直接打开线索提示框
|
||||
if (isClueCollected(clue1Id)) {
|
||||
setShowClueHint(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集线索1
|
||||
collectClue(clue1Id, clue1Name);
|
||||
|
||||
// 获取线索1的 ref
|
||||
const clue1Ref = getClueRef(clue1Id);
|
||||
|
||||
// 执行飞行动画
|
||||
if (clue1Ref.current && clueBookButtonRef.current) {
|
||||
flyToAnimation({
|
||||
fromRef: clue1Ref as React.RefObject<HTMLElement>,
|
||||
toRef: clueBookButtonRef as React.RefObject<HTMLElement>,
|
||||
targetPosition: "center",
|
||||
duration: 1200,
|
||||
onComplete: () => {
|
||||
// 动画完成后,打开线索提示框
|
||||
setShowClueHint(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 如果 ref 不可用,直接打开线索提示框
|
||||
setShowClueHint(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];
|
||||
// 线索3没有详情图片,返回线索图片
|
||||
if (clueNumber === "3") {
|
||||
return getStaticImage(`${sceneId}/线索${clueNumber}`);
|
||||
}
|
||||
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/scene2/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={handleBackpackClick}
|
||||
/>
|
||||
<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/scene2/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 SunzhongshanReflectionScene2;
|
||||
@@ -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