- 更新 package.json 和 package-lock.json 中的依赖项 - 新增/更新陈云、黄炎培、鲁迅、孙中山的音频文件 - 新增场景和组件的图片资源 - 清理未使用的音频和图片文件 - 优化多个组件和页面的实现逻辑
176 lines
5.3 KiB
TypeScript
176 lines
5.3 KiB
TypeScript
import { create } from 'zustand'
|
||
import { persist } from 'zustand/middleware'
|
||
import { STORE_NAMES } from './storeNames'
|
||
import { setUserDefine } from '@/lib/utils/api-common'
|
||
|
||
interface ChenyunStore {
|
||
// 存储每个场景(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
|
||
|
||
// ========== 断点续读 ==========
|
||
// 上次访问的场景(place1~place9)
|
||
lastVisitedPlace: string | null
|
||
setLastVisitedPlace: (place: string) => void
|
||
|
||
// ========== 体悟模块状态 ==========
|
||
// 银元之战是否已解决
|
||
isYinyuanSolved: boolean
|
||
// 设置银元之战解决状态
|
||
setYinyuanSolved: (solved: boolean) => void
|
||
|
||
// 米棉之战是否已解决
|
||
isMimianSolved: boolean
|
||
// 设置米棉之战解决状态
|
||
setMimianSolved: (solved: boolean) => 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 useChenyunStore = create<ChenyunStore>()(
|
||
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 }),
|
||
|
||
// ========== 体悟模块状态 ==========
|
||
isYinyuanSolved: false,
|
||
setYinyuanSolved: (solved: boolean) => {
|
||
set({ isYinyuanSolved: solved })
|
||
},
|
||
|
||
isMimianSolved: false,
|
||
setMimianSolved: (solved: boolean) => {
|
||
set({ isMimianSolved: solved })
|
||
},
|
||
}),
|
||
{
|
||
name: STORE_NAMES.CHENYUN, // localStorage key
|
||
partialize: (state) => ({
|
||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||
isYinyuanSolved: state.isYinyuanSolved,
|
||
isMimianSolved: state.isMimianSolved,
|
||
lastVisitedPlace: state.lastVisitedPlace,
|
||
}),
|
||
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
|
||
}
|
||
}
|
||
},
|
||
}
|
||
)
|
||
)
|
||
|
||
// ========== 易班平台数据保存 ==========
|
||
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,
|
||
}
|
||
|
||
try {
|
||
setUserDefine(CHENYUN_LESSON_ID, CHENYUN_KEY, JSON.stringify(payload))
|
||
} catch (error) {
|
||
console.error('Failed to save Chenyun store', error)
|
||
}
|
||
}
|