shop.ts — 商店 Store
文件: web/src/stores/shop.ts
职责
- 积分兑换管理
- 自定义奖励项 CRUD(全部通过 API 调用)
- 密码验证与设置
- 兑换历史记录
- 10 种预设奖励图标
依赖
| Store | 用途 |
|---|---|
useUserStore | 读取 shopPoints、shopPassword、shopLockUntil、shopAttempts;兑换后刷新用户数据 |
数据加载
load(): 通过 2 个 API 并行加载:api.get<ShopReward[]>("/shop/rewards")— 奖励列表api.get<ShopHistory[]>("/shop/history")— 兑换历史
- 初始化时自动调用。
数据持久化
全部 CRUD 操作均通过 API 调用,不在 Store 内直接操作数据。
| 方法 | API 端点 | 方式 |
|---|---|---|
load() | GET /shop/rewards + GET /shop/history | 读取 |
addReward() | POST /shop/rewards | 创建 → 重新 load |
updateReward() | PUT /shop/rewards/:id | 更新 → 重新 load |
deleteReward() | DELETE /shop/rewards/:id | 删除 → 重新 load |
toggleReward() | PUT /shop/rewards/:id/toggle | 切换启用/禁用 → 重新 load |
redeemReward() | POST /shop/rewards/:id/redeem | 兑换 → 重新 load + 刷新 userStore |
verifyPassword() | POST /shop/password/verify | 验证密码 |
setPassword() | PUT /shop/password | 设置/修改密码 |
所有 CRUD 操作后均调用
load()刷新数据,redeemReward额外调用user.load()同步积分余额。
核心数据结构
typescript
interface ShopReward {
id: string
name: string
cost: number
icon: string // 图标名称(对应 presetIcons 中的 value)
enabled: boolean // 是否启用
createdAt: string // 创建时间
}
interface ShopHistory {
id: string
rewardId: string
rewardName: string
cost: number
redeemedAt: string
pointsBefore: number
pointsAfter: number
}Reactive State
| 属性 | 类型 | 说明 |
|---|---|---|
rewards | Ref<ShopReward[]> | 奖励列表 |
shopHistory | Ref<ShopHistory[]> | 兑换历史 |
loading | Ref<boolean> | 加载状态 |
shopPoints | ComputedRef<number> | 当前积分(来自 userStore) |
availablePoints | ComputedRef<number> | 可用积分(同 shopPoints) |
hasPassword | ComputedRef<boolean> | 是否已设置密码 |
lockUntil | ComputedRef<number> | 密码锁定截止时间戳 |
presetIcons | const | 10 种预设图标:star, gift, game, book, music, sport, art, food, toy, travel |
方法
奖励管理
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
addReward | (data: { name, cost, icon }) => Promise<void> | — | 添加奖励,POST /shop/rewards → 重新 load |
updateReward | (id: string, data: Partial<{ name, cost, icon }>) => Promise<void> | — | 更新奖励,PUT /shop/rewards/:id → 重新 load |
deleteReward | (id: string) => Promise<void> | — | 删除奖励,DELETE /shop/rewards/:id → 重新 load |
toggleReward | (id: string) => Promise<void> | — | 启用/禁用奖励,PUT /shop/rewards/:id/toggle → 重新 load |
兑换
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
redeemReward | (rewardId: string) => Promise<boolean> | 是否成功 | 兑换奖励,成功后重新 load 奖励和用户数据 |
密码
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
verifyPassword | (pwd: string) => Promise<boolean> | 是否匹配 | 验证密码,POST /shop/password/verify |
setPassword | (newPwd: string, oldPwd?: string) => Promise<boolean> | 是否成功 | 设置/修改密码,PUT /shop/password |
工具
| 方法 | 签名 | 返回值 | 说明 |
|---|---|---|---|
getIconSvg | (icon: string) => string | SVG 路径 | 根据图标名称返回 SVG 文件路径 |