更新依赖项及历史人物相关内容资源
- 更新 package.json 和 package-lock.json 中的依赖项 - 新增/更新陈云、黄炎培、鲁迅、孙中山的音频文件 - 新增场景和组件的图片资源 - 清理未使用的音频和图片文件 - 优化多个组件和页面的实现逻辑
This commit is contained in:
1 parent
b1d55b9464
commit
e0fa47d21a
629 files changed
+24390
-2991
No files matched your search
@@ -154,8 +154,8 @@ export const useChenyunStore = create<ChenyunStore>()(
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存 ==========
|
||||
const CHENYUN_LESSON_ID = 'Cheny_duihua'
|
||||
const CHENYUN_KEY = 22
|
||||
export const CHENYUN_LESSON_ID = 'Cheny_duihua'
|
||||
export const CHENYUN_KEY = 22
|
||||
|
||||
export const saveChenyunStore = (): void => {
|
||||
const state = useChenyunStore.getState()
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
|
||||
export interface Clue {
|
||||
id: string // 格式: "sunzhongshan/reflection/scene1/clue1"
|
||||
name: string // 线索名称,如 "线索1"
|
||||
collected: boolean // 是否已收集
|
||||
collectedAt?: number // 收集时间戳
|
||||
}
|
||||
|
||||
interface ClueStore {
|
||||
clues: Clue[]
|
||||
// 收集线索
|
||||
collectClue: (id: string, name: string) => void
|
||||
// 检查线索是否已收集
|
||||
isClueCollected: (id: string) => boolean
|
||||
// 获取指定场景的所有线索
|
||||
getSceneClues: (sceneId: string) => Clue[]
|
||||
// 重置指定场景的线索
|
||||
resetSceneClues: (sceneId: string) => void
|
||||
// 重置所有线索
|
||||
resetAll: () => void
|
||||
}
|
||||
|
||||
export const useClueStore = create<ClueStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
clues: [],
|
||||
|
||||
collectClue: (id: string, name: string) => {
|
||||
const { clues } = get()
|
||||
const existingClue = clues.find((c) => c.id === id)
|
||||
|
||||
if (!existingClue) {
|
||||
set({
|
||||
clues: [
|
||||
...clues,
|
||||
{
|
||||
id,
|
||||
name,
|
||||
collected: true,
|
||||
collectedAt: Date.now(),
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if (!existingClue.collected) {
|
||||
set({
|
||||
clues: clues.map((c) =>
|
||||
c.id === id ? { ...c, collected: true, collectedAt: Date.now() } : c
|
||||
),
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
isClueCollected: (id: string) => {
|
||||
const { clues } = get()
|
||||
return clues.some((c) => c.id === id && c.collected)
|
||||
},
|
||||
|
||||
getSceneClues: (sceneId: string) => {
|
||||
const { clues } = get()
|
||||
return clues.filter((c) => c.id.startsWith(sceneId))
|
||||
},
|
||||
|
||||
resetSceneClues: (sceneId: string) => {
|
||||
const { clues } = get()
|
||||
set({
|
||||
clues: clues.filter((c) => !c.id.startsWith(sceneId)),
|
||||
})
|
||||
},
|
||||
|
||||
resetAll: () => {
|
||||
set({ clues: [] })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.CLUE, // localStorage key
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -1,12 +1,30 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine, getUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
interface HuangyanpeiStore {
|
||||
// 存储每个场景(place)的已访问展品ID集合
|
||||
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
|
||||
visitedExhibits: Record<string, Set<number>>
|
||||
|
||||
// 体悟模块“进入下一场景”引导弹窗是否已展示
|
||||
shownReflectionCompletionGuides: Record<string, boolean>
|
||||
markReflectionCompletionGuideShown: (scene: string) => void
|
||||
hasShownReflectionCompletionGuide: (scene: string) => boolean
|
||||
|
||||
// scene2 的专业选择缓存
|
||||
reflectionScene2Choice: '铁工科' | '商科' | null
|
||||
setReflectionScene2Choice: (choice: '铁工科' | '商科' | null) => void
|
||||
|
||||
// 上次访问的对话场景(place1~place6)
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
|
||||
// 体悟模块当前路由(reflection / reflection2 / reflection3 / reflection4)
|
||||
reflectionRoute: string
|
||||
setReflectionRoute: (route: string) => void
|
||||
|
||||
// 标记展品为已访问
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
|
||||
@@ -76,6 +94,10 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
shownReflectionCompletionGuides: {},
|
||||
reflectionScene2Choice: null,
|
||||
lastVisitedPlace: null,
|
||||
reflectionRoute: 'reflection',
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
@@ -113,6 +135,20 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
|
||||
set({ visitedExhibits: {} })
|
||||
},
|
||||
|
||||
markReflectionCompletionGuideShown: (scene: string) => {
|
||||
set((state) => ({
|
||||
shownReflectionCompletionGuides: {
|
||||
...state.shownReflectionCompletionGuides,
|
||||
[scene]: true,
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
hasShownReflectionCompletionGuide: (scene: string) => {
|
||||
const { shownReflectionCompletionGuides } = get()
|
||||
return shownReflectionCompletionGuides[scene] || false
|
||||
},
|
||||
|
||||
// ========== 体悟模块状态 ==========
|
||||
visitedReflectionCursors: {},
|
||||
|
||||
@@ -151,12 +187,20 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
|
||||
resetAllReflectionCursors: () => {
|
||||
set({ visitedReflectionCursors: {} })
|
||||
},
|
||||
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
setReflectionRoute: (route: string) => set({ reflectionRoute: route }),
|
||||
setReflectionScene2Choice: (choice) => set({ reflectionScene2Choice: choice }),
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.HUANGYANPEI, // localStorage key
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
visitedReflectionCursors: serializeVisitedExhibits(state.visitedReflectionCursors),
|
||||
shownReflectionCompletionGuides: state.shownReflectionCompletionGuides,
|
||||
reflectionScene2Choice: state.reflectionScene2Choice,
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
@@ -165,8 +209,72 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
|
||||
state.visitedReflectionCursors = deserializeVisitedExhibits(
|
||||
state.visitedReflectionCursors as any
|
||||
)
|
||||
if ((state as any).shownReflectionCompletionGuides === undefined) {
|
||||
;(state as any).shownReflectionCompletionGuides = {}
|
||||
}
|
||||
if ((state as any).reflectionScene2Choice === undefined) {
|
||||
;(state as any).reflectionScene2Choice = null
|
||||
}
|
||||
if ((state as any).lastVisitedPlace === undefined) {
|
||||
;(state as any).lastVisitedPlace = null
|
||||
}
|
||||
if ((state as any).reflectionRoute === undefined) {
|
||||
;(state as any).reflectionRoute = 'reflection'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(对话 + 体悟模块) ==========
|
||||
export const HUANGYANPEI_LESSON_ID = 'Huangyp_duihua'
|
||||
export const HUANGYANPEI_KEY = 10
|
||||
|
||||
export const saveHuangyanpeiStore = (): void => {
|
||||
const state = useHuangyanpeiStore.getState()
|
||||
|
||||
const payload = {
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
shownReflectionCompletionGuides: state.shownReflectionCompletionGuides,
|
||||
reflectionScene2Choice: state.reflectionScene2Choice,
|
||||
}
|
||||
|
||||
try {
|
||||
setUserDefine(HUANGYANPEI_LESSON_ID, HUANGYANPEI_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Huangyanpei store', error)
|
||||
}
|
||||
}
|
||||
|
||||
export const loadHuangyanpeiStore = async (): Promise<void> => {
|
||||
try {
|
||||
const raw = await getUserDefine(HUANGYANPEI_LESSON_ID, HUANGYANPEI_KEY)
|
||||
if (!raw) {
|
||||
return
|
||||
}
|
||||
|
||||
const payload = JSON.parse(raw) as {
|
||||
visitedExhibits?: Record<string, number[]>
|
||||
lastVisitedPlace?: string | null
|
||||
reflectionRoute?: string
|
||||
shownReflectionCompletionGuides?: Record<string, boolean>
|
||||
reflectionScene2Choice?: '铁工科' | '商科' | null
|
||||
}
|
||||
|
||||
useHuangyanpeiStore.setState((state) => ({
|
||||
visitedExhibits: payload.visitedExhibits
|
||||
? deserializeVisitedExhibits(payload.visitedExhibits)
|
||||
: state.visitedExhibits,
|
||||
lastVisitedPlace: payload.lastVisitedPlace ?? state.lastVisitedPlace,
|
||||
reflectionRoute: payload.reflectionRoute ?? state.reflectionRoute,
|
||||
shownReflectionCompletionGuides:
|
||||
payload.shownReflectionCompletionGuides ?? state.shownReflectionCompletionGuides,
|
||||
reflectionScene2Choice: payload.reflectionScene2Choice ?? state.reflectionScene2Choice,
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Failed to load Huangyanpei store', error)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
export { useChenyunStore } from './chenyunStore'
|
||||
export { useHuangyanpeiStore } from './huangyanpeiStore'
|
||||
export { useLuxunStore } from './luxunStore'
|
||||
export { useNieerStore } from './nieerStore'
|
||||
export { useSunzhongshanStore } from './sunzhongshanStore'
|
||||
export { useClueStore } from './clueStore'
|
||||
export { useZhanglepingStore } from './zhanglepingStore'
|
||||
export { usePuzzleStore } from './puzzleStore'
|
||||
export { useUserStore } from './userStore'
|
||||
export { STORE_NAMES } from './storeNames'
|
||||
@@ -1,12 +1,21 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
interface LuxunStore {
|
||||
// 存储每个场景(place)的已访问展品ID集合
|
||||
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
|
||||
visitedExhibits: Record<string, Set<number>>
|
||||
|
||||
// 体悟模块当前路由(reflection / reflection2 / reflection3)
|
||||
reflectionRoute: string
|
||||
setReflectionRoute: (route: string) => void
|
||||
|
||||
// 上次访问的场景(place1~place4)
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
|
||||
// 标记展品为已访问
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
|
||||
@@ -56,6 +65,8 @@ export const useLuxunStore = create<LuxunStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
reflectionRoute: 'reflection',
|
||||
lastVisitedPlace: null,
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
@@ -92,17 +103,48 @@ export const useLuxunStore = create<LuxunStore>()(
|
||||
resetAll: () => {
|
||||
set({ visitedExhibits: {} })
|
||||
},
|
||||
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
setReflectionRoute: (route: string) => set({ reflectionRoute: route }),
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.LUXUN,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
|
||||
if (state.lastVisitedPlace === undefined) {
|
||||
state.lastVisitedPlace = null
|
||||
}
|
||||
if ((state as any).reflectionRoute === undefined) {
|
||||
;(state as any).reflectionRoute = 'reflection'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(对话模块) ==========
|
||||
export const LUXUN_LESSON_ID = 'Lux_duihua'
|
||||
export const LUXUN_KEY = 7
|
||||
|
||||
export const saveLuxunStore = (): void => {
|
||||
const state = useLuxunStore.getState()
|
||||
|
||||
const payload = {
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
}
|
||||
|
||||
try {
|
||||
setUserDefine(LUXUN_LESSON_ID, LUXUN_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Luxun store', error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
interface MaozedongStore {
|
||||
// 每个场景(placeX)的已访问展品ID集合
|
||||
visitedExhibits: Record<string, Set<number>>
|
||||
|
||||
// 上次访问的场景(place1~place4)
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
|
||||
// 标记展品为已访问
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
// 检查展品是否已访问
|
||||
isExhibitVisited: (place: string, exhibitId: number) => boolean
|
||||
// 获取某个场景已访问的展品ID数组
|
||||
getVisitedExhibits: (place: string) => number[]
|
||||
// 清空某个场景
|
||||
resetPlace: (place: string) => void
|
||||
// 重置所有
|
||||
resetAll: () => void
|
||||
}
|
||||
|
||||
const serializeVisited = (data: Record<string, Set<number>>): Record<string, number[]> => {
|
||||
const out: Record<string, number[]> = {}
|
||||
for (const [k, v] of Object.entries(data)) out[k] = Array.from(v)
|
||||
return out
|
||||
}
|
||||
|
||||
const deserializeVisited = (
|
||||
data: Record<string, number[]> | undefined
|
||||
): Record<string, Set<number>> => {
|
||||
if (!data || typeof data !== 'object') return {}
|
||||
const out: Record<string, Set<number>> = {}
|
||||
for (const [k, v] of Object.entries(data)) out[k] = Array.isArray(v) ? new Set(v) : new Set()
|
||||
return out
|
||||
}
|
||||
|
||||
export const useMaozedongStore = create<MaozedongStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
const current = state.visitedExhibits[place] || new Set<number>()
|
||||
if (current.has(exhibitId)) return state
|
||||
return {
|
||||
visitedExhibits: {
|
||||
...state.visitedExhibits,
|
||||
[place]: new Set([...current, exhibitId]),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
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 next = { ...state.visitedExhibits }
|
||||
delete next[place]
|
||||
return { visitedExhibits: next }
|
||||
})
|
||||
},
|
||||
|
||||
resetAll: () => set({ visitedExhibits: {}, lastVisitedPlace: null }),
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.MAOZEDONG,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisited(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
state.visitedExhibits = deserializeVisited(state.visitedExhibits as any)
|
||||
if (state.lastVisitedPlace === undefined) state.lastVisitedPlace = null
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(参考陈云) ==========
|
||||
export const MAOZEDONG_LESSON_ID = 'Maozd_duihua'
|
||||
export const MAOZEDONG_KEY = 1
|
||||
|
||||
export const saveMaozedongStore = (): void => {
|
||||
const state = useMaozedongStore.getState()
|
||||
const payload = {
|
||||
visitedExhibits: serializeVisited(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
}
|
||||
try {
|
||||
setUserDefine(MAOZEDONG_LESSON_ID, MAOZEDONG_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Maozedong store', error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 将 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: {},
|
||||
|
||||
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: {} })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.NIEER,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
visitedReflectionCursors: serializeVisitedExhibits(state.visitedReflectionCursors),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionWorks: state.reflectionWorks,
|
||||
}),
|
||||
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 = {}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(与陈云对齐) ==========
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
interface RongdeshengStore {
|
||||
// 对话模块:每个 place 的已访问展品 ID 集合
|
||||
visitedExhibits: Record<string, Set<number>>
|
||||
|
||||
// 断点续读:上次访问的对话模块 place(place1~place4)
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
|
||||
// 体悟模块:每个场景的完成状态(用于解锁下一场景)
|
||||
reflectionCompleted: Record<string, boolean>
|
||||
// 体悟模块:每个场景已观看的视频
|
||||
reflectionVideoViewed: Record<string, Record<string, boolean>>
|
||||
// 体悟模块:是否已完成最终合成(看完 rongdesheng_2_11)
|
||||
reflectionFinalCompleted: boolean
|
||||
// 体悟模块:是否已显示过学习引导弹窗
|
||||
reflectionGuideShown: boolean
|
||||
|
||||
// 标记某个体悟场景的视频已观看
|
||||
markReflectionVideoViewed: (sceneId: string, videoId: string) => void
|
||||
// 标记某个体悟场景完成
|
||||
markReflectionCompleted: (sceneId: string) => void
|
||||
// 标记最终合成完成
|
||||
markReflectionFinalCompleted: () => void
|
||||
// 标记学习引导已显示
|
||||
markReflectionGuideShown: () => void
|
||||
// 重置体悟进度
|
||||
resetReflection: () => void
|
||||
|
||||
// 标记展品为已访问
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
|
||||
// 检查展品是否已访问
|
||||
isExhibitVisited: (place: string, exhibitId: number) => boolean
|
||||
|
||||
// 获取某个场景已访问展品 ID 数组
|
||||
getVisitedExhibits: (place: string) => number[]
|
||||
|
||||
// 重置某个场景的访问记录
|
||||
resetPlace: (place: string) => void
|
||||
|
||||
// 重置所有访问记录
|
||||
resetAll: () => void
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)) {
|
||||
visitedExhibits[place] = Array.isArray(exhibits) ? new Set(exhibits) : new Set()
|
||||
}
|
||||
return visitedExhibits
|
||||
}
|
||||
|
||||
export const useRongdeshengStore = create<RongdeshengStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionCompleted: {},
|
||||
reflectionVideoViewed: {},
|
||||
reflectionFinalCompleted: false,
|
||||
reflectionGuideShown: false,
|
||||
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
|
||||
markReflectionVideoViewed: (sceneId: string, videoId: string) => {
|
||||
set((state) => ({
|
||||
reflectionVideoViewed: {
|
||||
...state.reflectionVideoViewed,
|
||||
[sceneId]: {
|
||||
...(state.reflectionVideoViewed[sceneId] || {}),
|
||||
[videoId]: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
markReflectionCompleted: (sceneId: string) => {
|
||||
set((state) => {
|
||||
if (state.reflectionCompleted[sceneId]) return state
|
||||
return {
|
||||
reflectionCompleted: {
|
||||
...state.reflectionCompleted,
|
||||
[sceneId]: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
markReflectionFinalCompleted: () => set({ reflectionFinalCompleted: true }),
|
||||
|
||||
markReflectionGuideShown: () => set({ reflectionGuideShown: true }),
|
||||
|
||||
resetReflection: () =>
|
||||
set({
|
||||
reflectionCompleted: {},
|
||||
reflectionVideoViewed: {},
|
||||
reflectionFinalCompleted: false,
|
||||
reflectionGuideShown: false,
|
||||
}),
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
const current = state.visitedExhibits[place] || new Set<number>()
|
||||
if (current.has(exhibitId)) return state
|
||||
return {
|
||||
visitedExhibits: {
|
||||
...state.visitedExhibits,
|
||||
[place]: new Set([...current, exhibitId]),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
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 next = { ...state.visitedExhibits }
|
||||
delete next[place]
|
||||
return { visitedExhibits: next }
|
||||
})
|
||||
},
|
||||
|
||||
resetAll: () =>
|
||||
set({
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionCompleted: {},
|
||||
reflectionVideoViewed: {},
|
||||
reflectionFinalCompleted: false,
|
||||
reflectionGuideShown: false,
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.RONGDESHENG,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionCompleted: state.reflectionCompleted,
|
||||
reflectionVideoViewed: state.reflectionVideoViewed,
|
||||
reflectionFinalCompleted: state.reflectionFinalCompleted,
|
||||
reflectionGuideShown: state.reflectionGuideShown,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (!state) return
|
||||
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
|
||||
if (state.lastVisitedPlace === undefined) state.lastVisitedPlace = null
|
||||
if (!state.reflectionCompleted || typeof state.reflectionCompleted !== 'object') {
|
||||
state.reflectionCompleted = {}
|
||||
}
|
||||
if (!state.reflectionVideoViewed || typeof state.reflectionVideoViewed !== 'object') {
|
||||
state.reflectionVideoViewed = {}
|
||||
}
|
||||
if (typeof state.reflectionFinalCompleted !== 'boolean') {
|
||||
state.reflectionFinalCompleted = false
|
||||
}
|
||||
if (typeof state.reflectionGuideShown !== 'boolean') {
|
||||
state.reflectionGuideShown = false
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存 ==========
|
||||
export const RONGDESHENG_LESSON_ID = 'Rongds_duihua'
|
||||
export const RONGDESHENG_KEY = 19
|
||||
|
||||
export const saveRongdeshengStore = (): void => {
|
||||
const state = useRongdeshengStore.getState()
|
||||
const payload = {
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionCompleted: state.reflectionCompleted,
|
||||
reflectionVideoViewed: state.reflectionVideoViewed,
|
||||
reflectionFinalCompleted: state.reflectionFinalCompleted,
|
||||
reflectionGuideShown: state.reflectionGuideShown,
|
||||
}
|
||||
try {
|
||||
setUserDefine(RONGDESHENG_LESSON_ID, RONGDESHENG_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Rongdesheng store', error)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ export const STORE_NAMES = {
|
||||
LUXUN: 'luxun-storage',
|
||||
HUANGYANPEI: 'huangyanpei-storage',
|
||||
CHENYUN: 'chenyun-storage',
|
||||
MAOZEDONG: 'maozedong-storage',
|
||||
NIEER: 'nieer-storage',
|
||||
ZHANGLEPING: 'zhangleping-storage',
|
||||
RONGDESHENG: 'rongdesheng-storage',
|
||||
PUZZLE: 'puzzle-storage',
|
||||
CLUE: 'clue-storage',
|
||||
} as const
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
export interface Clue {
|
||||
id: string
|
||||
name: string
|
||||
collected: boolean
|
||||
collectedAt?: number
|
||||
}
|
||||
|
||||
interface SunzhongshanStore {
|
||||
// 存储每个场景(place)的已访问展品ID集合
|
||||
@@ -10,6 +18,21 @@ interface SunzhongshanStore {
|
||||
// 体悟模块通用布尔状态(引导是否展示、场景是否完成等)
|
||||
reflectionFlags: Record<string, boolean>
|
||||
|
||||
// 上次访问的场景(place1~place4)
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
|
||||
// 体悟模块当前路由(reflection、reflection1、reflection2、reflection3)
|
||||
reflectionRoute: string
|
||||
setReflectionRoute: (route: string) => void
|
||||
|
||||
// ========== 线索系统(原 clueStore,并入孙中山) ==========
|
||||
clues: Clue[]
|
||||
collectClue: (id: string, name: string) => void
|
||||
isClueCollected: (id: string) => boolean
|
||||
getSceneClues: (sceneId: string) => Clue[]
|
||||
resetSceneClues: (sceneId: string) => void
|
||||
|
||||
// 标记展品为已访问
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
|
||||
@@ -66,6 +89,9 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
reflectionFlags: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionRoute: 'reflection',
|
||||
clues: [],
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
@@ -100,7 +126,13 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
|
||||
},
|
||||
|
||||
resetAll: () => {
|
||||
set({ visitedExhibits: {}, reflectionFlags: {} })
|
||||
set({
|
||||
visitedExhibits: {},
|
||||
reflectionFlags: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionRoute: 'reflection',
|
||||
clues: [],
|
||||
})
|
||||
},
|
||||
|
||||
setReflectionFlag: (key: string, value: boolean) => {
|
||||
@@ -116,12 +148,60 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
|
||||
const { reflectionFlags } = get()
|
||||
return !!reflectionFlags[key]
|
||||
},
|
||||
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
setReflectionRoute: (route: string) => set({ reflectionRoute: route }),
|
||||
|
||||
collectClue: (id: string, name: string) => {
|
||||
const { clues } = get()
|
||||
const existingClue = clues.find((c) => c.id === id)
|
||||
|
||||
if (!existingClue) {
|
||||
set({
|
||||
clues: [
|
||||
...clues,
|
||||
{
|
||||
id,
|
||||
name,
|
||||
collected: true,
|
||||
collectedAt: Date.now(),
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if (!existingClue.collected) {
|
||||
set({
|
||||
clues: clues.map((c) =>
|
||||
c.id === id ? { ...c, collected: true, collectedAt: Date.now() } : c
|
||||
),
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
isClueCollected: (id: string) => {
|
||||
const { clues } = get()
|
||||
return clues.some((c) => c.id === id && c.collected)
|
||||
},
|
||||
|
||||
getSceneClues: (sceneId: string) => {
|
||||
const { clues } = get()
|
||||
return clues.filter((c) => c.id.startsWith(sceneId))
|
||||
},
|
||||
|
||||
resetSceneClues: (sceneId: string) => {
|
||||
const { clues } = get()
|
||||
set({
|
||||
clues: clues.filter((c) => !c.id.startsWith(sceneId)),
|
||||
})
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.SUNZHONGSHAN,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
reflectionFlags: state.reflectionFlags,
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
clues: state.clues,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
@@ -129,8 +209,37 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
|
||||
if (!state.reflectionFlags || typeof state.reflectionFlags !== 'object') {
|
||||
state.reflectionFlags = {}
|
||||
}
|
||||
if (state.lastVisitedPlace === undefined) {
|
||||
state.lastVisitedPlace = null
|
||||
}
|
||||
if (state.reflectionRoute === undefined) {
|
||||
state.reflectionRoute = 'reflection'
|
||||
}
|
||||
if (!(state as any).clues || !Array.isArray((state as any).clues)) {
|
||||
;(state as any).clues = []
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(对话模块) ==========
|
||||
export const SUNZHONGSHAN_LESSON_ID = 'Sunzs_duihua'
|
||||
export const SUNZHONGSHAN_KEY = 4
|
||||
|
||||
export const saveSunzhongshanStore = (): void => {
|
||||
const state = useSunzhongshanStore.getState()
|
||||
|
||||
const payload = {
|
||||
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
}
|
||||
|
||||
try {
|
||||
setUserDefine(SUNZHONGSHAN_LESSON_ID, SUNZHONGSHAN_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Sunzhongshan store', error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { STORE_NAMES } from './storeNames'
|
||||
import { setUserDefine } from '@/lib/utils/api-common'
|
||||
|
||||
interface ZhanglepingStore {
|
||||
// ========= 对话模块:展品访问进度/断点 =========
|
||||
visitedExhibits: Record<string, Set<number>>
|
||||
lastVisitedPlace: string | null
|
||||
setLastVisitedPlace: (place: string) => void
|
||||
markExhibitVisited: (place: string, exhibitId: number) => void
|
||||
isExhibitVisited: (place: string, exhibitId: number) => boolean
|
||||
getVisitedExhibits: (place: string) => number[]
|
||||
resetPlace: (place: string) => void
|
||||
resetAllDialog: () => void
|
||||
|
||||
// 体悟模块当前路由(reflection / reflection1 / ... / reflection5)
|
||||
reflectionRoute: string
|
||||
setReflectionRoute: (route: string) => void
|
||||
|
||||
// 存储每个reflection场景的已完成cursor ID集合
|
||||
// 格式:{ scene1: Set<1, 2>, scene2: Set<1, 2>, ... }
|
||||
visitedReflectionCursors: Record<string, Set<number>>
|
||||
|
||||
// 标记reflection cursor为已完成
|
||||
markReflectionCursorVisited: (scene: string, cursorId: number) => void
|
||||
|
||||
// 获取某个reflection场景已完成的cursor ID数组
|
||||
getVisitedReflectionCursors: (scene: string) => number[]
|
||||
|
||||
// 绘制模块提交结果(用于在 scene5 画板处展示)
|
||||
drawArtworkDataUrl: string
|
||||
drawArtworkCaption: string
|
||||
setDrawArtwork: (dataUrl: string, caption: string) => void
|
||||
clearDrawArtwork: () => void
|
||||
|
||||
// 是否已经看过学习引导窗口
|
||||
hasSeenStudyGuide: boolean
|
||||
markStudyGuideSeen: () => void
|
||||
}
|
||||
|
||||
// 将 Set 转换为数组以便序列化(体悟)
|
||||
const serializeVisited = (visited: Record<string, Set<number>>): Record<string, number[]> => {
|
||||
const serialized: Record<string, number[]> = {}
|
||||
for (const [scene, ids] of Object.entries(visited)) {
|
||||
serialized[scene] = Array.from(ids)
|
||||
}
|
||||
return serialized
|
||||
}
|
||||
|
||||
// 将数组转换回 Set(体悟)
|
||||
const deserializeVisited = (
|
||||
serialized: Record<string, number[]> | undefined
|
||||
): Record<string, Set<number>> => {
|
||||
if (!serialized || typeof serialized !== 'object') return {}
|
||||
const visited: Record<string, Set<number>> = {}
|
||||
for (const [scene, ids] of Object.entries(serialized)) {
|
||||
visited[scene] = new Set(Array.isArray(ids) ? ids : [])
|
||||
}
|
||||
return visited
|
||||
}
|
||||
|
||||
export const useZhanglepingStore = create<ZhanglepingStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
visitedExhibits: {},
|
||||
lastVisitedPlace: null,
|
||||
reflectionRoute: 'reflection',
|
||||
visitedReflectionCursors: {},
|
||||
drawArtworkDataUrl: '',
|
||||
drawArtworkCaption: '',
|
||||
hasSeenStudyGuide: false,
|
||||
|
||||
markExhibitVisited: (place: string, exhibitId: number) => {
|
||||
set((state) => {
|
||||
const current = state.visitedExhibits[place] || new Set<number>()
|
||||
if (current.has(exhibitId)) return state
|
||||
return {
|
||||
visitedExhibits: {
|
||||
...state.visitedExhibits,
|
||||
[place]: new Set([...current, exhibitId]),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
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 next = { ...state.visitedExhibits }
|
||||
delete next[place]
|
||||
return { visitedExhibits: next }
|
||||
})
|
||||
},
|
||||
resetAllDialog: () => set({ visitedExhibits: {}, lastVisitedPlace: null }),
|
||||
setLastVisitedPlace: (place: string) => set({ lastVisitedPlace: place }),
|
||||
|
||||
setReflectionRoute: (route: string) => set({ reflectionRoute: route }),
|
||||
|
||||
markReflectionCursorVisited: (scene: string, cursorId: number) => {
|
||||
set((state) => {
|
||||
const current = state.visitedReflectionCursors[scene] || new Set<number>()
|
||||
if (current.has(cursorId)) return state
|
||||
return {
|
||||
visitedReflectionCursors: {
|
||||
...state.visitedReflectionCursors,
|
||||
[scene]: new Set([...current, cursorId]),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getVisitedReflectionCursors: (scene: string) => {
|
||||
const { visitedReflectionCursors } = get()
|
||||
return Array.from(visitedReflectionCursors[scene] || [])
|
||||
},
|
||||
|
||||
setDrawArtwork: (dataUrl: string, caption: string) =>
|
||||
set({
|
||||
drawArtworkDataUrl: dataUrl,
|
||||
drawArtworkCaption: caption,
|
||||
}),
|
||||
|
||||
clearDrawArtwork: () =>
|
||||
set({
|
||||
drawArtworkDataUrl: '',
|
||||
drawArtworkCaption: '',
|
||||
}),
|
||||
|
||||
markStudyGuideSeen: () => set({ hasSeenStudyGuide: true }),
|
||||
}),
|
||||
{
|
||||
name: STORE_NAMES.ZHANGLEPING,
|
||||
partialize: (state) => ({
|
||||
visitedExhibits: serializeVisited(state.visitedExhibits),
|
||||
lastVisitedPlace: state.lastVisitedPlace,
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
visitedReflectionCursors: serializeVisited(state.visitedReflectionCursors),
|
||||
drawArtworkDataUrl: state.drawArtworkDataUrl,
|
||||
drawArtworkCaption: state.drawArtworkCaption,
|
||||
hasSeenStudyGuide: state.hasSeenStudyGuide,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (!state) return
|
||||
state.visitedExhibits = deserializeVisited((state as any).visitedExhibits)
|
||||
if ((state as any).lastVisitedPlace === undefined) {
|
||||
;(state as any).lastVisitedPlace = null
|
||||
}
|
||||
state.visitedReflectionCursors = deserializeVisited(state.visitedReflectionCursors as any)
|
||||
if ((state as any).reflectionRoute === undefined) {
|
||||
;(state as any).reflectionRoute = 'reflection'
|
||||
}
|
||||
if ((state as any).drawArtworkDataUrl === undefined) {
|
||||
;(state as any).drawArtworkDataUrl = ''
|
||||
}
|
||||
if ((state as any).drawArtworkCaption === undefined) {
|
||||
;(state as any).drawArtworkCaption = ''
|
||||
}
|
||||
if ((state as any).hasSeenStudyGuide === undefined) {
|
||||
;(state as any).hasSeenStudyGuide = false
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// ========== 易班平台数据保存(体悟模块) ==========
|
||||
export const ZHANGLEPING_LESSON_ID = 'Zhanglp_tiwu'
|
||||
export const ZHANGLEPING_KEY = 16
|
||||
const REMOTE_MAX_DATA_URL_LENGTH = 1200
|
||||
|
||||
export const saveZhanglepingStore = (): void => {
|
||||
const state = useZhanglepingStore.getState()
|
||||
|
||||
// 易班接口在当前环境是 GET 参数传输,完整 base64 图会导致 URL 超长请求失败。
|
||||
// 因此远端只保存轻量摘要;完整画作仍保留在本地 persist(localStorage)中。
|
||||
const remoteArtworkDataUrl =
|
||||
state.drawArtworkDataUrl.length <= REMOTE_MAX_DATA_URL_LENGTH ? state.drawArtworkDataUrl : ''
|
||||
|
||||
const payload = {
|
||||
reflectionRoute: state.reflectionRoute,
|
||||
visitedReflectionCursors: serializeVisited(state.visitedReflectionCursors),
|
||||
drawArtworkDataUrl: remoteArtworkDataUrl,
|
||||
drawArtworkCaption: state.drawArtworkCaption,
|
||||
hasDrawArtwork: Boolean(state.drawArtworkDataUrl),
|
||||
}
|
||||
|
||||
try {
|
||||
setUserDefine(ZHANGLEPING_LESSON_ID, ZHANGLEPING_KEY, JSON.stringify(payload))
|
||||
} catch (error) {
|
||||
console.error('Failed to save Zhangleping store', error)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user