169 lines
5.6 KiB
Dart
169 lines
5.6 KiB
Dart
/// ============================================================
|
||
/// 【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.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;
|
||
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,
|
||
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,
|
||
'id': id,
|
||
};
|
||
}
|
||
|
||
/// 局部更新(目前只用得上 avatar:头像上传成功后替换 URL)
|
||
CurrentUserExtension copyWith({String? avatar}) {
|
||
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,
|
||
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;
|
||
}
|