user.ts — 用户 Store
文件: web/src/stores/user.ts
职责
用户数据集中管理,涵盖个人信息、学习记录、复习阶段、积分、徽章、星球解锁等所有用户相关数据。是所有 Store 中的核心 Store。
依赖
无外部 Store 依赖。
数据加载
load(): 通过api.get<UserData>("/users/me")异步加载。初始化时自动调用。- 数据与
defaultUserData合并,确保缺失字段有默认值。
数据持久化
| 方法 | API 端点 | 方式 |
|---|---|---|
load() | GET /users/me | 读取 |
save() | PUT /users/me | 全量保存 |
addEXP(amount) | POST /users/me/exp | 专用端点,payload: { amount } |
addPoints(amount, source, sourceId, desc) | POST /users/me/points | 专用端点,payload: { amount, source, sourceId, desc } |
unlockBadge(badgeId) | POST /users/me/badges/:badgeId | 专用端点 |
addLearningRecord() | 内部调用 save() → PUT /users/me | 全量保存 |
recordEtymologyViewed() | 内部调用 save() → PUT /users/me | 全量保存 |
updateReviewStage() | 内部调用 save() → PUT /users/me | 全量保存 |
initReviewStage() | 内部调用 save() → PUT /users/me | 全量保存 |
markDailyReviewComplete() | 内部调用 save() → PUT /users/me | 全量保存 |
updateSettings() | 内部调用 save() → PUT /users/me | 全量保存 |
unlockPlanet() | 内部调用 save() → PUT /users/me | 全量保存 |
lockPlanet() | 内部调用 save() → PUT /users/me | 全量保存 |
addEXP、addPoints、unlockBadge已改为专用 API 端点(先更新本地 ref,再异步调用 API)。addLearningRecord仍使用save()全量保存。
核心数据结构
typescript
interface UserData {
userName: string
avatar: string
learned: string[] // 已学汉字列表
records: RecordItem[] // 学习记录
reviewStages: Record<string, ReviewStage> // 复习阶段(艾宾浩斯)
lastDailyReview: string | null // 上次每日复习日期
settings: UserSettings // 用户设置
streak: number // 连续学习天数
maxStreak: number // 最大连续天数
lastStudyDate: string | null // 最后学习日期
badges: BadgeLog[] // 已解锁徽章
totalEXP: number // 总经验值
shopPoints: number // 积分
pointLog: PointLogItem[] // 积分日志
etymologyViewed: string[] // 已查看字源的汉字
shopRewards: ShopReward[] // 自定义奖励
shopHistory: ShopHistoryItem[] // 兑换历史
shopPassword: string // 商店密码
shopLockUntil: number // 商店锁定时间戳
shopAttempts: number // 商店密码尝试次数
planetUnlocks: string[] // 已解锁星球(默认 ["nature_planet"])
}Reactive State
| 属性 | 类型 | 说明 |
|---|---|---|
userData | Ref<UserData> | 用户全部数据 |
loading | Ref<boolean> | 加载状态 |
getLearningStats | ComputedRef<{ learned, streak, todayReview, accuracy }> | 学习统计摘要 |
PLANET_ORDER | const string[] | 星球解锁顺序(6 颗) |
方法
数据加载与保存
| 方法 | 签名 | 说明 |
|---|---|---|
load | () => Promise<void> | 从 API 加载用户数据,自动合并默认值 |
save | () => void | 全量保存用户数据到 API(fire-and-forget) |
经验值与积分
| 方法 | 签名 | 说明 |
|---|---|---|
addEXP | (amount: number) => void | 增加经验值,调用 POST /users/me/exp |
addPoints | (amount: number, source: string, sourceId: string, desc: string) => void | 增加/扣除积分,自动记录日志,调用 POST /users/me/points |
徽章与字源
| 方法 | 签名 | 说明 |
|---|---|---|
unlockBadge | (badgeId: string) => void | 解锁徽章(去重),调用 POST /users/me/badges/:id |
recordEtymologyViewed | (char: string) => void | 记录字源查看(去重),调用 save() |
学习记录与复习
| 方法 | 签名 | 说明 |
|---|---|---|
addLearningRecord | (char: string, type: string, score: number) => void | 添加学习记录,自动更新 learned 列表、连续天数、复习阶段。调用 save() |
updateReviewStage | (char: string, score: number) => void | 根据得分更新复习阶段(艾宾浩斯曲线),调用 save() |
initReviewStage | (char: string) => void | 初始化汉字复习阶段,调用 save() |
markDailyReviewComplete | () => void | 标记今日复习完成,调用 save() |
查询方法
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
getCharStatus | (char: string) => string | "learned" | "failed" | "pending" | 查询汉字学习状态 |
getLatestScore | (char: string) => number | null | 最新得分 | 获取某汉字最新练习得分 |
getMaxScore | (char: string) => number | null | 最高得分 | 获取某汉字历史最高分 |
getStreak | () => number | 连续天数 | 计算当前连续学习天数 |
getTodayReviewCount | () => number | 待复习数 | 计算今日待复习汉字数 |
getAccuracy | () => number | 0-100 | 计算整体准确率 |
getFirstLearnedDate | (char: string) => Date | null | 首次学习日期 | 获取某汉字首次学习日期 |
hasReviewOnDate | (char: string, date: Date) => boolean | 是否复习过 | 检查某天是否复习过某汉字 |
设置
| 方法 | 签名 | 说明 |
|---|---|---|
updateSettings | (key: string, value: unknown) => void | 更新单个设置项,调用 save() |
星球管理
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
isPlanetUnlocked | (planetId: string) => boolean | 是否解锁 | 检查星球是否解锁 |
unlockPlanet | (planetId: string) => void | — | 解锁星球(去重),调用 save() |
lockPlanet | (planetId: string) => void | — | 锁定星球(nature_planet 不可锁),调用 save() |
getNextLockedPlanet | () => string | null | 下一个未解锁星球 | 按 PLANET_ORDER 返回第一个未解锁星球 |
星球解锁顺序
nature_planet → human_planet → animal_planet → object_planet → plant_planet → astronomy_planet