Skip to content

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全量保存

addEXPaddPointsunlockBadge 已改为专用 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

属性类型说明
userDataRef<UserData>用户全部数据
loadingRef<boolean>加载状态
getLearningStatsComputedRef<{ learned, streak, todayReview, accuracy }>学习统计摘要
PLANET_ORDERconst 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() => number0-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