feat: 鲁迅体悟场景1、场景2
This commit is contained in:
1 parent
dd1cabfb87
commit
3611a0857a
7 files changed
+680
-281
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 495 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
@@ -379,7 +379,7 @@ const BambooPuzzleGame = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
<div className="text-white/30 text-xs">
|
<div className="text-white/30 text-4xl">
|
||||||
拖拽立柱到这里
|
拖拽立柱到这里
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -432,7 +432,7 @@ const BambooPuzzleGame = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
<div className="text-white/30 text-xs">
|
<div className="text-white/30 text-4xl">
|
||||||
拖拽立柱到这里
|
拖拽立柱到这里
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -417,12 +417,13 @@ const DragFill = forwardRef<HTMLDivElement, DragFillProps>(
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
layout === "horizontal-options"
|
layout === "horizontal-options"
|
||||||
? "flex flex-row gap-6 flex-wrap w-full"
|
? optionClassName || "flex flex-row gap-6 flex-wrap w-full"
|
||||||
: cn(
|
: cn(
|
||||||
"grid gap-x-12 gap-y-16",
|
optionClassName,
|
||||||
normalizedOptions.length > 2
|
!optionClassName && "grid gap-x-12 gap-y-16",
|
||||||
|
!optionClassName && normalizedOptions.length > 2
|
||||||
? "grid-cols-2"
|
? "grid-cols-2"
|
||||||
: "grid-cols-1"
|
: !optionClassName && "grid-cols-1"
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -439,7 +440,8 @@ const DragFill = forwardRef<HTMLDivElement, DragFillProps>(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
renderOption && "select-none flex-1",
|
renderOption && "select-none",
|
||||||
|
!optionClassName && renderOption && "flex-1",
|
||||||
isDragging && "opacity-50"
|
isDragging && "opacity-50"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -151,3 +151,76 @@ button:not([disabled]){
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 鲁迅页面滚动条样式 - 古典文化风格 */
|
||||||
|
.luxun-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(212, 175, 55, 0.85) rgba(250, 245, 235, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxun-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxun-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(250, 245, 235, 0.06),
|
||||||
|
rgba(240, 235, 225, 0.04)
|
||||||
|
);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1.5px solid rgba(139, 115, 71, 0.2);
|
||||||
|
margin: 6px 2px;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 8px rgba(139, 115, 71, 0.08),
|
||||||
|
inset 0 0 2px rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxun-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(240, 190, 117, 0.95),
|
||||||
|
rgba(212, 175, 55, 0.95),
|
||||||
|
rgba(184, 134, 11, 0.95)
|
||||||
|
);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid rgba(250, 245, 235, 0.4);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 3px rgba(255, 255, 255, 0.25),
|
||||||
|
inset 0 -1px 2px rgba(139, 115, 71, 0.2),
|
||||||
|
0 2px 6px rgba(139, 115, 71, 0.25),
|
||||||
|
0 0 10px rgba(212, 175, 55, 0.15);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条滑块内部装饰线效果 */
|
||||||
|
.luxun-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 215, 140, 1),
|
||||||
|
rgba(240, 190, 117, 1),
|
||||||
|
rgba(212, 175, 55, 1)
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 4px rgba(255, 255, 255, 0.35),
|
||||||
|
inset 0 -1px 3px rgba(139, 115, 71, 0.25),
|
||||||
|
0 3px 8px rgba(139, 115, 71, 0.35),
|
||||||
|
0 0 14px rgba(212, 175, 55, 0.3);
|
||||||
|
border-color: rgba(250, 245, 235, 0.6);
|
||||||
|
transform: scaleX(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxun-scrollbar::-webkit-scrollbar-thumb:active {
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(184, 134, 11, 1),
|
||||||
|
rgba(139, 115, 71, 1),
|
||||||
|
rgba(107, 93, 71, 1)
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 2px 5px rgba(0, 0, 0, 0.4),
|
||||||
|
inset 0 -1px 2px rgba(139, 115, 71, 0.3),
|
||||||
|
0 1px 3px rgba(139, 115, 71, 0.3);
|
||||||
|
border-color: rgba(250, 245, 235, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,17 +1,69 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Image } from "@/components";
|
import { Image } from "@/components";
|
||||||
|
import SuccessAlert from "@/components/layout/success-alert";
|
||||||
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||||
|
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
|
|
||||||
const cityMap: Record<1 | 2 | 3 | 4 | 5, string> = {
|
// 城市配置 - 根据效果图调整位置
|
||||||
1: "luxun/reflection/scene1/绍兴",
|
// 布局:北京(上) -> 上海(中上) -> 绍兴(中) -> 广州(左下) -> 厦门(右下)
|
||||||
2: "luxun/reflection/scene1/北京",
|
const cityConfig = [
|
||||||
3: "luxun/reflection/scene1/厦门",
|
{
|
||||||
4: "luxun/reflection/scene1/广州",
|
id: 1,
|
||||||
5: "luxun/reflection/scene1/上海",
|
name: "绍兴",
|
||||||
};
|
location: "绍兴三味书屋",
|
||||||
|
imagePath: "luxun/reflection/scene1/绍兴",
|
||||||
|
position: { top: "58%", left: "60%" }, // 中间
|
||||||
|
iconPosition: { top: "0%", left: "0%" },
|
||||||
|
time: "1892年-1898年",
|
||||||
|
description: "少年鲁迅在此求学,为《从百草园到三味书屋》埋下创作种子。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "北京",
|
||||||
|
location: "北京八道湾胡同",
|
||||||
|
imagePath: "luxun/reflection/scene1/北京",
|
||||||
|
position: { top: "14%", left: "50%" }, // 顶部
|
||||||
|
iconPosition: { top: "43%", left: "0%" },
|
||||||
|
time: "1919-1923年",
|
||||||
|
description:
|
||||||
|
"鲁迅参与新文化运动的核心时期,《阿Q正传》等经典作品在此酝酿创作。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "厦门",
|
||||||
|
location: "厦门大学",
|
||||||
|
imagePath: "luxun/reflection/scene1/厦门",
|
||||||
|
position: { top: "80%", left: "70%" }, // 右下
|
||||||
|
iconPosition: { top: "16%", left: "40%" },
|
||||||
|
time: "1926年9-12月",
|
||||||
|
description:
|
||||||
|
"鲁迅应邀前往厦门大学任教,期间写下多篇批判现实的杂文,关注底层民众命运。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "广州",
|
||||||
|
location: "广州白云楼",
|
||||||
|
imagePath: "luxun/reflection/scene1/广州",
|
||||||
|
position: { top: "80%", left: "30%" }, // 左下
|
||||||
|
iconPosition: { top: "16%", left: "30%" },
|
||||||
|
time: "1927年3-9月",
|
||||||
|
description:
|
||||||
|
"先生在此整理旧作,思考民族觉醒之路,《朝花夕拾》部分篇章定稿于此。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "上海",
|
||||||
|
location: "上海鲁迅故居",
|
||||||
|
imagePath: "luxun/reflection/scene1/上海",
|
||||||
|
position: { top: "40%", left: "60%" }, // 中上
|
||||||
|
iconPosition: { top: "0%", left: "0%" },
|
||||||
|
time: "1933-1936年",
|
||||||
|
description:
|
||||||
|
"鲁迅晚年定居地,先生在此写下《故事新编》等作品,与进步文人并肩,以文字为刃批判黑暗。",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const LuxunReflectionScene1 = () => {
|
const LuxunReflectionScene1 = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -19,17 +71,50 @@ const LuxunReflectionScene1 = () => {
|
|||||||
moduleName: string;
|
moduleName: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// 已点击的城市ID集合
|
||||||
|
const [clickedCities, setClickedCities] = useState<Set<number>>(new Set());
|
||||||
|
// 是否显示成功提示
|
||||||
|
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||||
|
// 是否全部完成
|
||||||
|
const [allCompleted, setAllCompleted] = useState(false);
|
||||||
|
|
||||||
|
const progress = clickedCities.size;
|
||||||
|
const totalTasks = cityConfig.length;
|
||||||
|
|
||||||
|
// 检查是否全部完成
|
||||||
|
useEffect(() => {
|
||||||
|
if (progress === totalTasks && !allCompleted) {
|
||||||
|
setAllCompleted(true);
|
||||||
|
setShowSuccessAlert(true);
|
||||||
|
}
|
||||||
|
}, [progress, totalTasks, allCompleted]);
|
||||||
|
|
||||||
|
const handleCityClick = (cityId: number) => {
|
||||||
|
if (clickedCities.has(cityId)) return; // 已点击过的不再处理
|
||||||
|
|
||||||
|
setClickedCities((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.add(cityId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuccessAlertClose = () => {
|
||||||
|
setShowSuccessAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex justify-between w-full h-full bg-cover bg-center bg-no-repeat gap-8"
|
className="flex justify-between w-full h-full bg-cover bg-center bg-no-repeat gap-8 relative"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: getBackgroundImage(
|
backgroundImage: getBackgroundImage(
|
||||||
`${starName}/reflection/scene1/底图`
|
`${starName}/reflection/scene1/底图`
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* 离开按钮 */}
|
||||||
<Image
|
<Image
|
||||||
className="absolute right-5 top-5"
|
className="absolute right-5 top-5 z-10"
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/离开`)}
|
src={getStaticImage(`${starName}/reflection/scene1/离开`)}
|
||||||
button
|
button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -39,60 +124,119 @@ const LuxunReflectionScene1 = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 左侧 */}
|
{/* 左侧:鲁迅的文学足迹 - 地图区域 */}
|
||||||
<div className="w-[45%] relative">
|
<div className="w-[45%] relative">
|
||||||
<div className="absolute">
|
{/* 地图容器 */}
|
||||||
<img
|
<div className="relative w-full h-full">
|
||||||
className="absolute inset-0"
|
{cityConfig.map((city) => {
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/北京`)}
|
const isClicked = clickedCities.has(city.id);
|
||||||
alt=""
|
return (
|
||||||
/>
|
<div
|
||||||
{/* 两个拖拽接收区域 */}
|
key={city.id}
|
||||||
<div className=""></div>
|
className="absolute flex flex-col items-center cursor-pointer hover:scale-110 transition-all duration-300"
|
||||||
<div className="border border-red-500"></div>
|
style={{
|
||||||
</div>
|
top: city.position.top,
|
||||||
<div className="relative">
|
left: city.position.left,
|
||||||
<img
|
transform: "translate(-50%, -50%)",
|
||||||
className="absolute inset-0"
|
}}
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/上海`)}
|
onClick={() => handleCityClick(city.id)}
|
||||||
alt=""
|
>
|
||||||
/>
|
{/* 城市图片容器 */}
|
||||||
</div>
|
<div className="relative">
|
||||||
<div>
|
<img
|
||||||
<img
|
src={getStaticImage(city.imagePath)}
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/绍兴`)}
|
alt={city.name}
|
||||||
alt=""
|
className={`transition-all duration-300 ${
|
||||||
/>
|
isClicked ? "brightness-100" : "brightness-50 grayscale"
|
||||||
</div>
|
}`}
|
||||||
<div>
|
style={{
|
||||||
<img
|
filter: isClicked
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/厦门`)}
|
? "none"
|
||||||
alt=""
|
: "grayscale(100%) brightness(0.5)",
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
<div>
|
|
||||||
<img
|
{/* 点击图标和提示 - 覆盖在城市图片上 */}
|
||||||
src={getStaticImage(`${starName}/reflection/scene1/广州`)}
|
<div
|
||||||
alt=""
|
className="absolute -translate-x-full -translate-y-full flex flex-col items-center z-10 pointer-events-none"
|
||||||
/>
|
style={{
|
||||||
|
left: city.iconPosition.left,
|
||||||
|
top: city.iconPosition.top,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 点一点提示 - 始终显示 */}
|
||||||
|
{!isClicked && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<img
|
||||||
|
src={getStaticImage(
|
||||||
|
`${starName}/reflection/scene1/鲁迅体悟改版点一点`
|
||||||
|
)}
|
||||||
|
alt="点一点"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="rotate-y-[180deg] rotate-z-[180deg]"
|
||||||
|
src={getStaticImage(
|
||||||
|
`${starName}/reflection/scene1/鲁迅体悟点一点图标`
|
||||||
|
)}
|
||||||
|
alt="点一点图标"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 右侧 */}
|
|
||||||
<div className="flex-1">
|
{/* 右侧区域 */}
|
||||||
|
<div className="flex-1 relative">
|
||||||
{/* 任务进度 */}
|
{/* 任务进度 */}
|
||||||
<div>
|
<div className="absolute top-5 left-[24%] w-[445px] h-55 flex justify-center items-center">
|
||||||
<span className="text-white text-2xl font-bold [text-shadow:_6px_6px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921]">
|
<span className="text-white text-[54px] font-bold [text-shadow:_0px_0px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921]">
|
||||||
任务进度:(0/8)
|
{allCompleted
|
||||||
|
? "任务进度已完成"
|
||||||
|
: `任务进度:(${progress}/${totalTasks})`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* 鲁迅画廊-拖拽区域 */}
|
|
||||||
<div></div>
|
|
||||||
{/* 任务展示窗口 */}
|
{/* 任务展示窗口 */}
|
||||||
<div className="flex justify-between">
|
<div className="absolute bottom-0 right-7 w-[995px] h-[728px] font-bold text-7xl space-y-16 px-10 py-10 overflow-y-auto luxun-scrollbar">
|
||||||
<div>任务展示窗口</div>
|
{allCompleted
|
||||||
<div></div>
|
? // 全部完成后显示详细信息
|
||||||
|
cityConfig.map((city) => (
|
||||||
|
<div key={city.id} className="space-y-4">
|
||||||
|
<div className="text-red-600 text-6xl">{city.location}</div>
|
||||||
|
<div className="text-gray-600 text-4xl font-normal">
|
||||||
|
{city.time}
|
||||||
|
</div>
|
||||||
|
<div className="text-black text-4xl font-normal leading-relaxed">
|
||||||
|
{city.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: // 未完成时显示任务列表
|
||||||
|
cityConfig.map((city, index) => {
|
||||||
|
const isCompleted = clickedCities.has(city.id);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={city.id}
|
||||||
|
className={isCompleted ? "text-green-600 line-through" : ""}
|
||||||
|
>
|
||||||
|
{index + 1}.点击图标{city.location}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 成功提示弹窗 */}
|
||||||
|
<SuccessAlert
|
||||||
|
visible={showSuccessAlert}
|
||||||
|
title="恭喜你, 完成所有任务!"
|
||||||
|
onClose={handleSuccessAlertClose}
|
||||||
|
onContinue={handleSuccessAlertClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,132 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { createPortal } from "react-dom";
|
import { Image, DragFill } from "@/components";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import { Title, Image, Cursor, DragFill } from "@/components";
|
|
||||||
import type { DragFillResult } from "@/components";
|
import type { DragFillResult } from "@/components";
|
||||||
import { cn, getBackgroundImage, getStaticImage } from "@/lib/utils";
|
import SuccessAlert from "@/components/layout/success-alert";
|
||||||
import { useScale } from "@/contexts/ScaleContext";
|
import { getBackgroundImage, getStaticImage } from "@/lib/utils";
|
||||||
|
|
||||||
const starName = "luxun";
|
const starName = "luxun";
|
||||||
|
|
||||||
// 文学作品拖拽选项
|
// 拖拽元素配置 - 每个元素对应一个特定的标签放置区域
|
||||||
const literatureOptions = [
|
const dragItems = [
|
||||||
{ value: "朝花夕拾", label: "朝花夕拾", image: "luxun/reflection/scene2/拖拽-朝花夕拾" },
|
{
|
||||||
{ value: "阿Q正传", label: "阿Q正传", image: "luxun/reflection/scene2/拖拽-阿Q正传" },
|
id: "runtu",
|
||||||
{ value: "故事新编", label: "故事新编", image: "luxun/reflection/scene2/拖拽-故事新编" },
|
name: "闰土",
|
||||||
{ value: "百草园到三味书屋", label: "百草园到三味书屋", image: "luxun/reflection/scene2/拖拽-百草园到三味书屋" },
|
imagePath: "luxun/reflection/scene2/拖拽-润土",
|
||||||
{ value: "精神胜利法", label: "精神胜利法", image: "luxun/reflection/scene2/拖拽-精神胜利法" },
|
targetBlankId: "city-1-label-0", // 绍兴-人物
|
||||||
{ value: "润土", label: "润土", image: "luxun/reflection/scene2/拖拽-润土" },
|
},
|
||||||
|
{
|
||||||
|
id: "baicaoyuan",
|
||||||
|
name: "《从百草园到三味书屋》",
|
||||||
|
imagePath: "luxun/reflection/scene2/拖拽-百草园到三味书屋",
|
||||||
|
targetBlankId: "city-1-label-1", // 绍兴-作品
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "aqzhengzhuan",
|
||||||
|
name: "《阿Q正传》",
|
||||||
|
imagePath: "luxun/reflection/scene2/拖拽-阿Q正传",
|
||||||
|
targetBlankId: "city-2-label-0", // 北京-作品
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "jingshenshengli",
|
||||||
|
name: "“精神胜利法”",
|
||||||
|
imagePath: "luxun/reflection/scene2/拖拽-精神胜利法",
|
||||||
|
targetBlankId: "city-2-label-1", // 北京-核心主题
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "guanzhudiceng",
|
||||||
|
name: "“关注底层”",
|
||||||
|
imagePath: "luxun/reflection/scene2/关注底层",
|
||||||
|
targetBlankId: "city-3-label-0", // 厦门-创作方向
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "zhaohuaxishi",
|
||||||
|
name: "《朝花夕拾》",
|
||||||
|
imagePath: "luxun/reflection/scene2/拖拽-朝花夕拾",
|
||||||
|
targetBlankId: "city-4-label-0", // 广州-作品集
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gushixinbian",
|
||||||
|
name: "《故事新编》",
|
||||||
|
imagePath: "luxun/reflection/scene2/拖拽-故事新编",
|
||||||
|
targetBlankId: "city-5-label-0", // 上海-作品
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "biweidaoqiang",
|
||||||
|
name: "“笔为刀枪”",
|
||||||
|
imagePath: "luxun/reflection/scene2/笔为刀枪",
|
||||||
|
targetBlankId: "city-5-label-1", // 上海-创作中心
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 目标位置
|
// 城市配置 - 根据效果图调整位置
|
||||||
const targetBlanks = [
|
// 布局:北京(上) -> 上海(中上) -> 绍兴(中) -> 广州(左下) -> 厦门(右下)
|
||||||
{ id: "blank1", label: "位置1" },
|
const cityConfig = [
|
||||||
{ id: "blank2", label: "位置2" },
|
{
|
||||||
{ id: "blank3", label: "位置3" },
|
id: 1,
|
||||||
|
name: "绍兴",
|
||||||
|
location: "绍兴三味书屋",
|
||||||
|
imagePath: "luxun/reflection/scene2/绍兴",
|
||||||
|
position: { top: "58%", left: "60%" }, // 中间
|
||||||
|
labels: [
|
||||||
|
{ text: "人物", containerPosition: { top: "0%", left: "-20%" } },
|
||||||
|
{ text: "作品", containerPosition: { top: "0%", left: "20%" } },
|
||||||
|
],
|
||||||
|
time: "1892年-1898年",
|
||||||
|
description: "少年鲁迅在此求学,为《从百草园到三味书屋》埋下创作种子。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "北京",
|
||||||
|
location: "北京八道湾胡同",
|
||||||
|
imagePath: "luxun/reflection/scene2/北京",
|
||||||
|
position: { top: "14%", left: "50%" }, // 顶部
|
||||||
|
labels: [
|
||||||
|
{ text: "作品", containerPosition: { top: "0%", left: "-20%" } },
|
||||||
|
{ text: "核心主题", containerPosition: { top: "0%", left: "20%" } },
|
||||||
|
],
|
||||||
|
time: "1919-1923年",
|
||||||
|
description:
|
||||||
|
"鲁迅参与新文化运动的核心时期,《阿Q正传》等经典作品在此酝酿创作。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "厦门",
|
||||||
|
location: "厦门大学",
|
||||||
|
imagePath: "luxun/reflection/scene2/厦门",
|
||||||
|
position: { top: "80%", left: "70%" }, // 右下
|
||||||
|
labels: [
|
||||||
|
{ text: "创作方向", containerPosition: { top: "0%", left: "0%" } },
|
||||||
|
],
|
||||||
|
time: "1926年9-12月",
|
||||||
|
description:
|
||||||
|
"鲁迅应邀前往厦门大学任教,期间写下多篇批判现实的杂文,关注底层民众命运。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "广州",
|
||||||
|
location: "广州白云楼",
|
||||||
|
imagePath: "luxun/reflection/scene2/广州",
|
||||||
|
position: { top: "80%", left: "30%" }, // 左下
|
||||||
|
labels: [{ text: "作品集", containerPosition: { top: "0%", left: "0%" } }],
|
||||||
|
time: "1927年3-9月",
|
||||||
|
description:
|
||||||
|
"先生在此整理旧作,思考民族觉醒之路,《朝花夕拾》部分篇章定稿于此。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "上海",
|
||||||
|
location: "上海鲁迅故居",
|
||||||
|
imagePath: "luxun/reflection/scene2/上海",
|
||||||
|
position: { top: "40%", left: "60%" }, // 中上
|
||||||
|
labels: [
|
||||||
|
{ text: "作品", containerPosition: { top: "0%", left: "-20%" } },
|
||||||
|
{ text: "创作中心", containerPosition: { top: "0%", left: "20%" } },
|
||||||
|
],
|
||||||
|
time: "1933-1936年",
|
||||||
|
description:
|
||||||
|
"鲁迅晚年定居地,先生在此写下《故事新编》等作品,与进步文人并肩,以文字为刃批判黑暗。",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const LuxunReflectionScene2 = () => {
|
const LuxunReflectionScene2 = () => {
|
||||||
@@ -32,100 +135,89 @@ const LuxunReflectionScene2 = () => {
|
|||||||
moduleName: string;
|
moduleName: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
// DragFill 的值
|
||||||
const [visited, setVisited] = useState<Set<number>>(new Set());
|
const [dragFillValue, setDragFillValue] = useState<DragFillResult[]>([]);
|
||||||
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);
|
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||||
}, []);
|
// 是否全部完成
|
||||||
|
const [allCompleted, setAllCompleted] = useState(false);
|
||||||
|
|
||||||
// 重置游戏状态
|
// 转换为 DragFill 的 options
|
||||||
useEffect(() => {
|
const options = useMemo(
|
||||||
if (showDialog) {
|
() =>
|
||||||
setDragValue([]);
|
dragItems.map((item) => ({
|
||||||
completedRef.current = false;
|
value: item.id,
|
||||||
}
|
label: item.name,
|
||||||
}, [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>(
|
// 转换为 DragFill 的 blanks - 为每个标签创建独立的空白区域
|
||||||
() => ({
|
const blanks = useMemo(() => {
|
||||||
position: "absolute",
|
const allBlanks: Array<{ id: string; label: string }> = [];
|
||||||
width: `${DESIGN_WIDTH}px`,
|
cityConfig.forEach((city) => {
|
||||||
height: `${DESIGN_HEIGHT}px`,
|
city.labels.forEach((label, index) => {
|
||||||
left: `${offsetX}px`,
|
const labelText = typeof label === "string" ? label : label.text;
|
||||||
top: `${offsetY}px`,
|
allBlanks.push({
|
||||||
transform: `scale(${scale})`,
|
id: `city-${city.id}-label-${index}`,
|
||||||
transformOrigin: "top left",
|
label: labelText,
|
||||||
overflow: "hidden",
|
});
|
||||||
}),
|
});
|
||||||
[scale, offsetX, offsetY, DESIGN_WIDTH, DESIGN_HEIGHT]
|
});
|
||||||
);
|
return allBlanks;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理拖拽结果变化 - 验证是否正确放置
|
||||||
|
const handleValueChange = useCallback((newValue: DragFillResult[]) => {
|
||||||
|
// 验证每个元素是否放置到正确的标签区域
|
||||||
|
const filteredValue = newValue.filter((result) => {
|
||||||
|
const itemId = result.optionValue;
|
||||||
|
const blankId = result.blankId;
|
||||||
|
|
||||||
|
const item = dragItems.find((item) => item.id === itemId);
|
||||||
|
return item && item.targetBlankId === blankId;
|
||||||
|
});
|
||||||
|
|
||||||
|
setDragFillValue(filteredValue);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const progress = dragFillValue.length;
|
||||||
|
const totalTasks = dragItems.length; // 8个拖拽元素
|
||||||
|
|
||||||
|
// 检查是否全部完成
|
||||||
|
useEffect(() => {
|
||||||
|
if (progress === totalTasks && !allCompleted) {
|
||||||
|
setAllCompleted(true);
|
||||||
|
setShowSuccessAlert(true);
|
||||||
|
}
|
||||||
|
}, [progress, totalTasks, allCompleted]);
|
||||||
|
|
||||||
|
const handleSuccessAlertClose = () => {
|
||||||
|
setShowSuccessAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取每个标签区域已放置的元素
|
||||||
|
const getLabelPlacedItem = (cityId: number, labelIndex: number) => {
|
||||||
|
const blankId = `city-${cityId}-label-${labelIndex}`;
|
||||||
|
const result = dragFillValue.find((r) => r.blankId === blankId);
|
||||||
|
if (!result) return null;
|
||||||
|
return dragItems.find((item) => item.id === result.optionValue) || null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
className="flex justify-between w-full h-full bg-cover bg-center bg-no-repeat gap-10 relative"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: getBackgroundImage(`${starName}/reflection/scene2/底图`),
|
backgroundImage: getBackgroundImage(
|
||||||
|
`${starName}/reflection/scene2/底图`
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title
|
{/* 离开按钮 */}
|
||||||
title="跨越时空"
|
|
||||||
subtitle={subtitle}
|
|
||||||
subtitleClassName={subtitleClassName}
|
|
||||||
className="absolute top-10 left-1/2 -translate-x-1/2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
className="absolute right-5 top-5"
|
className="absolute right-5 top-5 z-10"
|
||||||
src={getStaticImage(`${starName}/reflection/scene2/离开`)}
|
src={getStaticImage(`${starName}/reflection/scene1/离开`)}
|
||||||
button
|
button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (moduleName && starName) {
|
if (moduleName && starName) {
|
||||||
@@ -134,130 +226,218 @@ const LuxunReflectionScene2 = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 点击区域 - 打开拖拽弹窗 */}
|
{/* 左侧:鲁迅的文学足迹 - 地图区域 */}
|
||||||
<Cursor
|
<div className="w-[44.5%] relative">
|
||||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
|
{/* 地图容器 */}
|
||||||
onClick={() => setShowDialog(true)}
|
<div className="relative w-full h-full">
|
||||||
read={visited.has(1)}
|
{cityConfig.map((city) => {
|
||||||
/>
|
return (
|
||||||
|
<div
|
||||||
{completed && (
|
key={city.id}
|
||||||
<Image
|
className="absolute flex flex-col items-center"
|
||||||
className="absolute bottom-0 right-0"
|
style={{
|
||||||
src={getStaticImage("components/place/继续前进")}
|
top: city.position.top,
|
||||||
button
|
left: city.position.left,
|
||||||
onClick={() => {
|
transform: "translate(-50%, -50%)",
|
||||||
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
|
<div className="relative">
|
||||||
className={cn("absolute inset-0 flex flex-col", "bg-black/70")}
|
<img src={getStaticImage(city.imagePath)} alt={city.name} />
|
||||||
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
|
<div className="absolute inset-0">
|
||||||
src={backSrc}
|
{city.labels.map((label, index) => {
|
||||||
onClick={() => setShowDialog(false)}
|
const blankId = `city-${city.id}-label-${index}`;
|
||||||
alt="返回"
|
const placedItem = getLabelPlacedItem(city.id, index);
|
||||||
className="absolute top-5 right-5 z-20"
|
const isFilled = !!placedItem;
|
||||||
button
|
const labelText =
|
||||||
/>
|
typeof label === "string" ? label : label.text;
|
||||||
|
const containerPosition =
|
||||||
|
typeof label === "string"
|
||||||
|
? { top: "0%", left: "0%" }
|
||||||
|
: label.containerPosition;
|
||||||
|
|
||||||
{/* 内容区域 */}
|
return (
|
||||||
<div className="relative z-10 w-full h-full flex items-center justify-center px-30 py-16 pt-30">
|
<div
|
||||||
<div className="w-full max-w-6xl">
|
key={index}
|
||||||
<div className="text-5xl font-bold text-white mb-8 text-center">
|
data-blank-id={blankId}
|
||||||
将文学作品拖拽到对应位置
|
className={`absolute border-2 border-dashed rounded-lg transition-all min-w-[120px] min-h-[120px] flex flex-col items-center justify-center ${
|
||||||
|
isFilled
|
||||||
|
? "border-green-500 bg-green-500/20"
|
||||||
|
: "border-white/60 bg-white/10 hover:border-blue-400 hover:bg-blue-400/20"
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
top: containerPosition.top,
|
||||||
|
left: containerPosition.left,
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 已放置的元素显示在容器框内 */}
|
||||||
|
{placedItem ? (
|
||||||
|
<img
|
||||||
|
src={getStaticImage(placedItem.imagePath)}
|
||||||
|
alt={placedItem.name}
|
||||||
|
className="w-20 h-20 object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="text-white/30 text-sm text-center">
|
||||||
|
{labelText}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
)}
|
);
|
||||||
</AnimatePresence>,
|
})}
|
||||||
document.body
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧区域 */}
|
||||||
|
<div className="flex-1 relative">
|
||||||
|
{/* 任务进度 */}
|
||||||
|
<div className="absolute top-4 left-[27%] w-[430px] h-51 flex justify-center items-center">
|
||||||
|
<span className="text-white text-[54px] font-bold [text-shadow:_0px_0px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921,_6px_6px_4px_#b37921]">
|
||||||
|
{allCompleted
|
||||||
|
? "任务进度已完成"
|
||||||
|
: `任务进度:(${progress}/${totalTasks})`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* 鲁迅画廊 */}
|
||||||
|
<div className="absolute top-[calc(2.4rem+204px)] left-0 w-[995px] h-[467px] pt-16 pl-70 pr-10">
|
||||||
|
{/* 提示图标 */}
|
||||||
|
<div className="absolute left-10 top-30 flex items-center z-10 pointer-events-none">
|
||||||
|
<img
|
||||||
|
src={getStaticImage(
|
||||||
|
`${starName}/reflection/scene2/鲁迅体悟拖一拖`
|
||||||
|
)}
|
||||||
|
alt="拖一拖"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={getStaticImage(
|
||||||
|
`${starName}/reflection/scene2/鲁迅体悟拖一拖图标`
|
||||||
|
)}
|
||||||
|
alt="拖一拖图标"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 使用 DragFill 组件 */}
|
||||||
|
<div className="mt-10">
|
||||||
|
<DragFill
|
||||||
|
options={options}
|
||||||
|
blanks={blanks}
|
||||||
|
value={dragFillValue}
|
||||||
|
onChange={handleValueChange}
|
||||||
|
allowDuplicate={false}
|
||||||
|
showSubmitButton={false}
|
||||||
|
layout="horizontal-options"
|
||||||
|
hideLabels={true}
|
||||||
|
className="w-full"
|
||||||
|
optionClassName="grid grid-cols-4 gap-10"
|
||||||
|
blankClassName="hidden" // 隐藏默认的空白区域,我们在地图上自定义显示
|
||||||
|
renderOption={(option, isUsed) => {
|
||||||
|
const item = dragItems.find((item) => item.id === option.value);
|
||||||
|
if (!item) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative transition-all duration-200 cursor-move ${
|
||||||
|
isUsed ? "opacity-30" : "hover:scale-105"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={getStaticImage(item.imagePath)}
|
||||||
|
alt={item.name}
|
||||||
|
className="w-full h-auto"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderDragging={(option) => {
|
||||||
|
const item = dragItems.find((item) => item.id === option.value);
|
||||||
|
if (!item) return null;
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={getStaticImage(item.imagePath)}
|
||||||
|
alt={item.name}
|
||||||
|
className="w-20 h-20 object-contain shadow-lg"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderBlank={(blank, _filledValue, _isHovered) => {
|
||||||
|
// 空白区域在地图上显示,返回一个隐藏的占位元素
|
||||||
|
// DragFill 会通过 data-blank-id 属性来识别空白区域
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-blank-id={blank.id}
|
||||||
|
className="hidden"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 任务展示窗口 */}
|
||||||
|
<div className="absolute bottom-3.5 right-8 w-[995px] h-[335px] px-10 py-4 overflow-y-auto luxun-scrollbar">
|
||||||
|
{allCompleted ? (
|
||||||
|
// 全部完成后显示详细信息
|
||||||
|
<div className="space-y-8">
|
||||||
|
{cityConfig.map((city) => (
|
||||||
|
<div key={city.id} className="space-y-4">
|
||||||
|
<div className="text-red-600 text-6xl font-bold">
|
||||||
|
{city.location}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-600 text-4xl font-normal">
|
||||||
|
{city.time}
|
||||||
|
</div>
|
||||||
|
<div className="text-black text-4xl font-normal leading-relaxed">
|
||||||
|
{city.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// 未完成时显示提示内容
|
||||||
|
<div className="space-y-8 text-center">
|
||||||
|
<div className="text-black text-5xl font-bold">任务展示窗口</div>
|
||||||
|
<div className="text-red-600 text-4xl font-bold">
|
||||||
|
请将以下内容拖拽至正确位置
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 gap-4 text-3xl font-bold">
|
||||||
|
{dragItems.map((item) => {
|
||||||
|
const isPlaced = dragFillValue.some(
|
||||||
|
(result) => result.optionValue === item.id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className={`${
|
||||||
|
isPlaced ? "text-green-600 line-through" : "text-black"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 成功提示弹窗 */}
|
||||||
|
<SuccessAlert
|
||||||
|
visible={showSuccessAlert}
|
||||||
|
title="恭喜你, 完成所有任务!"
|
||||||
|
onClose={handleSuccessAlertClose}
|
||||||
|
onContinue={handleSuccessAlertClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user