chore: 更新素材和组件优化

- 更新展品图片(鲁迅/毛泽东/孙中山/黄炎培/聂耳/张乐平故居)
- 新增点击查看图标、报纸素材
- 优化视频组件、拼图组件、展品组件
- 更新陈云音频资源
- 新增输入验证文档和工具函数
- 组件布局和动画优化

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.8 committed 2026-06-21 21:50:40 +08:00
1 parent 73d02eb62d
commit 73f6df92ce
130 files changed
+2345 -759

No files matched your search

+64 -2
View File
@@ -1,7 +1,11 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
import { setUserDefine } from '@/lib/utils/api-common'
import {
setUserDefine,
getUserDefineResult,
type UserDefineFetchStatus,
} from '@/lib/utils/api-common'
interface ChenyunStore {
// 存储每个场景(place)的已访问展品ID集合
@@ -38,6 +42,10 @@ interface ChenyunStore {
isMimianSolved: boolean
// 设置米棉之战解决状态
setMimianSolved: (solved: boolean) => void
// ========== 视频完成状态 ==========
completedVideos: Record<string, boolean>
markVideoCompleted: (videoKey: string) => void
}
// 将 Set 转换为数组以便序列化
@@ -69,6 +77,14 @@ const deserializeVisitedExhibits = (
return visitedExhibits
}
interface ChenyunPersistedState {
visitedExhibits?: Record<string, number[]>
isYinyuanSolved?: boolean
isMimianSolved?: boolean
lastVisitedPlace?: string | null
completedVideos?: Record<string, boolean>
}
export const useChenyunStore = create<ChenyunStore>()(
persist(
(set, get) => ({
@@ -107,7 +123,13 @@ export const useChenyunStore = create<ChenyunStore>()(
},
resetAll: () => {
set({ visitedExhibits: {} })
set({
visitedExhibits: {},
lastVisitedPlace: null,
isYinyuanSolved: false,
isMimianSolved: false,
completedVideos: {},
})
},
// ========== 断点续读 ==========
@@ -124,6 +146,13 @@ export const useChenyunStore = create<ChenyunStore>()(
setMimianSolved: (solved: boolean) => {
set({ isMimianSolved: solved })
},
completedVideos: {},
markVideoCompleted: (videoKey: string) => {
set((state) => ({
completedVideos: { ...state.completedVideos, [videoKey]: true },
}))
},
}),
{
name: STORE_NAMES.CHENYUN, // localStorage key
@@ -132,6 +161,7 @@ export const useChenyunStore = create<ChenyunStore>()(
isYinyuanSolved: state.isYinyuanSolved,
isMimianSolved: state.isMimianSolved,
lastVisitedPlace: state.lastVisitedPlace,
completedVideos: state.completedVideos,
}),
onRehydrateStorage: () => (state) => {
if (state) {
@@ -147,6 +177,9 @@ export const useChenyunStore = create<ChenyunStore>()(
if (state.lastVisitedPlace === undefined) {
state.lastVisitedPlace = null
}
if (!state.completedVideos || typeof state.completedVideos !== 'object') {
state.completedVideos = {}
}
}
},
}
@@ -165,6 +198,7 @@ export const saveChenyunStore = (): void => {
isYinyuanSolved: state.isYinyuanSolved,
isMimianSolved: state.isMimianSolved,
lastVisitedPlace: state.lastVisitedPlace,
completedVideos: state.completedVideos,
}
try {
@@ -173,3 +207,31 @@ export const saveChenyunStore = (): void => {
console.error('Failed to save Chenyun store', error)
}
}
export const applyChenyunPersistedState = (payload: ChenyunPersistedState): void => {
useChenyunStore.setState({
visitedExhibits: deserializeVisitedExhibits(payload.visitedExhibits),
isYinyuanSolved: payload.isYinyuanSolved ?? false,
isMimianSolved: payload.isMimianSolved ?? false,
lastVisitedPlace: payload.lastVisitedPlace ?? null,
completedVideos: payload.completedVideos ?? {},
})
}
export const loadChenyunStore = async (): Promise<UserDefineFetchStatus> => {
try {
const result = await getUserDefineResult(CHENYUN_LESSON_ID, CHENYUN_KEY)
if (result.status !== 'success') {
if (result.status === 'empty') {
useChenyunStore.getState().resetAll()
}
return result.status
}
applyChenyunPersistedState(JSON.parse(result.content) as ChenyunPersistedState)
return result.status
} catch (error) {
console.error('Failed to load Chenyun store', error)
return 'error'
}
}