feat: 添加全局键盘快捷键和更新故居数据,提升用户体验

- 在 main.tsx 中添加全局键盘快捷键功能,允许用户通过 Ctrl+Shift+R 重置所有缓存。
- 更新多个故居的拼图数据,增加黄炎培和鲁迅的相关信息,丰富学习内容。
- 调整 ResidenceLayout 组件以支持新的故居,确保数据一致性。
- 清理不再使用的拼图布局配置文件,提升代码整洁性。
This commit is contained in:
zhongyidian committed 2026-01-28 15:08:28 +08:00
1 parent 8ba36a4a1a
commit a365964f54
155 files changed
+1387 -348

No files matched your search

+6
View File
@@ -18,6 +18,8 @@ interface ClueStore {
getSceneClues: (sceneId: string) => Clue[];
// 重置指定场景的线索
resetSceneClues: (sceneId: string) => void;
// 重置所有线索
resetAll: () => void;
}
export const useClueStore = create<ClueStore>()(
@@ -68,6 +70,10 @@ export const useClueStore = create<ClueStore>()(
clues: clues.filter((c) => !c.id.startsWith(sceneId)),
});
},
resetAll: () => {
set({ clues: [] });
},
}),
{
name: "clue-storage", // localStorage key
+177
View File
@@ -0,0 +1,177 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface HuangyanpeiStore {
// 存储每个场景(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;
// ========== 体悟模块状态 ==========
// 存储每个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是否已完成
isReflectionCursorVisited: (scene: string, cursorId: number) => boolean;
// 获取某个reflection场景已完成的cursor ID数组
getVisitedReflectionCursors: (scene: string) => number[];
// 重置某个reflection场景的cursor记录
resetReflectionScene: (scene: string) => void;
// 重置所有reflection cursor记录
resetAllReflectionCursors: () => 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 useHuangyanpeiStore = create<HuangyanpeiStore>()(
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: {} });
},
// ========== 体悟模块状态 ==========
visitedReflectionCursors: {},
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: {} });
},
}),
{
name: "huangyanpei-storage", // localStorage key
partialize: (state) => ({
visitedExhibits: serializeVisitedExhibits(state.visitedExhibits),
visitedReflectionCursors: serializeVisitedExhibits(
state.visitedReflectionCursors
),
}),
onRehydrateStorage: () => (state) => {
if (state) {
// 确保反序列化后的数据格式正确
state.visitedExhibits = deserializeVisitedExhibits(
state.visitedExhibits as any
);
state.visitedReflectionCursors = deserializeVisitedExhibits(
state.visitedReflectionCursors as any
);
}
},
}
)
);
+4
View File
@@ -0,0 +1,4 @@
export { useChenyunStore } from "./chenyunStore";
export { useHuangyanpeiStore } from "./huangyanpeiStore";
export { useClueStore } from "./clueStore";
export { usePuzzleStore } from "./puzzleStore";
+7
View File
@@ -17,6 +17,9 @@ interface PuzzleStore {
// 重置指定故居的所有碎片
resetPuzzles: (residence: string) => void;
// 重置所有碎片
resetAll: () => void;
// 获取收集进度
getPuzzleCollectProgress: (residence: string) => number;
}
@@ -126,6 +129,10 @@ export const usePuzzleStore = create<PuzzleStore>()(
delete newPuzzles[residence];
set({ puzzles: newPuzzles });
},
resetAll: () => {
set({ puzzles: {} });
},
}),
{
name: "puzzle-storage", // localStorage key