feat(recite): 完成背诵模块基础框架与核心页面开发

本次提交实现了背诵模块的完整基础链路:
1. 新增词书、背单词接口层与对应数据模型
2. 完成背诵首页、章节列表页等核心页面开发
3. 实现音频播放、加载弹窗等通用组件
4. 配置路由跳转链路,替换旧版缓存传参方案
5. 对齐Vue端业务逻辑与UI样式
This commit is contained in:
崔宝新
2026-09-11 14:38:47 +08:00
parent c65df455f2
commit 27eba92315
17 changed files with 4367 additions and 36 deletions
+86
View File
@@ -0,0 +1,86 @@
import 'package:new_learn/core/api/lexicon/model.dart';
import 'package:new_learn/core/network/dio_client.dart';
/// ============================================================
/// 【lexicon 模块接口】背单词接口 —— 对标 Vue3 端 api/lexicon/index.ts
///
/// getChapters → GET GetChapters/{bookId} 章节列表
/// getLevels → GET GetLevels/{chapterId} 关卡列表
/// levelStart → POST LevelStart/{levelId} 开局取题
/// answerQuestion → POST Answer 提交答案
/// getLevelIsFinish → GET GetLevelIsFinish/{levelId} 关卡是否完成(轮询)
/// getLevelOverInfo → GET GetLevelOverInfo/{userLevelId} 闯关小结
///
/// 注意:getChapters Vue 端带 hideErrorToast,toast 由页面自行处理;
/// 异常统一向上抛,页面 catch 后决定提示。
/// ============================================================
class LexiconApi {
LexiconApi._();
static final LexiconApi instance = LexiconApi._();
/// 后端响应壳 {code, message, data} 统一取 data(用于裸值响应)
dynamic _unwrap(dynamic data) =>
data is Map<String, dynamic> && data['data'] != null
? data['data']
: data;
/// 章节列表(model 内部兼容响应壳)
Future<ChapterResponse> getChapters(int bookId) async {
final data = await DioClient.instance.get(
'/api/v1/Lexicon/GetChapters/$bookId',
);
return ChapterResponse.fromJson(data as Map<String, dynamic>);
}
/// 关卡列表
Future<LevelResponse> getLevels(int chapterId) async {
final data = await DioClient.instance.get(
'/api/v1/Lexicon/GetLevels/$chapterId',
);
return LevelResponse.fromJson(data as Map<String, dynamic>);
}
/// 开局取题
Future<LevelStartInfo> levelStart(int levelId) async {
final data = await DioClient.instance.post(
'/api/v1/Lexicon/LevelStart/$levelId',
);
return LevelStartInfo.fromJson(data as Map<String, dynamic>);
}
/// 提交答案(返回值与开局信息同构,但页面只关心成功与否)
Future<bool> answerQuestion({
required int levelId,
required int userLevelId,
required int lexiconId,
required String answer,
}) async {
final data = await DioClient.instance.post(
'/api/v1/Lexicon/Answer',
data: {
'levelId': levelId,
'userLevelId': userLevelId,
'lexiconId': lexiconId,
'answer': answer,
},
);
return data != null;
}
/// 关卡是否完成(完关后后端有短暂处理延迟,需轮询)
Future<bool> getLevelIsFinish(int levelId) async {
final data = _unwrap(
await DioClient.instance.get('/api/v1/Lexicon/GetLevelIsFinish/$levelId'),
);
return data == true;
}
/// 闯关小结
Future<LevelOverInfo> getLevelOverInfo(int userLevelId) async {
final data = await DioClient.instance.get(
'/api/v1/Lexicon/GetLevelOverInfo/$userLevelId',
);
return LevelOverInfo.fromJson(data as Map<String, dynamic>);
}
}