feat: remove LuxunReflection component and update routing for new reflection scenes

- Deleted the LuxunReflection component.
- Updated routing to include new reflection scenes: reflection2 and reflection3.
This commit is contained in:
zhongyidian committed 2026-01-03 20:26:46 +08:00
1 parent cad38e43df
commit dd1cabfb87
49 files changed
+522 -39

No files matched your search

+265
View File
@@ -0,0 +1,265 @@
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { createPortal } from "react-dom";
import { AnimatePresence, motion } from "framer-motion";
import { Title, Image, Cursor, DragFill } from "@/components";
import type { DragFillResult } from "@/components";
import { cn, getBackgroundImage, getStaticImage } from "@/lib/utils";
import { useScale } from "@/contexts/ScaleContext";
const starName = "luxun";
// 文学作品拖拽选项
const literatureOptions = [
{ value: "朝花夕拾", label: "朝花夕拾", image: "luxun/reflection/scene2/拖拽-朝花夕拾" },
{ value: "阿Q正传", label: "阿Q正传", image: "luxun/reflection/scene2/拖拽-阿Q正传" },
{ value: "故事新编", label: "故事新编", image: "luxun/reflection/scene2/拖拽-故事新编" },
{ value: "百草园到三味书屋", label: "百草园到三味书屋", image: "luxun/reflection/scene2/拖拽-百草园到三味书屋" },
{ value: "精神胜利法", label: "精神胜利法", image: "luxun/reflection/scene2/拖拽-精神胜利法" },
{ value: "润土", label: "润土", image: "luxun/reflection/scene2/拖拽-润土" },
];
// 目标位置
const targetBlanks = [
{ id: "blank1", label: "位置1" },
{ id: "blank2", label: "位置2" },
{ id: "blank3", label: "位置3" },
];
const LuxunReflectionScene2 = () => {
const navigate = useNavigate();
const { moduleName } = useParams<{
moduleName: string;
}>();
const [showDialog, setShowDialog] = useState(false);
const [visited, setVisited] = useState<Set<number>>(new Set());
const [dragValue, setDragValue] = useState<DragFillResult[]>([]);
const completedRef = useRef(false);
const { scale, offsetX, offsetY, DESIGN_WIDTH, DESIGN_HEIGHT } = useScale();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// 重置游戏状态
useEffect(() => {
if (showDialog) {
setDragValue([]);
completedRef.current = false;
}
}, [showDialog]);
// 检查是否完成
useEffect(() => {
if (dragValue.length === 3 && !completedRef.current) {
completedRef.current = true;
setTimeout(() => {
setVisited((prev) => {
const next = new Set(prev);
next.add(1);
return next;
});
setShowDialog(false);
}, 500);
}
}, [dragValue]);
const progressCount = visited.size;
const total = 1;
const completed = progressCount >= total;
const subtitle = completed
? "探索文学作品·精神传承(已完成)"
: `探索文学作品·精神传承(${progressCount}/${total})`;
const subtitleClassName = completed
? "text-[#fff998] [text-shadow:_0_8px_8px_rgba(0,0,0,0.35),_0_0_16px_rgba(0,0,0,0.25)]"
: undefined;
const handleValueChange = useCallback((newValue: DragFillResult[]) => {
if (newValue.length <= 3) {
setDragValue(newValue);
}
}, []);
const maskSrc = getStaticImage("components/蒙版遮罩");
const backSrc = getStaticImage("components/关闭");
const overlayStyle = useMemo<React.CSSProperties>(
() => ({
position: "fixed",
inset: 0,
zIndex: 9999,
pointerEvents: "none",
}),
[]
);
const scaledContainerStyle = useMemo<React.CSSProperties>(
() => ({
position: "absolute",
width: `${DESIGN_WIDTH}px`,
height: `${DESIGN_HEIGHT}px`,
left: `${offsetX}px`,
top: `${offsetY}px`,
transform: `scale(${scale})`,
transformOrigin: "top left",
overflow: "hidden",
}),
[scale, offsetX, offsetY, DESIGN_WIDTH, DESIGN_HEIGHT]
);
return (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: getBackgroundImage(`${starName}/reflection/scene2/底图`),
}}
>
<Title
title="跨越时空"
subtitle={subtitle}
subtitleClassName={subtitleClassName}
className="absolute top-10 left-1/2 -translate-x-1/2"
/>
<Image
className="absolute right-5 top-5"
src={getStaticImage(`${starName}/reflection/scene2/离开`)}
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/home`);
}
}}
/>
{/* 点击区域 - 打开拖拽弹窗 */}
<Cursor
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
onClick={() => setShowDialog(true)}
read={visited.has(1)}
/>
{completed && (
<Image
className="absolute bottom-0 right-0"
src={getStaticImage("components/place/继续前进")}
button
onClick={() => {
if (moduleName && starName) {
navigate(`/${moduleName}/${starName}/reflection3`);
}
}}
/>
)}
{/* 拖拽弹窗 */}
{mounted &&
createPortal(
<AnimatePresence>
{showDialog && (
<motion.div
key="literature-drag-dialog"
style={overlayStyle}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
>
<div style={scaledContainerStyle}>
<div
className={cn("absolute inset-0 flex flex-col", "bg-black/70")}
style={{ pointerEvents: "auto" }}
>
{/* 背景蒙版 */}
{maskSrc && (
<Image
src={maskSrc}
className="absolute inset-0 w-full h-full object-cover select-none pointer-events-none"
aria-hidden
/>
)}
{/* 返回按钮 */}
<Image
src={backSrc}
onClick={() => setShowDialog(false)}
alt="返回"
className="absolute top-5 right-5 z-20"
button
/>
{/* 内容区域 */}
<div className="relative z-10 w-full h-full flex items-center justify-center px-30 py-16 pt-30">
<div className="w-full max-w-6xl">
<div className="text-5xl font-bold text-white mb-8 text-center">
将文学作品拖拽到对应位置
</div>
<DragFill
options={literatureOptions.map((opt) => ({
value: opt.value,
label: opt.label,
}))}
blanks={targetBlanks}
value={dragValue}
onChange={handleValueChange}
allowDuplicate={false}
renderOption={(option) => {
const opt = literatureOptions.find(
(o) => o.value === option.value
);
return (
<div className="flex flex-col items-center gap-2">
<Image
src={getStaticImage(opt?.image || "")}
className="w-32 h-32 object-contain"
/>
<span className="text-2xl text-white">{option.label}</span>
</div>
);
}}
renderBlank={(blank, filledValue, isHovered) => {
const filledOption = filledValue
? literatureOptions.find((opt) => opt.value === filledValue)
: null;
return (
<div
className={cn(
"w-48 h-48 border-2 rounded-lg flex flex-col items-center justify-center gap-2 transition-all",
filledValue
? "border-white bg-white/20"
: "border-dashed border-white/50 bg-white/10",
isHovered && "border-white bg-white/30"
)}
>
{filledOption ? (
<>
<Image
src={getStaticImage(filledOption.image)}
className="w-24 h-24 object-contain"
/>
<span className="text-white text-lg">{filledOption.label}</span>
</>
) : (
<span className="text-white text-xl">{blank.label}</span>
)}
</div>
);
}}
/>
</div>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>,
document.body
)}
</div>
);
};
export default LuxunReflectionScene2;