Commit non-media application and asset changes while leaving audio, video, and environment files out of version control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
254 lines
8.0 KiB
TypeScript
254 lines
8.0 KiB
TypeScript
import { create } from 'zustand'
|
|
import { persist } from 'zustand/middleware'
|
|
import { STORE_NAMES } from './storeNames'
|
|
import { setUserDefine } from '@/lib/utils/api-common'
|
|
|
|
interface NieerStore {
|
|
// 存储每个场景(place)的已访问展品ID集合
|
|
// 格式:{ place1: Set<1,2>, place2: Set<1,2>, ... }
|
|
visitedExhibits: Record<string, Set<number>>
|
|
|
|
// 标记展品为已访问
|
|
markExhibitVisited: (place: string, exhibitId: number) => void
|
|
|
|
// 检查展品是否已访问
|
|
isExhibitVisited: (place: string, exhibitId: number) => boolean
|
|
|
|
// 获取某个场景已访问的展品ID数组
|
|
getVisitedExhibits: (place: string) => number[]
|
|
|
|
// 重置某个场景的访问记录
|
|
resetPlace: (place: string) => void
|
|
|
|
// 重置所有访问记录
|
|
resetAll: () => void
|
|
|
|
// ========== 断点续读 ==========
|
|
lastVisitedPlace: string | null
|
|
setLastVisitedPlace: (place: string) => void
|
|
|
|
// ========== 体悟模块状态 ==========
|
|
// 存储每个reflection场景的已完成cursor ID集合
|
|
visitedReflectionCursors: Record<string, Set<number>>
|
|
|
|
// 标记reflection cursor为已完成
|
|
markReflectionCursorVisited: (scene: string, cursorId: number) => void
|
|
|
|
// 检查reflection cursor是否已完成
|
|
isReflectionCursorVisited: (scene: string, cursorId: number) => boolean
|
|
|
|
// 获取某个reflection场景已完成的cursor ID数组
|
|
getVisitedReflectionCursors: (scene: string) => number[]
|
|
|
|
// 重置某个reflection场景的cursor记录
|
|
resetReflectionScene: (scene: string) => void
|
|
|
|
// 重置所有reflection cursor记录
|
|
resetAllReflectionCursors: () => void
|
|
|
|
// ========== 旅行箱成果 ==========
|
|
// 每个 scene 对应一次分享后的成果快照(用于旅行箱展示)
|
|
reflectionWorks: Record<number, Record<string, unknown>>
|
|
setReflectionWork: (sceneId: number, payload: Record<string, unknown>) => void
|
|
resetReflectionWorks: () => void
|
|
|
|
// 是否已经看过体悟首页学习引导
|
|
hasSeenReflectionGuide: boolean
|
|
markReflectionGuideSeen: () => void
|
|
|
|
// 是否已经看过旅行箱提示
|
|
hasSeenBoxHint: boolean
|
|
markBoxHintSeen: () => void
|
|
}
|
|
|
|
// 将 Set 转换为数组以便序列化
|
|
const serializeVisitedExhibits = (
|
|
visitedExhibits: Record<string, Set<number>>
|
|
): Record<string, number[]> => {
|
|
const serialized: Record<string, number[]> = {}
|
|
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
|
|
serialized[place] = Array.from(exhibits)
|
|
}
|
|
return serialized
|
|
}
|
|
|
|
// 将数组转换回 Set
|
|
const deserializeVisitedExhibits = (
|
|
serialized: Record<string, number[]> | undefined
|
|
): Record<string, Set<number>> => {
|
|
if (!serialized || typeof serialized !== 'object') {
|
|
return {}
|
|
}
|
|
const visitedExhibits: Record<string, Set<number>> = {}
|
|
for (const [place, exhibits] of Object.entries(serialized)) {
|
|
if (Array.isArray(exhibits)) {
|
|
visitedExhibits[place] = new Set(exhibits)
|
|
} else {
|
|
visitedExhibits[place] = new Set()
|
|
}
|
|
}
|
|
return visitedExhibits
|
|
}
|
|
|
|
export const useNieerStore = create<NieerStore>()(
|
|
persist(
|
|
(set, get) => ({
|
|
visitedExhibits: {},
|
|
|
|
markExhibitVisited: (place: string, exhibitId: number) => {
|
|
set((state) => {
|
|
const currentExhibits = state.visitedExhibits[place] || new Set<number>()
|
|
if (!currentExhibits.has(exhibitId)) {
|
|
const newExhibits = {
|
|
...state.visitedExhibits,
|
|
[place]: new Set([...currentExhibits, exhibitId]),
|
|
}
|
|
return { visitedExhibits: newExhibits }
|
|
}
|
|
return state
|
|
})
|
|
},
|
|
|
|
isExhibitVisited: (place: string, exhibitId: number) => {
|
|
const { visitedExhibits } = get()
|
|
return visitedExhibits[place]?.has(exhibitId) || false
|
|
},
|
|
|
|
getVisitedExhibits: (place: string) => {
|
|
const { visitedExhibits } = get()
|
|
return Array.from(visitedExhibits[place] || [])
|
|
},
|
|
|
|
resetPlace: (place: string) => {
|
|
set((state) => {
|
|
const newExhibits = { ...state.visitedExhibits }
|
|
delete newExhibits[place]
|
|
return { visitedExhibits: newExhibits }
|
|
})
|
|
},
|
|
|
|
resetAll: () => {
|
|
set({ visitedExhibits: {} })
|
|
},
|
|
|
|
lastVisitedPlace: null,
|
|
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
|
|
|
// ========== 体悟模块状态 ==========
|
|
visitedReflectionCursors: {},
|
|
reflectionWorks: {},
|
|
hasSeenReflectionGuide: false,
|
|
hasSeenBoxHint: false,
|
|
|
|
markReflectionCursorVisited: (scene: string, cursorId: number) => {
|
|
set((state) => {
|
|
const currentCursors = state.visitedReflectionCursors[scene] || new Set<number>()
|
|
if (!currentCursors.has(cursorId)) {
|
|
const newCursors = {
|
|
...state.visitedReflectionCursors,
|
|
[scene]: new Set([...currentCursors, cursorId]),
|
|
}
|
|
return { visitedReflectionCursors: newCursors }
|
|
}
|
|
return state
|
|
})
|
|
},
|
|
|
|
isReflectionCursorVisited: (scene: string, cursorId: number) => {
|
|
const { visitedReflectionCursors } = get()
|
|
return visitedReflectionCursors[scene]?.has(cursorId) || false
|
|
},
|
|
|
|
getVisitedReflectionCursors: (scene: string) => {
|
|
const { visitedReflectionCursors } = get()
|
|
return Array.from(visitedReflectionCursors[scene] || [])
|
|
},
|
|
|
|
resetReflectionScene: (scene: string) => {
|
|
set((state) => {
|
|
const newCursors = { ...state.visitedReflectionCursors }
|
|
delete newCursors[scene]
|
|
return { visitedReflectionCursors: newCursors }
|
|
})
|
|
},
|
|
|
|
resetAllReflectionCursors: () => {
|
|
set({ visitedReflectionCursors: {} })
|
|
},
|
|
|
|
setReflectionWork: (sceneId: number, payload: Record<string, unknown>) => {
|
|
set((state) => ({
|
|
reflectionWorks: {
|
|
...state.reflectionWorks,
|
|
[sceneId]: {
|
|
...state.reflectionWorks[sceneId],
|
|
...payload,
|
|
updatedAt: Date.now(),
|
|
},
|
|
},
|
|
}))
|
|
},
|
|
|
|
resetReflectionWorks: () => {
|
|
set({ reflectionWorks: {} })
|
|
},
|
|
|
|
markReflectionGuideSeen: () => {
|
|
set({ hasSeenReflectionGuide: true })
|
|
},
|
|
|
|
markBoxHintSeen: () => {
|
|
set({ hasSeenBoxHint: true })
|
|
},
|
|
}),
|
|
{
|
|
name: STORE_NAMES.NIEER,
|
|
partialize: (state) => ({
|
|
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
|
visitedReflectionCursors: serializeVisitedExhibits(state.visitedReflectionCursors),
|
|
lastVisitedPlace: state.lastVisitedPlace,
|
|
reflectionWorks: state.reflectionWorks,
|
|
hasSeenReflectionGuide: state.hasSeenReflectionGuide,
|
|
hasSeenBoxHint: state.hasSeenBoxHint,
|
|
}),
|
|
onRehydrateStorage: () => (state) => {
|
|
if (state) {
|
|
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
|
|
state.visitedReflectionCursors = deserializeVisitedExhibits(
|
|
state.visitedReflectionCursors as any
|
|
)
|
|
if (state.lastVisitedPlace === undefined) {
|
|
state.lastVisitedPlace = null
|
|
}
|
|
if (!state.reflectionWorks || typeof state.reflectionWorks !== 'object') {
|
|
state.reflectionWorks = {}
|
|
}
|
|
if ((state as any).hasSeenReflectionGuide === undefined) {
|
|
;(state as any).hasSeenReflectionGuide = false
|
|
}
|
|
if ((state as any).hasSeenBoxHint === undefined) {
|
|
;(state as any).hasSeenBoxHint = false
|
|
}
|
|
}
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
// ========== 易班平台数据保存(与陈云对齐) ==========
|
|
export const NIEER_LESSON_ID = 'Nieer_duihua'
|
|
export const NIEER_KEY = 5
|
|
|
|
export const saveNieerStore = (): void => {
|
|
const state = useNieerStore.getState()
|
|
const payload = {
|
|
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
|
lastVisitedPlace: state.lastVisitedPlace,
|
|
}
|
|
try {
|
|
setUserDefine(NIEER_LESSON_ID, NIEER_KEY, JSON.stringify(payload))
|
|
} catch (error) {
|
|
console.error('Failed to save Nieer store', error)
|
|
}
|
|
}
|