Files
App.K12.Student.Miniapp_Flu…/lib/core/models/user_model.dart
T
崔宝新 5e4cfe9ac6 feat: 新增诊断报告全流程功能,适配登录后报告跳转逻辑
实现了入门测/出门测报告的完整链路:
1. 新增UserInfo模型的出门测报告标记字段与相关接口
2. 新增全局报告路由拦截器,自动在登录后/应用回前台时检查报告状态
3. 新增报告相关API、页面与路由配置,包含欢迎页、H5壳子、AI计算页
4. 适配登录成功后的报告跳转逻辑,更新用户状态管理方法
2026-09-09 16:49:51 +08:00

180 lines
6.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 【UserModel】用户相关模型
///
/// CurrentUserExtension:GET /api/v1/User/GetCurrentUserExtension 的响应体,
/// 对齐 Vue 端 api/user/type.ts 的 CurrentUserExtension 全部字段(17 个)。
///
/// 一、为什么字段全都要:
/// 登录后要缓存这套信息(user_storage.dart 里存 JSON),
/// "我的"页 / 任务页都可能用到,缺一个就得多调一次接口。
///
/// 二、高频坑:
/// 1. fromJson 全部 ?? 兜底,服务端缺字段/类型漂移不会闪崩;
/// 2. 数字统一 (json['x'] as num?)?.toInt(),防 int/double 不一致;
/// 3. toJson 与 fromJson 必须成对,否则缓存/回填会丢字段。
/// ============================================================
class CurrentUserExtension {
const CurrentUserExtension({
this.userId = 0,
this.userName = '',
this.userPoints = 0.0,
this.levelName = '',
this.avatar = '',
this.className = '',
this.classId = 0,
this.gradeLevel = '',
this.graduationYear = 0,
this.targetConfigId = 0,
this.targetConfigName = '',
this.userAvatarStatus = 0,
this.userAvatarReason = '',
this.isShowGuide = false,
this.isShowReport = false,
this.isShowAfterReport = false,
this.id = 0,
});
final int userId;
final String userName;
final double userPoints;
final String levelName;
final String avatar;
final String className;
final int classId;
final String gradeLevel;
final int graduationYear;
final int targetConfigId;
final String targetConfigName;
final int userAvatarStatus;
final String userAvatarReason;
final bool isShowGuide;
final bool isShowReport;
/// 是否显示出门测试报告(默认 false,后端置 true 后,用户完成报告查看后置回 false)
final bool isShowAfterReport;
final int id;
/// JSON → 对象(服务器响应)
///
/// 两个兼容点(对齐接口真实返回 {code, message, data: {...}}):
/// 1. data 包装:data 存在就取 data,直接返回 {userId,...} 也支持;
/// 2. 字符串数字:后端 userId/classId 返回的是字符串
/// ("820466383020136"),不能直接 as num,用 _toInt 统一转换。
factory CurrentUserExtension.fromJson(Map<String, dynamic> json) {
final payload = (json['data'] as Map<String, dynamic>?) ?? json;
return CurrentUserExtension(
userId: _toInt(payload['userId']),
userName: payload['userName'] as String? ?? '',
userPoints: _toDouble(payload['userPoints']),
levelName: payload['levelName'] as String? ?? '',
avatar: payload['avatar'] as String? ?? '',
className: payload['className'] as String? ?? '',
classId: _toInt(payload['classId']),
gradeLevel: payload['gradeLevel'] as String? ?? '',
graduationYear: _toInt(payload['graduationYear']),
targetConfigId: _toInt(payload['targetConfigId']),
targetConfigName: payload['targetConfigName'] as String? ?? '',
userAvatarStatus: _toInt(payload['userAvatarStatus']),
userAvatarReason: payload['userAvatarReason'] as String? ?? '',
isShowGuide: payload['isShowGuide'] as bool? ?? false,
isShowReport: payload['isShowReport'] as bool? ?? false,
isShowAfterReport: payload['isShowAfterReport'] as bool? ?? false,
id: _toInt(payload['id']),
);
}
/// 对象 → JSON(写入本地缓存)
Map<String, dynamic> toJson() {
return {
'userId': userId,
'userName': userName,
'userPoints': userPoints,
'levelName': levelName,
'avatar': avatar,
'className': className,
'classId': classId,
'gradeLevel': gradeLevel,
'graduationYear': graduationYear,
'targetConfigId': targetConfigId,
'targetConfigName': targetConfigName,
'userAvatarStatus': userAvatarStatus,
'userAvatarReason': userAvatarReason,
'isShowGuide': isShowGuide,
'isShowReport': isShowReport,
'isShowAfterReport': isShowAfterReport,
'id': id,
};
}
/// 局部更新(目前只用得上 avatar / isShowReport / isShowAfterReport)
CurrentUserExtension copyWith({
String? avatar,
bool? isShowReport,
bool? isShowAfterReport,
}) {
return CurrentUserExtension(
userId: userId,
userName: userName,
userPoints: userPoints,
levelName: levelName,
avatar: avatar ?? this.avatar,
className: className,
classId: classId,
gradeLevel: gradeLevel,
graduationYear: graduationYear,
targetConfigId: targetConfigId,
targetConfigName: targetConfigName,
userAvatarStatus: userAvatarStatus,
userAvatarReason: userAvatarReason,
isShowGuide: isShowGuide,
isShowReport: isShowReport ?? this.isShowReport,
isShowAfterReport: isShowAfterReport ?? this.isShowAfterReport,
id: id,
);
}
}
/// 用户积分等级(GET /api/v1/User/GetUserPointLevel,备用)
class UserPointLevel {
const UserPointLevel({
this.userId = 0,
this.totalPoints = 0,
this.level = 0,
this.levelPoints = 0,
this.levelMaxPoints = 0,
this.levelUpPoints = 0,
});
final int userId;
final int totalPoints;
final int level;
final int levelPoints;
final int levelMaxPoints;
final int levelUpPoints;
factory UserPointLevel.fromJson(Map<String, dynamic> json) {
return UserPointLevel(
userId: (json['userId'] as num?)?.toInt() ?? 0,
totalPoints: (json['totalPoints'] as num?)?.toInt() ?? 0,
level: (json['level'] as num?)?.toInt() ?? 0,
levelPoints: (json['levelPoints'] as num?)?.toInt() ?? 0,
levelMaxPoints: (json['levelMaxPoints'] as num?)?.toInt() ?? 0,
levelUpPoints: (json['levelUpPoints'] as num?)?.toInt() ?? 0,
);
}
}
/// 兼容"字符串数字"(后端 userId/classId 返回 "820466383020136")
int _toInt(dynamic v) {
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
/// 兼容小数积分(后端 userPoints 返回 105.5)
double _toDouble(dynamic v) {
if (v is num) return v.toDouble();
if (v is String) return double.tryParse(v) ?? 0;
return 0;
}