This commit is contained in:
chenshuainan
2026-09-28 20:12:34 +08:00
parent 847986f861
commit 5f53bd8f94
60 changed files with 14624 additions and 398 deletions
@@ -16,6 +16,7 @@ class CourseRecord {
this.accuracyRate = 0,
this.status = 0,
this.statusName = '',
this.overcomeTimeSecond = 0,
});
final int id; // 记录 id
@@ -23,6 +24,7 @@ class CourseRecord {
final int accuracyRate; // 正确率(0-100)
final int status; // 状态:0 未开始 / 1 进行中 / 2 成功 / 3 失败 等(按后端枚举)
final String statusName; // 状态名称
final int overcomeTimeSecond; // 攻克用时(秒)
factory CourseRecord.fromJson(Map<String, dynamic> json) {
return CourseRecord(
@@ -31,6 +33,7 @@ class CourseRecord {
accuracyRate: _parseInt(json['accuracyRate']),
status: _parseInt(json['status']),
statusName: json['statusName'] as String? ?? '',
overcomeTimeSecond: _parseInt(json['overcomeTimeSecond']),
);
}
}
@@ -0,0 +1,107 @@
import 'package:new_learn/core/api/conquerPackage/classExercises/model.dart';
import 'package:new_learn/core/network/dio_client.dart';
/// ============================================================
/// 【课堂习题模块接口】
///
/// 对标 Vue 端 api/task/index.ts、api/userCollect/index.ts:
///
/// GetRecordData GET /api/v1/StudyPack/GetRecord/{recordId}
/// GetListByRecordQuestion GET /api/v1/Question/GetListByRecord/{recordId}
/// QuestionRenewTime POST /api/v1/Question/RenewTime
/// PushAIGrading POST /api/v1/Question/PushAIGrading
/// RecordSubmit POST /api/v1/StudyPack/RecordSubmit
/// GetSubmitReport GET /api/v1/StudyPack/GetReport/{packId}(60s 超时)
/// AddUserCollect POST /api/v1/UserCollect/AddUserCollect
/// CancleCollect POST /api/v1/UserCollect/CancleCollect
/// Feedback POST /api/v1/Question/Feedback
///
/// 说明:
/// - RenewTime / PushAIGrading / RecordSubmit 不脱壳,返回原始 data,
/// 由提交控制器读取 code / message / aiGradingCount / id;
/// - 异常统一抛 ApiException,交页面/控制器处理。
/// ============================================================
class ClassExercisesApi {
ClassExercisesApi._();
static final ClassExercisesApi instance = ClassExercisesApi._();
/// 获取学习记录信息(edit 模式 onLoad 第一步)
Future<ClassExercisesRecord> getRecordData(String recordId) async {
final data = await DioClient.instance.get(
'/api/v1/StudyPack/GetRecord/$recordId',
);
return ClassExercisesRecord.fromJson(data);
}
/// 根据学习记录获取题目列表
Future<ClassExercisesQuestionList> getListByRecordQuestion(
String recordId,
) async {
final data = await DioClient.instance.get(
'/api/v1/Question/GetListByRecord/$recordId',
);
return ClassExercisesQuestionList.fromJson(data);
}
/// 提交单题作答 / 更新作答时长
/// body: {id, childId, courseId, userAnswer, startTime, endTime,
/// answerStatus, index?}
/// 返回原始响应(提交失败重试队列会读 requestParams/data 兜底)
Future<dynamic> questionRenewTime(Map<String, dynamic> body) {
return DioClient.instance.post('/api/v1/Question/RenewTime', data: body);
}
/// 提交 AI 图片批改
/// body: {id, childId, courseId, startTime, endTime,
/// answerImgs:[{url,thumbnailUrl}]}
Future<dynamic> pushAIGrading(Map<String, dynamic> body) {
return DioClient.instance.post(
'/api/v1/Question/PushAIGrading',
data: body,
);
}
/// 交卷
/// body: {recordId};返回 {code,message,aiGradingCount}
Future<dynamic> recordSubmit(String recordId) {
return DioClient.instance.post(
'/api/v1/StudyPack/RecordSubmit',
data: {'recordId': recordId},
);
}
/// 获取攻克报告(view 模式数据源,题目批改可能耗时,超时 60s)
Future<ClassExercisesReport> getSubmitReport(String packId) async {
final data = await DioClient.instance.get(
'/api/v1/StudyPack/GetReport/$packId',
receiveTimeout: const Duration(seconds: 60),
);
return ClassExercisesReport.fromJson(data);
}
/// 添加收藏 / 错题本
/// body: {type, packId, questionId, recordQuestionId}
/// 返回原始响应(成功时 res.id 为收藏记录 id)
Future<dynamic> addUserCollect(Map<String, dynamic> body) {
return DioClient.instance.post(
'/api/v1/UserCollect/AddUserCollect',
data: body,
);
}
/// 取消收藏 / 移出错题本
/// body: {type, id}
Future<dynamic> cancleCollect(Map<String, dynamic> body) {
return DioClient.instance.post(
'/api/v1/UserCollect/CancleCollect',
data: body,
);
}
/// 题目反馈
/// body: {id, questionId, feedbackTypes, description, images}
Future<dynamic> feedback(Map<String, dynamic> body) {
return DioClient.instance.post('/api/v1/Question/Feedback', data: body);
}
}
@@ -0,0 +1,127 @@
/// ============================================================
/// 【课堂习题模块模型】接口响应壳解析
///
/// 对标 Vue 端:
/// api/task/index.ts → GetRecordData / GetListByRecordQuestion
/// / GetSubmitReport / RecordSubmit
/// api/question/index.ts → RenewTime / PushAIGrading / Feedback
///
/// 说明:
/// 题目字段非常多且解析器(questionParser)会动态增删字段,
/// 题目列表统一以 `Map<String, dynamic>` 承载,业务侧再包 QuestionModel,
/// 全量保留后端原字段名,不做字段裁剪。
/// ============================================================
library;
/// 从响应中取业务 payload(兼容 {code,message,data:{...}} 脱壳)
Map<String, dynamic> unwrapData(dynamic resp) {
if (resp is Map<String, dynamic>) {
final data = resp['data'];
if (data is Map<String, dynamic>) return data;
return resp;
}
return <String, dynamic>{};
}
/// GET StudyPack/GetRecord/{recordId} 响应
class ClassExercisesRecord {
const ClassExercisesRecord({
this.recordId = '',
this.name = '',
this.baseSubjectId,
this.raw = const {},
});
/// 学习记录 id
final String recordId;
/// 攻克包名称(用于 NavBar 标题:`${name}专项智能攻克`)
final String name;
/// 学科 id(决定 iSFilllankItemUpload:0 输入框 / 1 图片作答)
final int? baseSubjectId;
final Map<String, dynamic> raw;
factory ClassExercisesRecord.fromJson(dynamic json) {
final data = unwrapData(json);
return ClassExercisesRecord(
recordId:
data['recordId']?.toString() ?? data['id']?.toString() ?? '',
name: data['name']?.toString() ?? '',
baseSubjectId:
int.tryParse('${data['baseSubjectId'] ?? ''}'),
raw: data,
);
}
}
/// GET Question/GetListByRecord/{recordId} 响应
class ClassExercisesQuestionList {
const ClassExercisesQuestionList({
this.items = const [],
this.raw = const {},
});
/// 原始题目列表(字段全量保留,交给 parseQuestionData 处理)
final List<Map<String, dynamic>> items;
final Map<String, dynamic> raw;
factory ClassExercisesQuestionList.fromJson(dynamic json) {
final data = unwrapData(json);
final rawList = data['questions'] ?? data['list'] ?? data['data'];
final list = <Map<String, dynamic>>[];
if (rawList is List) {
for (final item in rawList) {
if (item is Map) {
list.add(Map<String, dynamic>.from(item));
}
}
}
return ClassExercisesQuestionList(items: list, raw: data);
}
}
/// GET StudyPack/GetReport/{packId} 响应(view 模式数据源)
class ClassExercisesReport {
const ClassExercisesReport({
this.id = '',
this.name = '',
this.baseSubjectId,
this.questions = const [],
this.raw = const {},
});
/// 报告/记录 id(view 模式下作为 recordId 使用)
final String id;
final String name;
final int? baseSubjectId;
/// 报告内题目列表(字段全量保留)
final List<Map<String, dynamic>> questions;
final Map<String, dynamic> raw;
factory ClassExercisesReport.fromJson(dynamic json) {
final data = unwrapData(json);
final rawList = data['questions'];
final list = <Map<String, dynamic>>[];
if (rawList is List) {
for (final item in rawList) {
if (item is Map) {
list.add(Map<String, dynamic>.from(item));
}
}
}
return ClassExercisesReport(
id: data['id']?.toString() ?? data['recordId']?.toString() ?? '',
name: data['name']?.toString() ?? '',
baseSubjectId: int.tryParse('${data['baseSubjectId'] ?? ''}'),
questions: list,
raw: data,
);
}
}