import { create } from 'zustand' import { persist } from 'zustand/middleware' import { STORE_NAMES } from './storeNames' import { setUserDefine, getUserDefineResult, type UserDefineFetchStatus, } from '@/lib/utils/api-common' interface ChenyunStore { // 存储每个场景(place)的已访问展品ID集合 // 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... } visitedExhibits: Record> // 标记展品为已访问 markExhibitVisited: (place: string, exhibitId: number) => void // 检查展品是否已访问 isExhibitVisited: (place: string, exhibitId: number) => boolean // 获取某个场景已访问的展品ID数组 getVisitedExhibits: (place: string) => number[] // 重置某个场景的访问记录 resetPlace: (place: string) => void // 重置所有访问记录 resetAll: () => void // ========== 断点续读 ========== // 上次访问的场景(place1~place9) lastVisitedPlace: string | null setLastVisitedPlace: (place: string) => void // ========== 体悟模块状态 ========== // 银元之战是否已解决 isYinyuanSolved: boolean // 设置银元之战解决状态 setYinyuanSolved: (solved: boolean) => void // 米棉之战是否已解决 isMimianSolved: boolean // 设置米棉之战解决状态 setMimianSolved: (solved: boolean) => void // ========== 视频完成状态 ========== completedVideos: Record markVideoCompleted: (videoKey: string) => void } // 将 Set 转换为数组以便序列化 const serializeVisitedExhibits = ( visitedExhibits: Record> ): Record => { const serialized: Record = {} for (const [place, exhibits] of Object.entries(visitedExhibits)) { serialized[place] = Array.from(exhibits) } return serialized } // 将数组转换回 Set const deserializeVisitedExhibits = ( serialized: Record | undefined ): Record> => { if (!serialized || typeof serialized !== 'object') { return {} } const visitedExhibits: Record> = {} for (const [place, exhibits] of Object.entries(serialized)) { if (Array.isArray(exhibits)) { visitedExhibits[place] = new Set(exhibits) } else { visitedExhibits[place] = new Set() } } return visitedExhibits } interface ChenyunPersistedState { visitedExhibits?: Record isYinyuanSolved?: boolean isMimianSolved?: boolean lastVisitedPlace?: string | null completedVideos?: Record } export const useChenyunStore = create()( persist( (set, get) => ({ visitedExhibits: {}, markExhibitVisited: (place: string, exhibitId: number) => { set((state) => { const currentExhibits = state.visitedExhibits[place] || new Set() 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, isYinyuanSolved: false, isMimianSolved: false, completedVideos: {}, }) }, // ========== 断点续读 ========== lastVisitedPlace: null, setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }), // ========== 体悟模块状态 ========== isYinyuanSolved: false, setYinyuanSolved: (solved: boolean) => { set({ isYinyuanSolved: solved }) }, isMimianSolved: false, setMimianSolved: (solved: boolean) => { set({ isMimianSolved: solved }) }, completedVideos: {}, markVideoCompleted: (videoKey: string) => { set((state) => ({ completedVideos: { ...state.completedVideos, [videoKey]: true }, })) }, }), { name: STORE_NAMES.CHENYUN, // localStorage key partialize: (state) => ({ visitedExhibits: serializeVisitedExhibits(state.visitedExhibits), isYinyuanSolved: state.isYinyuanSolved, isMimianSolved: state.isMimianSolved, lastVisitedPlace: state.lastVisitedPlace, completedVideos: state.completedVideos, }), onRehydrateStorage: () => (state) => { if (state) { // 确保反序列化后的数据格式正确 state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any) // 确保解决状态有默认值 if (state.isYinyuanSolved === undefined) { state.isYinyuanSolved = false } if (state.isMimianSolved === undefined) { state.isMimianSolved = false } if (state.lastVisitedPlace === undefined) { state.lastVisitedPlace = null } if (!state.completedVideos || typeof state.completedVideos !== 'object') { state.completedVideos = {} } } }, } ) ) // ========== 易班平台数据保存 ========== export const CHENYUN_LESSON_ID = 'Cheny_duihua' export const CHENYUN_KEY = 22 export const saveChenyunStore = (): void => { const state = useChenyunStore.getState() const payload = { visitedExhibits: serializeVisitedExhibits(state.visitedExhibits), isYinyuanSolved: state.isYinyuanSolved, isMimianSolved: state.isMimianSolved, lastVisitedPlace: state.lastVisitedPlace, completedVideos: state.completedVideos, } try { setUserDefine(CHENYUN_LESSON_ID, CHENYUN_KEY, JSON.stringify(payload)) } catch (error) { 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 => { 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' } }