import { useState, useEffect, useMemo, useCallback } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Image, DragFill, GuideSteps, GuideStepsItem } from "@/components"; import type { DragFillResult } from "@/components"; import ErrorAlert from "@/components/layout/alert/error-alert"; import SuccessAlert from "@/components/layout/alert/success-alert"; import { getBackgroundImage, getStaticImage } from "@/lib/utils"; const starName = "luxun"; // 拖拽元素配置 - 每个元素对应一个特定的标签放置区域 const dragItems = [ { id: "runtu", name: "闰土", imagePath: "luxun/reflection/scene2/拖拽-润土", targetBlankId: "city-1-label-0", // 绍兴-人物 errorHint: "❌闰土的故事和鲁迅的少年绍兴时光有关哦", successMessage: "✅答对啦! 闰土是鲁迅绍兴时期笔下的经典人物", }, { id: "baicaoyuan", name: "《从百草园到三味书屋》", imagePath: "luxun/reflection/scene2/拖拽-百草园到三味书屋", targetBlankId: "city-1-label-1", // 绍兴-作品 errorHint: "❌这个作品写的是鲁迅在绍兴的故事哦", successMessage: "✅真棒! 这篇散文记录了鲁迅在绍兴的求学时光", }, { id: "aqzhengzhuan", name: "《阿Q正传》", imagePath: "luxun/reflection/scene2/拖拽-阿Q正传", targetBlankId: "city-2-label-0", // 北京-作品 errorHint: "❌这部经典小说和鲁迅在北京的创作有关哦", successMessage: "✅答对啦! 《阿Q正传》是鲁迅小说的代表作, 诞生于北京", }, { id: "jingshenshengli", name: '"精神胜利法"', imagePath: "luxun/reflection/scene2/拖拽-精神胜利法", targetBlankId: "city-2-label-1", // 北京-核心主题 errorHint: "❌这个主题是《阿Q正传》的核心, 和北京创作背景相关哦", successMessage: "✅没错! 《阿Q正传》画出了国人的灵魂, 以便疗救。", }, { id: "guanzhudiceng", name: '"关注底层"', imagePath: "luxun/reflection/scene2/关注底层", targetBlankId: "city-3-label-0", // 厦门-创作方向 errorHint: "❌这个创作方向是鲁迅在厦门的核心追求哦", successMessage: "✅答对啦!鲁迅在厦门任教时,依旧关注底层民众的命运", }, { id: "zhaohuaxishi", name: "《朝花夕拾》", imagePath: "luxun/reflection/scene2/拖拽-朝花夕拾", targetBlankId: "city-4-label-0", // 广州-作品集 errorHint: "❌这个作品集和鲁迅在广州的旅居时光有关哦", successMessage: "✅真棒!《朝花夕拾》部分篇章在广州白云楼定稿", }, { id: "gushixinbian", name: "《故事新编》", imagePath: "luxun/reflection/scene2/拖拽-故事新编", targetBlankId: "city-5-label-0", // 上海-作品 errorHint: "❌再试试~这个作品和上海的创作故事更相关哦", successMessage: "✅答对啦!《故事新编》是鲁迅在上海故居完成的 经典作品集", }, { id: "biweidaoqiang", name: '"笔为刀枪"', imagePath: "luxun/reflection/scene2/笔为刀枪", targetBlankId: "city-5-label-1", // 上海-创作中心 errorHint: "❌这个初心是鲁迅在上海创作的核心哦", successMessage: "✅没错!鲁迅在上海以笔为刃,批判黑暗的社会现实", }, ]; // 城市配置 - 根据效果图调整位置 // 布局:北京(上) -> 上海(中上) -> 绍兴(中) -> 广州(左下) -> 厦门(右下) const cityConfig = [ { id: 1, name: "绍兴", location: "绍兴三味书屋", imagePath: "luxun/reflection/scene2/绍兴", position: { top: "65%", left: "65%" }, // 中间 labels: [ { text: "人物", containerPosition: { top: "50%", left: "20%" } }, { text: "作品", containerPosition: { top: "50%", left: "50%" } }, ], time: "1892年-1898年", description: "少年鲁迅在此求学,为《从百草园到三味书屋》埋下创作种子。", }, { id: 2, name: "北京", location: "北京八道湾胡同", imagePath: "luxun/reflection/scene2/北京", position: { top: "14%", left: "62%" }, // 顶部 labels: [ { text: "作品", containerPosition: { top: "72%", left: "30%" } }, { text: "核心主题", containerPosition: { top: "72%", left: "70%" } }, ], time: "1919-1923年", description: "鲁迅参与新文化运动的核心时期,《阿Q正传》等经典作品在此酝酿创作。", }, { id: 3, name: "厦门", location: "厦门大学", imagePath: "luxun/reflection/scene2/厦门", position: { top: "83%", left: "78%" }, // 右下 labels: [ { text: "创作方向", containerPosition: { top: "52%", left: "60%" } }, ], time: "1926年9-12月", description: "鲁迅应邀前往厦门大学任教,期间写下多篇批判现实的杂文,关注底层民众命运。", }, { id: 4, name: "广州", location: "广州白云楼", imagePath: "luxun/reflection/scene2/广州", position: { top: "88%", left: "35%" }, // 左下 labels: [ { text: "作品集", containerPosition: { top: "38%", left: "50%" } }, ], time: "1927年3-9月", description: "先生在此整理旧作,思考民族觉醒之路,《朝花夕拾》部分篇章定稿于此。", }, { id: 5, name: "上海", location: "上海鲁迅故居", imagePath: "luxun/reflection/scene2/上海", position: { top: "48%", left: "70%" }, // 中上 labels: [ { text: "作品", containerPosition: { top: "50%", left: "20%" } }, { text: "创作中心", containerPosition: { top: "50%", left: "50%" } }, ], time: "1933-1936年", description: "鲁迅晚年定居地,先生在此写下《故事新编》等作品,与进步文人并肩,以文字为刃批判黑暗。", }, ]; const LuxunReflectionScene2 = () => { const navigate = useNavigate(); const { moduleName } = useParams<{ moduleName: string; }>(); // DragFill 的值 const [dragFillValue, setDragFillValue] = useState([]); // 是否全部完成 const [allCompleted, setAllCompleted] = useState(false); // 错误提示相关状态 const [showErrorAlert, setShowErrorAlert] = useState(false); const [errorHint, setErrorHint] = useState(""); // 成功提示相关状态 const [showSuccessAlert, setShowSuccessAlert] = useState(false); const [successMessage, setSuccessMessage] = useState(""); // 引导对话是否显示 const [guideVisible, setGuideVisible] = useState(true); // 通关提示对话是否显示 const [completionGuideVisible, setCompletionGuideVisible] = useState(false); // 通关提示是否已经显示过(防止重复显示) const [completionGuideShown, setCompletionGuideShown] = useState(false); // 转换为 DragFill 的 options const options = useMemo( () => dragItems.map((item) => ({ value: item.id, label: item.name, })), [] ); // 转换为 DragFill 的 blanks - 为每个标签创建独立的空白区域 const blanks = useMemo(() => { const allBlanks: Array<{ id: string; label: string }> = []; cityConfig.forEach((city) => { city.labels.forEach((label, index) => { const labelText = typeof label === "string" ? label : label.text; allBlanks.push({ id: `city-${city.id}-label-${index}`, label: labelText, }); }); }); return allBlanks; }, []); // 处理拖拽结果变化 - 验证是否正确放置 const handleValueChange = useCallback( (newValue: DragFillResult[]) => { // 找出新添加或位置改变的拖拽结果(与当前值比较) const previousValue = dragFillValue; const changedResults = newValue.filter( (newResult) => !previousValue.some( (prevResult) => prevResult.optionValue === newResult.optionValue && prevResult.blankId === newResult.blankId ) ); // 检查改变的结果是否有错误 let hasError = false; let errorMessage = ""; let successItem = null; for (const result of changedResults) { const itemId = result.optionValue; const blankId = result.blankId; const item = dragItems.find((item) => item.id === itemId); // 如果放置位置不正确,显示错误提示 if (item && item.targetBlankId !== blankId) { hasError = true; errorMessage = item.errorHint || "放置位置不正确,请再试试~"; break; // 只显示第一个错误的提示 } else if (item && item.targetBlankId === blankId) { // 如果放置正确,记录成功项(只记录第一个正确的) if (!successItem) { successItem = item; } } } if (hasError) { // 显示错误提示 setErrorHint(errorMessage); setShowErrorAlert(true); // 不更新拖拽值,保持之前正确的状态 // DragFill 是受控组件,不更新 value 会保持之前的状态 return; } // 验证每个元素是否放置到正确的标签区域,只保留正确的放置 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; }); // 如果拖拽正确,显示成功提示 if (successItem) { setSuccessMessage(successItem.successMessage); setShowSuccessAlert(true); } setDragFillValue(filteredValue); }, [dragFillValue] ); const progress = dragFillValue.length; const totalTasks = dragItems.length; // 8个拖拽元素 // 检查是否全部完成 useEffect(() => { if (progress === totalTasks && !allCompleted) { setAllCompleted(true); // 不在这里显示通关提示,等待所有成功提示关闭后再显示 } }, [progress, totalTasks, allCompleted]); const handleErrorAlertClose = () => { setShowErrorAlert(false); setErrorHint(""); }; const handleSuccessAlertClose = () => { setShowSuccessAlert(false); setSuccessMessage(""); }; // 当所有任务完成且成功提示关闭后,显示通关提示(只显示一次) useEffect(() => { if (allCompleted && !showSuccessAlert && !completionGuideShown) { setCompletionGuideVisible(true); setCompletionGuideShown(true); } }, [allCompleted, showSuccessAlert, completionGuideShown]); // 获取每个标签区域已放置的元素 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 (
{/* 离开按钮 */} { if (moduleName && starName) { navigate(`/${moduleName}/${starName}/reflection`); } }} /> {/* 左侧:鲁迅的文学足迹 - 地图区域 */}
{/* 地图容器 */}
{cityConfig.map((city) => { return (
{/* 城市图片容器 */}
{city.name} {/* 标签区域 - 每个标签是一个独立的容器框 */}
{city.labels.map((label, index) => { const blankId = `city-${city.id}-label-${index}`; const placedItem = getLabelPlacedItem(city.id, index); const isFilled = !!placedItem; const labelText = typeof label === "string" ? label : label.text; const containerPosition = typeof label === "string" ? { top: "0%", left: "0%" } : label.containerPosition; return (
{/* 已放置的元素显示在容器框内 */} {placedItem ? ( {placedItem.name} ) : (
{labelText}
)}
); })}
); })}
{/* 导航栏 */}
{/* 任务2漫集美学 */}
{/* 任务3学习精神 */}
{ if (moduleName && allCompleted) { navigate(`/${moduleName}/${starName}/reflection3`); } }} > {/* 任务完成后显示点一点图标 */} {allCompleted && (
点一点图标
)}
{/* 右侧区域 */}
{/* 任务进度 */}
{allCompleted ? "任务进度已完成" : `任务进度:(${progress}/${totalTasks})`}
{/* 鲁迅画廊 */}
{/* 提示图标 */}
拖一拖 拖一拖图标
请将右侧图标拖到左侧对应位置。
{/* 使用 DragFill 组件 */}
{ const item = dragItems.find((item) => item.id === option.value); if (!item) return null; return (
{item.name}
); }} renderDragging={(option) => { const item = dragItems.find((item) => item.id === option.value); if (!item) return null; return ( {item.name} ); }} renderBlank={(blank, _filledValue, _isHovered) => { // 空白区域在地图上显示,返回一个隐藏的占位元素 // DragFill 会通过 data-blank-id 属性来识别空白区域 return (
); }} />
{/* 任务展示窗口 */}
{allCompleted ? ( // 全部完成后显示详细信息
{cityConfig.map((city) => (
{city.location}
{city.time}
{city.description}
))}
) : ( // 未完成时显示提示内容
任务展示窗口
{/*
请将以下内容拖拽至正确位置
*/}
{dragItems.map((item) => { const isPlaced = dragFillValue.some( (result) => result.optionValue === item.id ); return (
{item.name}
); })}
)}
{/* 错误提示弹窗 */} {/* 成功提示弹窗 */} {/* 学习引导对话 */} 这些线索藏在先生的文字里,长按卡片拖到对应地标旁,还原创作故事! {/* 通关提示对话 */} 你读懂了鲁迅文字与土地的联结,再深入看看文字里的初心!
); }; export default LuxunReflectionScene2;