feat: 更新 ESLint 和 Prettier 配置,增强代码质量管理

- 在 .gitignore 中添加 public/audio/* 以忽略音频文件。
- 更新 bun.lock,添加 eslint-config-prettier 和 eslint-plugin-prettier 依赖,确保 ESLint 与 Prettier 的兼容性。
- 修改 eslint.config.ts,整合 Prettier 规则,优化代码风格一致性。
- 在 package.json 中添加 lint:fix 和 format 脚本,简化代码格式化和修复流程。
- 更新 tailwind.config.ts 和其他组件文件,统一代码风格,提升可读性。
This commit is contained in:
zhongyidian committed 2026-03-05 13:50:47 +08:00
1 parent 5d00adc1a5
commit 76c67b492e
216 files changed
+12454 -13976

No files matched your search

+65 -47
View File
@@ -1,67 +1,68 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
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>>;
visitedExhibits: Record<string, Set<number>>
// 标记展品为已访问
markExhibitVisited: (place: string, exhibitId: number) => void;
markExhibitVisited: (place: string, exhibitId: number) => void
// 检查展品是否已访问
isExhibitVisited: (place: string, exhibitId: number) => boolean;
isExhibitVisited: (place: string, exhibitId: number) => boolean
// 获取某个场景已访问的展品ID数组
getVisitedExhibits: (place: string) => number[];
getVisitedExhibits: (place: string) => number[]
// 重置某个场景的访问记录
resetPlace: (place: string) => void;
resetPlace: (place: string) => void
// 重置所有访问记录
resetAll: () => void;
resetAll: () => void
// ========== 体悟模块状态 ==========
// 银元之战是否已解决
isYinyuanSolved: boolean;
isYinyuanSolved: boolean
// 设置银元之战解决状态
setYinyuanSolved: (solved: boolean) => void;
setYinyuanSolved: (solved: boolean) => void
// 米棉之战是否已解决
isMimianSolved: boolean;
isMimianSolved: boolean
// 设置米棉之战解决状态
setMimianSolved: (solved: boolean) => void;
setMimianSolved: (solved: boolean) => void
}
// 将 Set 转换为数组以便序列化
const serializeVisitedExhibits = (
visitedExhibits: Record<string, Set<number>>
): Record<string, number[]> => {
const serialized: Record<string, number[]> = {};
const serialized: Record<string, number[]> = {}
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
serialized[place] = Array.from(exhibits);
serialized[place] = Array.from(exhibits)
}
return serialized;
};
return serialized
}
// 将数组转换回 Set
const deserializeVisitedExhibits = (
serialized: Record<string, number[]> | undefined
): Record<string, Set<number>> => {
if (!serialized || typeof serialized !== "object") {
return {};
if (!serialized || typeof serialized !== 'object') {
return {}
}
const visitedExhibits: Record<string, Set<number>> = {};
const visitedExhibits: Record<string, Set<number>> = {}
for (const [place, exhibits] of Object.entries(serialized)) {
if (Array.isArray(exhibits)) {
visitedExhibits[place] = new Set(exhibits);
visitedExhibits[place] = new Set(exhibits)
} else {
visitedExhibits[place] = new Set();
visitedExhibits[place] = new Set()
}
}
return visitedExhibits;
};
return visitedExhibits
}
export const useChenyunStore = create<ChenyunStore>()(
persist(
@@ -70,50 +71,49 @@ export const useChenyunStore = create<ChenyunStore>()(
markExhibitVisited: (place: string, exhibitId: number) => {
set((state) => {
const currentExhibits =
state.visitedExhibits[place] || new Set<number>();
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 { visitedExhibits: newExhibits }
}
return state;
});
return state
})
},
isExhibitVisited: (place: string, exhibitId: number) => {
const { visitedExhibits } = get();
return visitedExhibits[place]?.has(exhibitId) || false;
const { visitedExhibits } = get()
return visitedExhibits[place]?.has(exhibitId) || false
},
getVisitedExhibits: (place: string) => {
const { visitedExhibits } = get();
return Array.from(visitedExhibits[place] || []);
const { visitedExhibits } = get()
return Array.from(visitedExhibits[place] || [])
},
resetPlace: (place: string) => {
set((state) => {
const newExhibits = { ...state.visitedExhibits };
delete newExhibits[place];
return { visitedExhibits: newExhibits };
});
const newExhibits = { ...state.visitedExhibits }
delete newExhibits[place]
return { visitedExhibits: newExhibits }
})
},
resetAll: () => {
set({ visitedExhibits: {} });
set({ visitedExhibits: {} })
},
// ========== 体悟模块状态 ==========
isYinyuanSolved: false,
setYinyuanSolved: (solved: boolean) => {
set({ isYinyuanSolved: solved });
set({ isYinyuanSolved: solved })
},
isMimianSolved: false,
setMimianSolved: (solved: boolean) => {
set({ isMimianSolved: solved });
set({ isMimianSolved: solved })
},
}),
{
@@ -126,18 +126,36 @@ export const useChenyunStore = create<ChenyunStore>()(
onRehydrateStorage: () => (state) => {
if (state) {
// 确保反序列化后的数据格式正确
state.visitedExhibits = deserializeVisitedExhibits(
state.visitedExhibits as any
);
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
// 确保解决状态有默认值
if (state.isYinyuanSolved === undefined) {
state.isYinyuanSolved = false;
state.isYinyuanSolved = false
}
if (state.isMimianSolved === undefined) {
state.isMimianSolved = false;
state.isMimianSolved = false
}
}
},
}
)
);
)
// ========== 易班平台数据保存 ==========
const CHENYUN_LESSON_ID = 'Cheny_duihua'
const CHENYUN_KEY = 22
export const saveChenyunStore = (): void => {
const state = useChenyunStore.getState()
const payload = {
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
isYinyuanSolved: state.isYinyuanSolved,
isMimianSolved: state.isMimianSolved,
}
try {
setUserDefine(CHENYUN_LESSON_ID, CHENYUN_KEY, JSON.stringify(payload))
} catch (error) {
console.error('Failed to save Chenyun store', error)
}
}
+26 -28
View File
@@ -1,26 +1,26 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
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; // 收集时间戳
id: string // 格式: "sunzhongshan/reflection/scene1/clue1"
name: string // 线索名称,如 "线索1"
collected: boolean // 是否已收集
collectedAt?: number // 收集时间戳
}
interface ClueStore {
clues: Clue[];
clues: Clue[]
// 收集线索
collectClue: (id: string, name: string) => void;
collectClue: (id: string, name: string) => void
// 检查线索是否已收集
isClueCollected: (id: string) => boolean;
isClueCollected: (id: string) => boolean
// 获取指定场景的所有线索
getSceneClues: (sceneId: string) => Clue[];
getSceneClues: (sceneId: string) => Clue[]
// 重置指定场景的线索
resetSceneClues: (sceneId: string) => void;
resetSceneClues: (sceneId: string) => void
// 重置所有线索
resetAll: () => void;
resetAll: () => void
}
export const useClueStore = create<ClueStore>()(
@@ -29,8 +29,8 @@ export const useClueStore = create<ClueStore>()(
clues: [],
collectClue: (id: string, name: string) => {
const { clues } = get();
const existingClue = clues.find((c) => c.id === id);
const { clues } = get()
const existingClue = clues.find((c) => c.id === id)
if (!existingClue) {
set({
@@ -43,41 +43,39 @@ export const useClueStore = create<ClueStore>()(
collectedAt: Date.now(),
},
],
});
})
} else if (!existingClue.collected) {
set({
clues: clues.map((c) =>
c.id === id
? { ...c, collected: true, collectedAt: Date.now() }
: 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);
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));
const { clues } = get()
return clues.filter((c) => c.id.startsWith(sceneId))
},
resetSceneClues: (sceneId: string) => {
const { clues } = get();
const { clues } = get()
set({
clues: clues.filter((c) => !c.id.startsWith(sceneId)),
});
})
},
resetAll: () => {
set({ clues: [] });
set({ clues: [] })
},
}),
{
name: STORE_NAMES.CLUE, // localStorage key
}
)
);
)
+58 -64
View File
@@ -1,76 +1,76 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
interface HuangyanpeiStore {
// 存储每个场景(place)的已访问展品ID集合
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
visitedExhibits: Record<string, Set<number>>;
visitedExhibits: Record<string, Set<number>>
// 标记展品为已访问
markExhibitVisited: (place: string, exhibitId: number) => void;
markExhibitVisited: (place: string, exhibitId: number) => void
// 检查展品是否已访问
isExhibitVisited: (place: string, exhibitId: number) => boolean;
isExhibitVisited: (place: string, exhibitId: number) => boolean
// 获取某个场景已访问的展品ID数组
getVisitedExhibits: (place: string) => number[];
getVisitedExhibits: (place: string) => number[]
// 重置某个场景的访问记录
resetPlace: (place: string) => void;
resetPlace: (place: string) => void
// 重置所有访问记录
resetAll: () => void;
resetAll: () => void
// ========== 体悟模块状态 ==========
// 存储每个reflection场景的已完成cursor ID集合
// 格式:{ scene1: Set<1, 2>, scene2: Set<1, 2>, ... }
visitedReflectionCursors: Record<string, Set<number>>;
visitedReflectionCursors: Record<string, Set<number>>
// 标记reflection cursor为已完成
markReflectionCursorVisited: (scene: string, cursorId: number) => void;
markReflectionCursorVisited: (scene: string, cursorId: number) => void
// 检查reflection cursor是否已完成
isReflectionCursorVisited: (scene: string, cursorId: number) => boolean;
isReflectionCursorVisited: (scene: string, cursorId: number) => boolean
// 获取某个reflection场景已完成的cursor ID数组
getVisitedReflectionCursors: (scene: string) => number[];
getVisitedReflectionCursors: (scene: string) => number[]
// 重置某个reflection场景的cursor记录
resetReflectionScene: (scene: string) => void;
resetReflectionScene: (scene: string) => void
// 重置所有reflection cursor记录
resetAllReflectionCursors: () => void;
resetAllReflectionCursors: () => void
}
// 将 Set 转换为数组以便序列化
const serializeVisitedExhibits = (
visitedExhibits: Record<string, Set<number>>
): Record<string, number[]> => {
const serialized: Record<string, number[]> = {};
const serialized: Record<string, number[]> = {}
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
serialized[place] = Array.from(exhibits);
serialized[place] = Array.from(exhibits)
}
return serialized;
};
return serialized
}
// 将数组转换回 Set
const deserializeVisitedExhibits = (
serialized: Record<string, number[]> | undefined
): Record<string, Set<number>> => {
if (!serialized || typeof serialized !== "object") {
return {};
if (!serialized || typeof serialized !== 'object') {
return {}
}
const visitedExhibits: Record<string, Set<number>> = {};
const visitedExhibits: Record<string, Set<number>> = {}
for (const [place, exhibits] of Object.entries(serialized)) {
if (Array.isArray(exhibits)) {
visitedExhibits[place] = new Set(exhibits);
visitedExhibits[place] = new Set(exhibits)
} else {
visitedExhibits[place] = new Set();
visitedExhibits[place] = new Set()
}
}
return visitedExhibits;
};
return visitedExhibits
}
export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
persist(
@@ -79,39 +79,38 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
markExhibitVisited: (place: string, exhibitId: number) => {
set((state) => {
const currentExhibits =
state.visitedExhibits[place] || new Set<number>();
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 { visitedExhibits: newExhibits }
}
return state;
});
return state
})
},
isExhibitVisited: (place: string, exhibitId: number) => {
const { visitedExhibits } = get();
return visitedExhibits[place]?.has(exhibitId) || false;
const { visitedExhibits } = get()
return visitedExhibits[place]?.has(exhibitId) || false
},
getVisitedExhibits: (place: string) => {
const { visitedExhibits } = get();
return Array.from(visitedExhibits[place] || []);
const { visitedExhibits } = get()
return Array.from(visitedExhibits[place] || [])
},
resetPlace: (place: string) => {
set((state) => {
const newExhibits = { ...state.visitedExhibits };
delete newExhibits[place];
return { visitedExhibits: newExhibits };
});
const newExhibits = { ...state.visitedExhibits }
delete newExhibits[place]
return { visitedExhibits: newExhibits }
})
},
resetAll: () => {
set({ visitedExhibits: {} });
set({ visitedExhibits: {} })
},
// ========== 体悟模块状态 ==========
@@ -119,60 +118,55 @@ export const useHuangyanpeiStore = create<HuangyanpeiStore>()(
markReflectionCursorVisited: (scene: string, cursorId: number) => {
set((state) => {
const currentCursors =
state.visitedReflectionCursors[scene] || new Set<number>();
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 { visitedReflectionCursors: newCursors }
}
return state;
});
return state
})
},
isReflectionCursorVisited: (scene: string, cursorId: number) => {
const { visitedReflectionCursors } = get();
return visitedReflectionCursors[scene]?.has(cursorId) || false;
const { visitedReflectionCursors } = get()
return visitedReflectionCursors[scene]?.has(cursorId) || false
},
getVisitedReflectionCursors: (scene: string) => {
const { visitedReflectionCursors } = get();
return Array.from(visitedReflectionCursors[scene] || []);
const { visitedReflectionCursors } = get()
return Array.from(visitedReflectionCursors[scene] || [])
},
resetReflectionScene: (scene: string) => {
set((state) => {
const newCursors = { ...state.visitedReflectionCursors };
delete newCursors[scene];
return { visitedReflectionCursors: newCursors };
});
const newCursors = { ...state.visitedReflectionCursors }
delete newCursors[scene]
return { visitedReflectionCursors: newCursors }
})
},
resetAllReflectionCursors: () => {
set({ visitedReflectionCursors: {} });
set({ visitedReflectionCursors: {} })
},
}),
{
name: STORE_NAMES.HUANGYANPEI, // localStorage key
partialize: (state) => ({
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
visitedReflectionCursors: serializeVisitedExhibits(
state.visitedReflectionCursors
),
visitedReflectionCursors: serializeVisitedExhibits(state.visitedReflectionCursors),
}),
onRehydrateStorage: () => (state) => {
if (state) {
// 确保反序列化后的数据格式正确
state.visitedExhibits = deserializeVisitedExhibits(
state.visitedExhibits as any
);
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
state.visitedReflectionCursors = deserializeVisitedExhibits(
state.visitedReflectionCursors as any
);
)
}
},
}
)
);
)
+8 -8
View File
@@ -1,8 +1,8 @@
export { useChenyunStore } from "./chenyunStore";
export { useHuangyanpeiStore } from "./huangyanpeiStore";
export { useLuxunStore } from "./luxunStore";
export { useSunzhongshanStore } from "./sunzhongshanStore";
export { useClueStore } from "./clueStore";
export { usePuzzleStore } from "./puzzleStore";
export { useUserStore } from "./userStore";
export { STORE_NAMES } from "./storeNames";
export { useChenyunStore } from './chenyunStore'
export { useHuangyanpeiStore } from './huangyanpeiStore'
export { useLuxunStore } from './luxunStore'
export { useSunzhongshanStore } from './sunzhongshanStore'
export { useClueStore } from './clueStore'
export { usePuzzleStore } from './puzzleStore'
export { useUserStore } from './userStore'
export { STORE_NAMES } from './storeNames'
+36 -41
View File
@@ -1,56 +1,56 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
interface LuxunStore {
// 存储每个场景(place)的已访问展品ID集合
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
visitedExhibits: Record<string, Set<number>>;
visitedExhibits: Record<string, Set<number>>
// 标记展品为已访问
markExhibitVisited: (place: string, exhibitId: number) => void;
markExhibitVisited: (place: string, exhibitId: number) => void
// 检查展品是否已访问
isExhibitVisited: (place: string, exhibitId: number) => boolean;
isExhibitVisited: (place: string, exhibitId: number) => boolean
// 获取某个场景已访问的展品ID数组
getVisitedExhibits: (place: string) => number[];
getVisitedExhibits: (place: string) => number[]
// 重置某个场景的访问记录
resetPlace: (place: string) => void;
resetPlace: (place: string) => void
// 重置所有访问记录
resetAll: () => void;
resetAll: () => void
}
// 将 Set 转换为数组以便序列化
const serializeVisitedExhibits = (
visitedExhibits: Record<string, Set<number>>
): Record<string, number[]> => {
const serialized: Record<string, number[]> = {};
const serialized: Record<string, number[]> = {}
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
serialized[place] = Array.from(exhibits);
serialized[place] = Array.from(exhibits)
}
return serialized;
};
return serialized
}
// 将数组转换回 Set
const deserializeVisitedExhibits = (
serialized: Record<string, number[]> | undefined
): Record<string, Set<number>> => {
if (!serialized || typeof serialized !== "object") {
return {};
if (!serialized || typeof serialized !== 'object') {
return {}
}
const visitedExhibits: Record<string, Set<number>> = {};
const visitedExhibits: Record<string, Set<number>> = {}
for (const [place, exhibits] of Object.entries(serialized)) {
if (Array.isArray(exhibits)) {
visitedExhibits[place] = new Set(exhibits);
visitedExhibits[place] = new Set(exhibits)
} else {
visitedExhibits[place] = new Set();
visitedExhibits[place] = new Set()
}
}
return visitedExhibits;
};
return visitedExhibits
}
export const useLuxunStore = create<LuxunStore>()(
persist(
@@ -59,39 +59,38 @@ export const useLuxunStore = create<LuxunStore>()(
markExhibitVisited: (place: string, exhibitId: number) => {
set((state) => {
const currentExhibits =
state.visitedExhibits[place] || new Set<number>();
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 { visitedExhibits: newExhibits }
}
return state;
});
return state
})
},
isExhibitVisited: (place: string, exhibitId: number) => {
const { visitedExhibits } = get();
return visitedExhibits[place]?.has(exhibitId) || false;
const { visitedExhibits } = get()
return visitedExhibits[place]?.has(exhibitId) || false
},
getVisitedExhibits: (place: string) => {
const { visitedExhibits } = get();
return Array.from(visitedExhibits[place] || []);
const { visitedExhibits } = get()
return Array.from(visitedExhibits[place] || [])
},
resetPlace: (place: string) => {
set((state) => {
const newExhibits = { ...state.visitedExhibits };
delete newExhibits[place];
return { visitedExhibits: newExhibits };
});
const newExhibits = { ...state.visitedExhibits }
delete newExhibits[place]
return { visitedExhibits: newExhibits }
})
},
resetAll: () => {
set({ visitedExhibits: {} });
set({ visitedExhibits: {} })
},
}),
{
@@ -101,13 +100,9 @@ export const useLuxunStore = create<LuxunStore>()(
}),
onRehydrateStorage: () => (state) => {
if (state) {
state.visitedExhibits = deserializeVisitedExhibits(
state.visitedExhibits as any
);
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
}
},
}
)
);
)
+62 -64
View File
@@ -1,75 +1,73 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
interface PuzzleStore {
// 每个故居的已解锁碎片集合,格式:{ residence: Set<pieceId> }
puzzles: Record<string, Set<number>>;
puzzles: Record<string, Set<number>>
// 解锁碎片
unlockPuzzle: (residence: string, pieceId: number) => void;
unlockPuzzle: (residence: string, pieceId: number) => void
// 检查碎片是否解锁
isPuzzleUnlocked: (residence: string, pieceId: number) => boolean;
isPuzzleUnlocked: (residence: string, pieceId: number) => boolean
// 获取已解锁碎片列表
getUnlockedPuzzles: (residence: string) => number[];
getUnlockedPuzzles: (residence: string) => number[]
// 重置指定故居的所有碎片
resetPuzzles: (residence: string) => void;
resetPuzzles: (residence: string) => void
// 重置所有碎片
resetAll: () => void;
resetAll: () => void
// 获取收集进度
getPuzzleCollectProgress: (residence: string) => number;
getPuzzleCollectProgress: (residence: string) => number
}
// 将 Set 转换为数组以便序列化
const serializePuzzles = (puzzles: Record<string, Set<number>>) => {
const serialized: Record<string, number[]> = {};
const serialized: Record<string, number[]> = {}
for (const [residence, pieces] of Object.entries(puzzles)) {
serialized[residence] = Array.from(pieces);
serialized[residence] = Array.from(pieces)
}
return serialized;
};
return serialized
}
// 将数组转换回 Set
const deserializePuzzles = (
serialized: Record<string, number[]> | undefined
) => {
if (!serialized || typeof serialized !== "object") {
return {};
const deserializePuzzles = (serialized: Record<string, number[]> | undefined) => {
if (!serialized || typeof serialized !== 'object') {
return {}
}
const puzzles: Record<string, Set<number>> = {};
const puzzles: Record<string, Set<number>> = {}
for (const [residence, pieces] of Object.entries(serialized)) {
// 确保 pieces 是数组
if (Array.isArray(pieces)) {
puzzles[residence] = new Set(pieces);
puzzles[residence] = new Set(pieces)
} else {
// 如果格式不对,创建新的 Set
puzzles[residence] = new Set();
puzzles[residence] = new Set()
}
}
return puzzles;
};
return puzzles
}
// 辅助函数:确保 puzzles 中的值都是 Set
const ensurePuzzlesAreSets = (puzzles: any): Record<string, Set<number>> => {
const fixed: Record<string, Set<number>> = {};
if (puzzles && typeof puzzles === "object") {
const fixed: Record<string, Set<number>> = {}
if (puzzles && typeof puzzles === 'object') {
for (const [residence, pieces] of Object.entries(puzzles)) {
if (pieces instanceof Set) {
fixed[residence] = pieces;
fixed[residence] = pieces
} else if (Array.isArray(pieces)) {
fixed[residence] = new Set(pieces);
fixed[residence] = new Set(pieces)
} else {
fixed[residence] = new Set();
fixed[residence] = new Set()
}
}
}
return fixed;
};
return fixed
}
export const usePuzzleStore = create<PuzzleStore>()(
persist(
@@ -77,62 +75,62 @@ export const usePuzzleStore = create<PuzzleStore>()(
puzzles: {},
unlockPuzzle: (residence: string, pieceId: number) => {
const { puzzles } = get();
const { puzzles } = get()
// 确保 puzzles 格式正确
const fixedPuzzles = ensurePuzzlesAreSets(puzzles);
const residencePuzzles = fixedPuzzles[residence] || new Set<number>();
const fixedPuzzles = ensurePuzzlesAreSets(puzzles)
const residencePuzzles = fixedPuzzles[residence] || new Set<number>()
if (!residencePuzzles.has(pieceId)) {
const newPuzzles = { ...fixedPuzzles };
newPuzzles[residence] = new Set([...residencePuzzles, pieceId]);
set({ puzzles: newPuzzles });
const newPuzzles = { ...fixedPuzzles }
newPuzzles[residence] = new Set([...residencePuzzles, pieceId])
set({ puzzles: newPuzzles })
}
},
isPuzzleUnlocked: (residence: string, pieceId: number) => {
const { puzzles } = get();
const { puzzles } = get()
// 确保 puzzles 格式正确
const fixedPuzzles = ensurePuzzlesAreSets(puzzles);
const residencePuzzles = fixedPuzzles[residence];
const fixedPuzzles = ensurePuzzlesAreSets(puzzles)
const residencePuzzles = fixedPuzzles[residence]
if (!residencePuzzles) {
return false;
return false
}
return residencePuzzles.has(pieceId);
return residencePuzzles.has(pieceId)
},
getUnlockedPuzzles: (residence: string) => {
const { puzzles } = get();
const { puzzles } = get()
// 确保 puzzles 格式正确
const fixedPuzzles = ensurePuzzlesAreSets(puzzles);
const residencePuzzles = fixedPuzzles[residence];
const fixedPuzzles = ensurePuzzlesAreSets(puzzles)
const residencePuzzles = fixedPuzzles[residence]
if (!residencePuzzles) {
return [];
return []
}
return Array.from(residencePuzzles);
return Array.from(residencePuzzles)
},
getPuzzleCollectProgress: (residence: string): number => {
const totalPuzzles = 16;
const { puzzles } = get();
const fixedPuzzles = ensurePuzzlesAreSets(puzzles);
const residencePuzzles = fixedPuzzles[residence];
const totalPuzzles = 16
const { puzzles } = get()
const fixedPuzzles = ensurePuzzlesAreSets(puzzles)
const residencePuzzles = fixedPuzzles[residence]
if (!residencePuzzles) {
return 0;
return 0
}
return (residencePuzzles.size / totalPuzzles) * 100;
return (residencePuzzles.size / totalPuzzles) * 100
},
resetPuzzles: (residence: string) => {
const { puzzles } = get();
const { puzzles } = get()
// 确保 puzzles 格式正确
const fixedPuzzles = ensurePuzzlesAreSets(puzzles);
const newPuzzles = { ...fixedPuzzles };
delete newPuzzles[residence];
set({ puzzles: newPuzzles });
const fixedPuzzles = ensurePuzzlesAreSets(puzzles)
const newPuzzles = { ...fixedPuzzles }
delete newPuzzles[residence]
set({ puzzles: newPuzzles })
},
resetAll: () => {
set({ puzzles: {} });
set({ puzzles: {} })
},
}),
{
@@ -142,19 +140,19 @@ export const usePuzzleStore = create<PuzzleStore>()(
}),
merge: (persistedState, currentState) => {
const persisted = persistedState as {
puzzles?: Record<string, number[]>;
};
puzzles?: Record<string, number[]>
}
return {
...currentState,
puzzles: deserializePuzzles(persisted?.puzzles),
};
}
},
onRehydrateStorage: () => (state) => {
// 在恢复时,确保所有 puzzles 都是 Set
if (state) {
state.puzzles = ensurePuzzlesAreSets(state.puzzles);
state.puzzles = ensurePuzzlesAreSets(state.puzzles)
}
},
}
)
);
)
+10 -12
View File
@@ -1,13 +1,13 @@
//lessonId 课时唯一id
export const STORE_NAMES = {
USER: "user-storage",
SUNZHONGSHAN: "sunzhongshan-storage",
LUXUN: "luxun-storage",
HUANGYANPEI: "huangyanpei-storage",
CHENYUN: "chenyun-storage",
PUZZLE: "puzzle-storage",
CLUE: "clue-storage",
} as const;
USER: 'user-storage',
SUNZHONGSHAN: 'sunzhongshan-storage',
LUXUN: 'luxun-storage',
HUANGYANPEI: 'huangyanpei-storage',
CHENYUN: 'chenyun-storage',
PUZZLE: 'puzzle-storage',
CLUE: 'clue-storage',
} as const
//课程的第几课时
export const COURSE_ORDER = {
@@ -18,8 +18,6 @@ export const COURSE_ORDER = {
COURSE_ORDER_5: 5,
COURSE_ORDER_6: 6,
COURSE_ORDER_7: 7,
} as const;
export type StoreNameKey = keyof typeof STORE_NAMES;
} as const
export type StoreNameKey = keyof typeof STORE_NAMES
+44 -52
View File
@@ -1,65 +1,65 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
interface SunzhongshanStore {
// 存储每个场景(place)的已访问展品ID集合
// 格式:{ place1: Set<1, 2>, place2: Set<1, 2>, ... }
visitedExhibits: Record<string, Set<number>>;
visitedExhibits: Record<string, Set<number>>
// 体悟模块通用布尔状态(引导是否展示、场景是否完成等)
reflectionFlags: Record<string, boolean>;
reflectionFlags: Record<string, boolean>
// 标记展品为已访问
markExhibitVisited: (place: string, exhibitId: number) => void;
markExhibitVisited: (place: string, exhibitId: number) => void
// 检查展品是否已访问
isExhibitVisited: (place: string, exhibitId: number) => boolean;
isExhibitVisited: (place: string, exhibitId: number) => boolean
// 获取某个场景已访问的展品ID数组
getVisitedExhibits: (place: string) => number[];
getVisitedExhibits: (place: string) => number[]
// 重置某个场景的访问记录
resetPlace: (place: string) => void;
resetPlace: (place: string) => void
// 重置所有访问记录
resetAll: () => void;
resetAll: () => void
// 设置体悟模块布尔状态
setReflectionFlag: (key: string, value: boolean) => void;
setReflectionFlag: (key: string, value: boolean) => void
// 获取体悟模块布尔状态
getReflectionFlag: (key: string) => boolean;
getReflectionFlag: (key: string) => boolean
}
// 将 Set 转换为数组以便序列化
const serializeVisitedExhibits = (
visitedExhibits: Record<string, Set<number>>
): Record<string, number[]> => {
const serialized: Record<string, number[]> = {};
const serialized: Record<string, number[]> = {}
for (const [place, exhibits] of Object.entries(visitedExhibits)) {
serialized[place] = Array.from(exhibits);
serialized[place] = Array.from(exhibits)
}
return serialized;
};
return serialized
}
// 将数组转换回 Set
const deserializeVisitedExhibits = (
serialized: Record<string, number[]> | undefined
): Record<string, Set<number>> => {
if (!serialized || typeof serialized !== "object") {
return {};
if (!serialized || typeof serialized !== 'object') {
return {}
}
const visitedExhibits: Record<string, Set<number>> = {};
const visitedExhibits: Record<string, Set<number>> = {}
for (const [place, exhibits] of Object.entries(serialized)) {
if (Array.isArray(exhibits)) {
visitedExhibits[place] = new Set(exhibits);
visitedExhibits[place] = new Set(exhibits)
} else {
visitedExhibits[place] = new Set();
visitedExhibits[place] = new Set()
}
}
return visitedExhibits;
};
return visitedExhibits
}
export const useSunzhongshanStore = create<SunzhongshanStore>()(
persist(
@@ -69,39 +69,38 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
markExhibitVisited: (place: string, exhibitId: number) => {
set((state) => {
const currentExhibits =
state.visitedExhibits[place] || new Set<number>();
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 { visitedExhibits: newExhibits }
}
return state;
});
return state
})
},
isExhibitVisited: (place: string, exhibitId: number) => {
const { visitedExhibits } = get();
return visitedExhibits[place]?.has(exhibitId) || false;
const { visitedExhibits } = get()
return visitedExhibits[place]?.has(exhibitId) || false
},
getVisitedExhibits: (place: string) => {
const { visitedExhibits } = get();
return Array.from(visitedExhibits[place] || []);
const { visitedExhibits } = get()
return Array.from(visitedExhibits[place] || [])
},
resetPlace: (place: string) => {
set((state) => {
const newExhibits = { ...state.visitedExhibits };
delete newExhibits[place];
return { visitedExhibits: newExhibits };
});
const newExhibits = { ...state.visitedExhibits }
delete newExhibits[place]
return { visitedExhibits: newExhibits }
})
},
resetAll: () => {
set({ visitedExhibits: {}, reflectionFlags: {} });
set({ visitedExhibits: {}, reflectionFlags: {} })
},
setReflectionFlag: (key: string, value: boolean) => {
@@ -110,12 +109,12 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
...state.reflectionFlags,
[key]: value,
},
}));
}))
},
getReflectionFlag: (key: string) => {
const { reflectionFlags } = get();
return !!reflectionFlags[key];
const { reflectionFlags } = get()
return !!reflectionFlags[key]
},
}),
{
@@ -126,19 +125,12 @@ export const useSunzhongshanStore = create<SunzhongshanStore>()(
}),
onRehydrateStorage: () => (state) => {
if (state) {
state.visitedExhibits = deserializeVisitedExhibits(
state.visitedExhibits as any
);
if (
!state.reflectionFlags ||
typeof state.reflectionFlags !== "object"
) {
state.reflectionFlags = {};
state.visitedExhibits = deserializeVisitedExhibits(state.visitedExhibits as any)
if (!state.reflectionFlags || typeof state.reflectionFlags !== 'object') {
state.reflectionFlags = {}
}
}
},
}
)
);
)
+10 -12
View File
@@ -1,13 +1,13 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { STORE_NAMES } from "./storeNames";
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { STORE_NAMES } from './storeNames'
interface UserStore {
courseId: string | null;
uid: string | null;
courseId: string | null
uid: string | null
setCourseInfo: (info: { courseId?: string | null; uid?: string | null }) => void;
resetCourseInfo: () => void;
setCourseInfo: (info: { courseId?: string | null; uid?: string | null }) => void
resetCourseInfo: () => void
}
export const useUserStore = create<UserStore>()(
@@ -20,17 +20,15 @@ export const useUserStore = create<UserStore>()(
set((state) => ({
courseId: info.courseId ?? state.courseId,
uid: info.uid ?? state.uid,
}));
}))
},
resetCourseInfo: () => {
set({ courseId: null, uid: null });
set({ courseId: null, uid: null })
},
}),
{
name: STORE_NAMES.USER,
}
)
);
)