fix: 修复开发/测试包模拟进度被易班同步覆盖的问题
进度模拟面板写入 puzzleStore 后,App 启动时易班同步会将其覆盖; 现在在同步完成后按 sessionStorage 中保存的模式重放模拟进度, 确保测试包的"到达某故居/全流程完成"调试入口生效。 同时纳入近期未提交的页面、组件、资源与环境变量改动。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6d40e19346
commit
49ecabbf3a
155 files changed
+1956
-724
No files matched your search
@@ -4,7 +4,24 @@ import { Image } from '@/components'
|
||||
import { usePuzzleStore } from '@/store/puzzleStore'
|
||||
import { getResidenceStageUnlocks, isResidenceUnlocked } from '@/config/progression'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useRongdeshengStore } from '@/store/rongdeshengStore'
|
||||
import { getUserDefineResult } from '@/lib/utils/api-common'
|
||||
import { showYibanSyncErrorPrompt, showYibanUnauthenticatedPrompt } from '@/components'
|
||||
import {
|
||||
RONGDESHENG_KEY,
|
||||
RONGDESHENG_LESSON_ID,
|
||||
useRongdeshengStore,
|
||||
} from '@/store/rongdeshengStore'
|
||||
|
||||
const deserializeVisitedExhibits = (
|
||||
data: Record<string, number[]> | undefined
|
||||
): Record<string, Set<number>> => {
|
||||
if (!data || typeof data !== 'object') return {}
|
||||
const visitedExhibits: Record<string, Set<number>> = {}
|
||||
for (const [place, exhibits] of Object.entries(data)) {
|
||||
visitedExhibits[place] = new Set(Array.isArray(exhibits) ? exhibits : [])
|
||||
}
|
||||
return visitedExhibits
|
||||
}
|
||||
|
||||
const RongdeshengHome = () => {
|
||||
const navigate = useNavigate()
|
||||
@@ -25,6 +42,64 @@ const RongdeshengHome = () => {
|
||||
}
|
||||
}, [isLoaded, navigate, puzzles, starName])
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
try {
|
||||
const result = await getUserDefineResult(RONGDESHENG_LESSON_ID, RONGDESHENG_KEY)
|
||||
|
||||
if (result.status !== 'success') {
|
||||
useRongdeshengStore.setState((prev) => ({
|
||||
...prev,
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionCompleted: {},
|
||||
reflectionVideoViewed: {},
|
||||
reflectionFinalCompleted: false,
|
||||
reflectionGuideShown: false,
|
||||
}))
|
||||
|
||||
if (result.status === 'unauthenticated') {
|
||||
showYibanUnauthenticatedPrompt()
|
||||
} else if (result.status === 'error') {
|
||||
showYibanSyncErrorPrompt()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const data = JSON.parse(result.content) as {
|
||||
visitedExhibits?: Record<string, number[]>
|
||||
lastVisitedPlace?: string | null
|
||||
reflectionCompleted?: Record<string, boolean>
|
||||
reflectionVideoViewed?: Record<string, Record<string, boolean>>
|
||||
reflectionFinalCompleted?: boolean
|
||||
reflectionGuideShown?: boolean
|
||||
}
|
||||
|
||||
useRongdeshengStore.setState((prev) => ({
|
||||
...prev,
|
||||
visitedExhibits: deserializeVisitedExhibits(data.visitedExhibits),
|
||||
lastVisitedPlace: data.lastVisitedPlace ?? null,
|
||||
reflectionCompleted: data.reflectionCompleted ?? {},
|
||||
reflectionVideoViewed: data.reflectionVideoViewed ?? {},
|
||||
reflectionFinalCompleted: data.reflectionFinalCompleted ?? false,
|
||||
reflectionGuideShown: data.reflectionGuideShown ?? false,
|
||||
}))
|
||||
} catch (error) {
|
||||
useRongdeshengStore.setState((prev) => ({
|
||||
...prev,
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionCompleted: {},
|
||||
reflectionVideoViewed: {},
|
||||
reflectionFinalCompleted: false,
|
||||
reflectionGuideShown: false,
|
||||
}))
|
||||
showYibanSyncErrorPrompt()
|
||||
console.error('Failed to load Rongdesheng store', error)
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
if (!moduleName) return null
|
||||
|
||||
return (
|
||||
|
||||
@@ -47,7 +47,7 @@ type QuizAnchor = {
|
||||
const QUIZ_ANCHORS: QuizAnchor[] = [
|
||||
{
|
||||
id: 'quiz-1',
|
||||
time: 60,
|
||||
time: 60.5,
|
||||
type: 'drag',
|
||||
title: '',
|
||||
prompt: '【拖拽】荣德生大力发展与国计民生密切相关的',
|
||||
@@ -505,7 +505,7 @@ const RongdeshengInternalization = () => {
|
||||
onContinue={() => {
|
||||
setShowEnding(false)
|
||||
setCourseTimeProgress(21)
|
||||
navigate('/menu')
|
||||
navigate('/learning-map', { state: { completedResidence: 'rongdesheng' } })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -262,7 +262,7 @@ const RongdeshengPlace2 = () => {
|
||||
speaker="student"
|
||||
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_07.mp3')}
|
||||
>
|
||||
哇,这是福新面粉公司的老照片!荣德生先生为什么选择办面粉,面粉厂又为什么在上海创办?福新面粉厂后来发展得怎么样?
|
||||
哇,这是福新面粉公司的老照片!荣德生先生为什么选择办面粉厂,面粉厂又为什么在上海创办?福新面粉厂后来发展得怎么样?
|
||||
</GuideStepsItem>
|
||||
<GuideStepsItem
|
||||
speaker="guide"
|
||||
|
||||
@@ -186,8 +186,6 @@ const RongdeshengPlace4 = () => {
|
||||
// 触发碎片获取动画(展品3 -> 碎片13,展品4 -> 碎片14)
|
||||
if (closingId === 3) {
|
||||
triggerPuzzleAnimation(13)
|
||||
} else if (closingId === 4) {
|
||||
triggerPuzzleAnimation(14)
|
||||
}
|
||||
|
||||
if (closingId === 4) {
|
||||
@@ -377,7 +375,7 @@ const RongdeshengPlace4 = () => {
|
||||
<EndingOverlay
|
||||
visible={endingGuideVisible}
|
||||
audioSrc={getPublicPath('audio/rongdesheng/mp3_rongdesheng_30.mp3')}
|
||||
text="接下来,请你跨越时空,走进1915年的申新纺织厂,参与工厂的创办与运营中,在模拟决策中感受民族工业在风雨中崛起的力量。让我们带着这份感动与启发,传承爱国情怀,续写新时代的实业篇章。"
|
||||
text="接下来,请你跨越时空,走进1915年的申新纺织厂,参与工厂的创办与运营,在模拟决策中感受民族工业在风雨中崛起的力量。让我们带着这份感动与启发,传承爱国情怀,续写新时代的实业篇章。"
|
||||
animationSpeed={200}
|
||||
onContinue={() => setEndingGuideVisible(false)}
|
||||
/>
|
||||
|
||||
@@ -32,12 +32,17 @@ export const FinalScene = ({
|
||||
|
||||
{finalVideoStage !== 'idle' && (
|
||||
<div className="fixed inset-0 z-50 w-full h-full bg-black">
|
||||
{/* 返回按钮 */}
|
||||
<div className="bg-[#5C524B] absolute right-6 top-6 z-10">
|
||||
<Image src={getStaticImage('components/离开')} button onClick={onExit} />
|
||||
</div>
|
||||
|
||||
<Video src={FINAL_VIDEO_SRC} autoPlay onEnded={onVideoEnded} className="w-full h-full" />
|
||||
{finalVideoStage === 'ended' && (
|
||||
<div
|
||||
className="absolute w-[844px] h-[188px] bottom-5 right-160 bg-cover bg-center bg-no-repeat cursor-pointer"
|
||||
className="absolute w-[844px] h-[188px] bottom-5 right-160 bg-cover bg-center bg-no-repeat"
|
||||
style={{ backgroundImage: getBackgroundImage('rongdesheng/reflection/提示版') }}
|
||||
onClick={onExit}
|
||||
// onClick={onExit}
|
||||
>
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-2xl font-normal p-4">
|
||||
<div
|
||||
|
||||
@@ -48,7 +48,7 @@ export const QuestionPanel = ({
|
||||
}}
|
||||
onClick={() => onOptionSelect(option.id)}
|
||||
>
|
||||
<div className="flex justify-center items-center h-full relative">
|
||||
<div className="flex justify-start items-center h-full relative pl-5">
|
||||
<span className="text-[#6D6552] text-2xl">{option.text}</span>
|
||||
<div className="absolute -left-8 -top-6">
|
||||
{isSelected && option.isCorrect && (
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
export const HINT_TEXTS = {
|
||||
answeredPending: '选择成功,在场景中点击对应气泡查看决策影响',
|
||||
answeredAllViewed: '已了解所有决策影响,请点击提交查看结果',
|
||||
answeredPending: '选择成功,在场景中点击对应气泡查看决策影响。',
|
||||
answeredAllViewed: '已了解所有决策影响,请点击提交查看结果。',
|
||||
partialCorrect: '还有合适的决策未选择,请继续补充。',
|
||||
finalScene: '恭喜你成功闯过所有难关!点击合成按钮,解锁申新纺织厂从初创到自强的完整红色故事吧~',
|
||||
finalEnded: '恭喜你完成推演!保存勋章,和同学分享申新纺织厂从初创到自强的红色实业故事吧~',
|
||||
finalScene:
|
||||
'恭喜你成功闯过所有难关!点击“合成坚守图谱”按钮,解锁申新纺织厂从初创到自强的完整红色故事吧~',
|
||||
finalEnded: '恭喜你完成推演!和同学分享申新纺织厂从初创到自强的红色实业故事吧~',
|
||||
} as const
|
||||
|
||||
export type HintKey = keyof typeof HINT_TEXTS
|
||||
|
||||
@@ -13,11 +13,13 @@ import { HINT_AUDIOS, HINT_TEXTS } from './constants'
|
||||
import { MODULES, getSceneTitle } from './modules.data'
|
||||
import type { ModuleId, ModuleState, VideoPlayingState } from './types'
|
||||
import { setCourseTimeProgress } from '@/lib/utils/api-common'
|
||||
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
|
||||
|
||||
const RongdeshengReflection = () => {
|
||||
const navigate = useNavigate()
|
||||
const { moduleName } = useParams<{ moduleName: string }>()
|
||||
const starName = 'rongdesheng'
|
||||
const { triggerPuzzleAnimation } = usePuzzleAnimation()
|
||||
|
||||
// 持久化 store
|
||||
const persistedCompleted = useRongdeshengStore((s) => s.reflectionCompleted)
|
||||
@@ -223,6 +225,9 @@ const RongdeshengReflection = () => {
|
||||
[currentModuleId]: { ...prev[currentModuleId], completed: true },
|
||||
}))
|
||||
markReflectionCompleted(currentModuleId)
|
||||
if (currentModuleId === 'scene3') {
|
||||
triggerPuzzleAnimation(14)
|
||||
}
|
||||
}
|
||||
|
||||
// 结算页"下一场景":切到下一场景或进入最终合成场景
|
||||
@@ -296,7 +301,7 @@ const RongdeshengReflection = () => {
|
||||
{/* 返回按钮 */}
|
||||
<Image
|
||||
className="absolute right-5 top-5 z-10"
|
||||
src={getStaticImage('components/返回')}
|
||||
src={getStaticImage('components/离开')}
|
||||
button
|
||||
onClick={() => navigate(`/${moduleName}/${starName}/home`)}
|
||||
/>
|
||||
|
||||
@@ -11,17 +11,17 @@ export const MODULES: Module[] = [
|
||||
options: [
|
||||
{
|
||||
id: 'opt1-1',
|
||||
text: '选址上海杨树浦(临黄浦江,便于原料运输)',
|
||||
text: 'A.选址上海杨树浦(临黄浦江,便于原料运输)',
|
||||
isCorrect: true,
|
||||
videoResourceId: 'video1-1',
|
||||
},
|
||||
{
|
||||
id: 'opt1-2',
|
||||
text: '联合无锡帮华商与实业同仁招股筹款',
|
||||
text: 'B.联合无锡帮华商与实业同仁招股筹款',
|
||||
isCorrect: true,
|
||||
videoResourceId: 'video1-2',
|
||||
},
|
||||
{ id: 'opt1-3', text: '依附租界洋行,换取建厂便利与资金支持', isCorrect: false },
|
||||
{ id: 'opt1-3', text: 'C.依附租界洋行,换取建厂便利与资金支持', isCorrect: false },
|
||||
],
|
||||
videoResources: [
|
||||
{
|
||||
@@ -86,7 +86,7 @@ export const MODULES: Module[] = [
|
||||
'护厂抗争的胜利,是民族工业坚守爱国初心的体现,也是国人联合“反抗压迫、团结抗争”的印证!',
|
||||
resultImage: 'rongdesheng/reflection/场景2结算',
|
||||
wrongHint:
|
||||
'荣氏兄弟坚守"实业救国"初心,拒绝洋行干涉企业经营,不愿民族产业沦为外资附庸!试试其他方案吧~',
|
||||
'荣氏兄弟坚守“实业救国”初心,拒绝洋行干涉企业经营,不愿民族产业沦为外资附庸!试试其他方案吧~',
|
||||
audio: { wrongHint: '' },
|
||||
},
|
||||
{
|
||||
@@ -181,7 +181,8 @@ export const MODULES: Module[] = [
|
||||
{
|
||||
id: 'opt5-1',
|
||||
text: 'A.仿制外国先进设备,改良生产工艺',
|
||||
isCorrect: false,
|
||||
videoResourceId: 'video5-2',
|
||||
isCorrect: true,
|
||||
},
|
||||
{
|
||||
id: 'opt5-2',
|
||||
@@ -192,8 +193,7 @@ export const MODULES: Module[] = [
|
||||
{
|
||||
id: 'opt5-3',
|
||||
text: 'C.降价促销,以低价抢占市场',
|
||||
videoResourceId: 'video5-2',
|
||||
isCorrect: true,
|
||||
isCorrect: false,
|
||||
},
|
||||
],
|
||||
videoResources: [
|
||||
|
||||
Reference in new issue
Block a user