feat:修改英文双引号

This commit is contained in:
kevinzhu committed 2026-04-30 09:17:31 +08:00
commit 4a2be3b4d0
1351 files changed
+56547

No files matched your search

@@ -0,0 +1,524 @@
import React, { useState, useRef, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
Image,
ClueHintModal,
ClueBookModal,
flyToAnimation,
Video,
StudyGuideModal,
EndingOverlay,
} from '@/components'
import { SuccessAlert } from '@/components/layout'
import { SingleChoiceQuizPanel } from '@/components/layout/quiz'
import { getBackgroundImage, getStaticImage, getPublicPath } from '@/lib/utils'
import type { ClueHint } from '@/components/clue'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { useSunzhongshanStore, saveSunzhongshanStore } from '@/store/sunzhongshanStore'
const starName = 'sunzhongshan'
const sceneId = `${starName}/reflection/scene1`
const GUIDE_STORAGE_KEY = `${sceneId}-guide-shown`
// 线索配置 - 根据实际图片调整位置
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 { triggerPuzzleAnimation } = usePuzzleAnimation()
const { getReflectionFlag, setReflectionFlag } = useSunzhongshanStore()
const [showClueHint, setShowClueHint] = useState(false)
const [showClueBook, setShowClueBook] = useState(false)
const [showSuccessAlert, setShowSuccessAlert] = useState(false)
const [successAlertContent, setSuccessAlertContent] = useState('')
const [showVideo, setShowVideo] = useState(true)
const [showGuide, setShowGuide] = useState(false)
const [showQuiz1, setShowQuiz1] = useState(false)
const [showQuiz2, setShowQuiz2] = useState(false)
const [showQuiz3, setShowQuiz3] = useState(false)
const [showCompleteGuide, setShowCompleteGuide] = useState(false)
const [pendingQuiz, setPendingQuiz] = useState<'quiz2' | 'quiz3' | 'complete' | null>(null)
const { collectClue, isClueCollected } = useSunzhongshanStore()
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('获得1条线索,已获得线索可在线索簿中查看!')
setShowSuccessAlert(true)
},
})
}
}
// 处理背包点击 - 只收集线索1,不打开线索提示框
const handleBackpackClick = () => {
const clue1Id = `${sceneId}/clue1`
const clue1Name = '线索1'
// 如果线索1已收集,显示提示
if (isClueCollected(clue1Id)) {
setSuccessAlertContent('该线索已收集!')
setShowSuccessAlert(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: () => {
// 动画完成后显示成功提示
setSuccessAlertContent('获得该线索!快去线索簿查看详细信息吧!')
setShowSuccessAlert(true)
},
})
}
}
// 处理线索提示按钮点击 - 打开线索提示框
const handleClueHintClick = () => {
setShowClueHint(true)
}
// 处理视频播放结束
const handleVideoEnd = () => {
setShowVideo(false)
// 检查是否需要显示学习引导
const hasShownGuide = getReflectionFlag(GUIDE_STORAGE_KEY)
if (!hasShownGuide) {
setShowGuide(true)
}
}
// 处理学习引导关闭
const handleGuideClose = () => {
setShowGuide(false)
setReflectionFlag(GUIDE_STORAGE_KEY, true)
}
// 检查是否所有线索都已收集
const areAllCluesCollected = () => {
return clueConfigs.every((clue) => isClueCollected(clue.id))
}
// 处理线索簿关闭
const handleClueBookClose = () => {
setShowClueBook(false)
// 如果所有线索都已收集,显示题目1
if (areAllCluesCollected()) {
setShowQuiz1(true)
}
}
// 处理题目1提交
const handleQuiz1Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz1(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setPendingQuiz('quiz2')
}
}
// 处理题目2提交
const handleQuiz2Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz2(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
setPendingQuiz('quiz3')
}
}
// 处理题目3提交
const handleQuiz3Submit = (_value: string, isCorrect: boolean) => {
if (isCorrect) {
setShowQuiz3(false)
setSuccessAlertContent('回答正确!')
setShowSuccessAlert(true)
// 设置待显示的完成提示
setPendingQuiz('complete')
}
}
// 处理完成提示关闭
const handleCompleteGuideClose = () => {
setShowCompleteGuide(false)
// 保存场景1完成状态
setReflectionFlag(`${starName}-reflection-scene1-completed`, true)
if (moduleName && starName) {
const { setReflectionRoute } = useSunzhongshanStore.getState()
setReflectionRoute('reflection2')
saveSunzhongshanStore()
navigate(`/${moduleName}/${starName}/reflection2`)
}
}
// 获取线索图片
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}-详情`)
}
// 获取线索提示图片
const getClueHintImage = (clueId: string): string | null => {
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`),
}}
>
{/* 视频播放 - 覆盖在页面内容之上 */}
{showVideo && (
<div className="fixed inset-0 z-9999 bg-black">
<Video
src={getPublicPath(`video/${starName}/sunzhongshan_11.mp4`)}
className="w-full h-full"
onEnded={handleVideoEnd}
autoPlay={true}
showSkipButton={true}
onSkip={handleVideoEnd}
/>
</div>
)}
{/* 关闭按钮 */}
<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={handleClueHintClick}
/>
<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={handleClueBookClose}
getClueImage={getClueImage}
getClueDetailImage={getClueDetailImage}
getClueHintImage={getClueHintImage}
/>
{/* 成功提示弹窗 */}
<SuccessAlert
visible={showSuccessAlert}
title="恭喜你!"
content={successAlertContent}
audioSrc={
successAlertContent === '获得1条线索,已获得线索可在线索簿中查看!'
? getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_49.mp3')
: undefined
}
onContinue={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
triggerPuzzleAnimation(12)
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
onClose={() => {
setShowSuccessAlert(false)
setSuccessAlertContent('')
// 检查是否有待显示的题目或完成提示
if (pendingQuiz === 'quiz2') {
setShowQuiz2(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'quiz3') {
setShowQuiz3(true)
setPendingQuiz(null)
} else if (pendingQuiz === 'complete') {
setShowCompleteGuide(true)
setPendingQuiz(null)
}
}}
/>
{/* 学习引导弹窗 */}
<StudyGuideModal
visible={showGuide}
onContinue={handleGuideClose}
firstLineIndent="2em"
zIndex={10000}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_48.mp3')}
>
同学你好,欢迎来到二十世纪初的上海。
<br />
辛亥革命之后,民主共和的观念深入人心。面对中外反动势力,社会各界更广泛地行动起来,以各自的方式争取着民主自由和国家尊严。
<br />
你需要在本场景中找出4条线索,并正确回答所有问题后,才能拨开时局迷雾,进入下一个场景。
</StudyGuideModal>
{/* 题目1:今天的日期是? */}
<SingleChoiceQuizPanel
visible={showQuiz1}
title="【单选】今天的日期是?"
options={[
{ value: 'A', label: 'A.1919年5月5日' },
{ value: 'B', label: 'B.1921年5月5日' },
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz1Submit}
zIndex={10001}
/>
{/* 题目2:今天的日记写什么呢? */}
<SingleChoiceQuizPanel
visible={showQuiz2}
title="【单选】今天的日记写什么呢?"
options={[
{
value: 'A',
label:
'A.今天路过莫利爱路29号,旁观了中国国民党党部举行的庆祝活动。得知孙中山先生重整旗鼓,预备再度联合西南军阀进行二次护法。思及1918年第一次护法运动的失败,隐隐为孙先生此番努力感到忧虑。',
},
{
value: 'B',
label:
'B.参加了中华职业学校同学为庆祝广州军政府成立、孙中山在广州就任非常大总统而组织的活动,深感革命成功有望。',
},
]}
correctAnswer="A"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz2Submit}
zIndex={10001}
/>
{/* 题目3:作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现? */}
<SingleChoiceQuizPanel
visible={showQuiz3}
title="【单选】作为五四运动的见证者、参与者,你觉得中国革命有望通过怎样的方式实现?"
options={[
{
value: 'A',
label: 'A.团结强大的军阀,共举民主共和旗帜',
},
{
value: 'B',
label: 'B.团结各族各界群众,建立广泛的革命统一战线',
},
]}
correctAnswer="B"
errorMessage="回答错误,请你再仔细思考一下吧。"
onSubmit={handleQuiz3Submit}
zIndex={10001}
/>
{/* 完成提示弹窗 */}
<EndingOverlay
visible={showCompleteGuide}
text={`恭喜你完成场景一的探索!\n粤军总司令陈炯明与孙中山政见无法统一。1922年6月16日,陈炯明部将叶举率4000余人包围总统府,炮击孙中山住所粤秀楼。这一叛变行动使得北伐事业再次陷于停顿,第二次护法运动失败……\n请你走进场景二,继续探索吧!`}
animationSpeed={200}
onContinue={handleCompleteGuideClose}
audioSrc={getPublicPath('audio/sunzhongshan/mp3_sunzhongshan_50.mp3')}
/>
</div>
)
}
export default SunzhongshanReflectionScene1