Skip to content

服务层 (Services)

4个 Service,TypeScript 实现 | 文件位于 web/src/services/


架构概览

services/
├── api-service.ts       ← HTTP 请求统一封装(axios)
├── resource-service.ts  ← 资源加载(笔顺/字源/音频,纯本地)
├── resource-cache.ts    ← LRU 缓存
└── reward-service.ts    ← 奖励发放(EXP/积分/徽章)

api-service.ts — API 服务

文件: web/src/services/api-service.ts

职责

  • 基于 axios 的统一 HTTP 请求封装
  • 响应拦截器自动解包 { code, msg, data } 格式
  • 切换后端只需修改 baseURL

API

typescript
export const api = {
  get<T>(url, params?): Promise<T>
  post<T>(url, data?): Promise<T>
  put<T>(url, data?): Promise<T>
  delete<T>(url): Promise<T>
}

配置

  • baseURL: import.meta.env.VITE_API_BASE_URL || "/api/v1"
  • timeout: 10000ms
  • 响应拦截器:code !== 0 时自动 reject,code === 0 时返回 data
  • 所有环境(开发/生产)均启用 Mock API(mockjs 拦截 axios 请求)

使用示例

typescript
// Store 中调用
const data = await api.get<UserData>("/users/me");
await api.put("/users/me", userData);
const chars = await api.get<{ list: Character[] }>("/characters", { pageSize: 500 });

详见 api-service.md


resource-service.ts — 资源服务

文件: web/src/services/resource-service.ts

职责

  • Hanzi Writer 笔顺数据加载(本地 JSON,import.meta.glob + 请求去重)
  • 字源 SVG 加载(本地 public/data/ 目录)
  • 汉字音频播放(本地 MP3 → TTS 降级)
  • 字源索引缓存管理(localStorage → 内存 → 本地文件)

核心函数

函数说明
getHanziWriterData(char)获取笔顺数据(LRU缓存 + 请求去重,同一汉字并发复用 Promise)
getEtymologySVGUrl(char, stage)获取字源 SVG 本地 URL
fetchEtymologyIndex()获取字源索引(localStorage → 内存 → 本地文件)
playAudio(char, pronunciation)播放汉字音频(本地 MP3 → TTS 降级)
getAudioUrl(pronunciation)获取音频本地 URL
checkEtymologyAvailable(char, stage)预检查字源 SVG 可用性(Image 预加载,3s 超时)
fetchEtymologyIndexRaw()获取字源索引原始 JSON(供 character.vue)
isEtymologyAvailableByIndex(char, script)通过缓存判断字源可用性(零延迟)

资源加载策略

资源策略
HanziWriter本地 JSON (import.meta.glob) + LRU 缓存 + 请求去重
EtymologySVG本地 public/data/etymology-svg/
字源索引localStorage → 内存缓存 → local index.json
HanziAudio本地 public/data/hanzi-audio/ → TTS 降级

详见 resource-service.md


resource-cache.ts — 资源缓存

文件: web/src/services/resource-cache.ts

职责

  • LRU 缓存策略,最大容量 50 个条目
  • 缓存 Hanzi Writer 笔顺数据等高频资源
  • 持久化到 localStorage(tzxq_resource_cache),500ms 防抖写入

方法

方法说明
resourceCache.get(key)获取缓存值(命中时移到最新位置)
resourceCache.set(key, value)设置缓存值(满时淘汰最旧条目)
resourceCache.has(key)检查缓存键是否存在
resourceCache.clear()清空所有缓存
resourceCache.size()获取当前缓存条目数

详见 resource-cache.md


reward-service.ts — 奖励服务

文件: web/src/services/reward-service.ts

职责

  • 所有模块的经验值(EXP)/ 积分(Points)/ 徽章发放统一入口
  • 确保奖励规则一致、去重、可追溯

奖励常量

常量EXP积分触发场景
WRITE_COMPLETE101完成一次书写练习
WRITE_PERFECT101书写满分
FIRST_LEARN51首次学会某个汉字
RECOGNITION_PASS50认读通过
RECOGNITION_FAIL10认读未通过
REVIEW_CHAR51完成单个汉字复习全流程
DAILY_REVIEW_FULL505每日复习完成率 100%
DAILY_REVIEW_80303每日复习完成率 ≥ 80%
BADGE_UNLOCK505解锁新徽章

核心函数

函数调用入口
awardWriteReward(char, score, isFirstLearn?)书写模块
awardRecognitionReward(char, score)认读模块
awardReviewCharReward(char)复习模块(带去重)
awardDailyReviewReward(percent)每日复习完成
awardGameReward(gameType, level, stars, levelConfig)3 款游戏
awardBadgeReward(badgeId)徽章解锁
resetReviewDedup()新会话开始时

详见 reward-service.md


数据流

页面组件 → Pinia Store → api-service.ts (axios)
                            ↓ (所有环境)
                       Mock API (mockjs 拦截)

                   mock/data/index.ts (JSON 数据)

                       localStorage (tzxq_mock_db)

资源流:

页面组件 → resource-service.ts → 本地 JSON/文件 → TTS 降级

         resource-cache.ts (LRU 缓存,localStorage 持久化)