Files
App.K12.Student.Miniapp_Flu…/lib/core/api/class/model.dart
T
崔宝新 586c75f841 feat: 完成班级模块全量功能迭代
本次提交完成了班级模块的多项核心功能开发与优化:
1. 新增DELETE请求封装,支持业务删除接口且不自动重试
2. 完善UserPostCommonDto模型,新增头像字段适配详情接口
3. 重构路由架构,使用StatefulShellRoute实现Tab页面保活
4. 新增我的互动页面,包含我的动态/点赞/留言三个子页
5. 新增全部同学页面,展示班级全员信息与在线状态
6. 新增动态详情页面,支持查看完整评论与点赞头像
7. 抽离通用PostListBody组件,复用列表分页与交互逻辑
8. 优化班级列表页加载逻辑与图片预览交互
9. 新增评论删除、多图预览等细节交互功能
2026-09-07 09:20:06 +08:00

315 lines
8.8 KiB
Dart

/// ============================================================
/// 【class 模块模型】班级模块实体 —— 对标 Vue3 api/class/type.ts + api/userPoint/type.ts
///
/// 内容(合并自原 features/class/models/):
/// 班级动态:UserPostCommonDto / UserPostDto / UserPostResponse / ClassUser
/// 积分排行:UserRank / UserRankPagedList / UserCountResult
///
/// 序列化:手动 fromJson(响应壳 {code,message,data:{...}} 先解包 data,再逐字段取),
/// 不依赖 build_runner。数字用 _toInt 兜底字符串数字。
///
/// 说明:UserPostDto 提供 copyWith(点赞/评论乐观更新需要在列表内替换单条)。
/// ============================================================
library;
// ============ 班级动态 ============
class UserPostCommonDto {
const UserPostCommonDto({
this.userId = 0,
this.userName = '',
this.content = '',
this.createTime = '',
this.id = 0,
this.avatar = '',
});
final int userId;
final String userName;
final String content;
final String createTime;
final int id;
final String avatar;
factory UserPostCommonDto.fromJson(Map<String, dynamic> json) =>
UserPostCommonDto(
userId: _toInt(json['userId']),
userName: json['userName'] as String? ?? '',
content: json['content'] as String? ?? '',
createTime: json['createTime'] as String? ?? '',
id: _toInt(json['id']),
// 详情接口的留言项带评论者头像
avatar: json['avatar'] as String? ?? '',
);
}
class UserPostDto {
const UserPostDto({
this.userId = 0,
this.userName = '',
this.avatar = '',
this.classId = 0,
this.content = '',
this.likeCount = 0,
this.replyCount = 0,
this.subjectName = '',
this.createTime = '',
this.userLikeStatus = false,
this.commons = const [],
this.id = 0,
this.urls = const [],
this.type = 0, // 1 攻克任务 2 试题笔记 3 讲义笔记
this.userLikeAvatars = const [],
});
final int userId;
final String userName;
final String avatar;
final int classId;
final String content;
final int likeCount;
final int replyCount;
final String subjectName;
final String createTime;
final bool userLikeStatus;
final List<UserPostCommonDto> commons;
final int id;
final List<String> urls;
final int type;
final List<String> userLikeAvatars;
factory UserPostDto.fromJson(Map<String, dynamic> json) => UserPostDto(
userId: _toInt(json['userId']),
userName: json['userName'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
classId: _toInt(json['classId']),
content: json['content'] as String? ?? '',
likeCount: _toInt(json['likeCount']),
replyCount: _toInt(json['replyCount']),
subjectName: json['subjectName'] as String? ?? '',
createTime: json['createTime'] as String? ?? '',
userLikeStatus: json['userLikeStatus'] as bool? ?? false,
commons: (json['commons'] as List<dynamic>? ?? const [])
.whereType<Map<String, dynamic>>()
.map(UserPostCommonDto.fromJson)
.toList(),
id: _toInt(json['id']),
urls: (json['urls'] as List<dynamic>? ?? const [])
.whereType<String>()
.toList(),
type: _toInt(json['type']),
userLikeAvatars: (json['userLikeAvatars'] as List<dynamic>? ?? const [])
.whereType<String>()
.toList(),
);
UserPostDto copyWith({
int? userId,
String? userName,
String? avatar,
int? classId,
String? content,
int? likeCount,
int? replyCount,
String? subjectName,
String? createTime,
bool? userLikeStatus,
List<UserPostCommonDto>? commons,
int? id,
List<String>? urls,
int? type,
List<String>? userLikeAvatars,
}) {
return UserPostDto(
userId: userId ?? this.userId,
userName: userName ?? this.userName,
avatar: avatar ?? this.avatar,
classId: classId ?? this.classId,
content: content ?? this.content,
likeCount: likeCount ?? this.likeCount,
replyCount: replyCount ?? this.replyCount,
subjectName: subjectName ?? this.subjectName,
createTime: createTime ?? this.createTime,
userLikeStatus: userLikeStatus ?? this.userLikeStatus,
commons: commons ?? this.commons,
id: id ?? this.id,
urls: urls ?? this.urls,
type: type ?? this.type,
userLikeAvatars: userLikeAvatars ?? this.userLikeAvatars,
);
}
}
/// 班级动态分页响应
///
/// 响应壳:{code, message, data: {data: UserPostDto[], total, ...}}
/// 外层 data 是分页对象,内层 data 是动态数组。
class UserPostResponse {
const UserPostResponse({
this.data = const [],
this.total = 0,
this.pageIndex = 1,
this.pageSize = 10,
this.id = 0,
});
final List<UserPostDto> data;
final int total;
final int pageIndex;
final int pageSize;
final int id;
factory UserPostResponse.fromJson(Map<String, dynamic> json) {
final payload = json['data'] is Map<String, dynamic>
? json['data'] as Map<String, dynamic>
: json;
final list = payload['data'];
return UserPostResponse(
data: list is List
? list
.whereType<Map<String, dynamic>>()
.map(UserPostDto.fromJson)
.toList()
: const [],
total: _toInt(payload['total']),
pageIndex: _toInt(payload['pageIndex']),
pageSize: _toInt(payload['pageSize']),
id: _toInt(payload['id']),
);
}
}
class ClassUser {
const ClassUser({
this.userId = 0,
this.userName = '',
this.avatar = '',
this.isOnline = false,
});
final int userId;
final String userName;
final String avatar;
final bool isOnline;
factory ClassUser.fromJson(Map<String, dynamic> json) => ClassUser(
userId: _toInt(json['userId']),
userName: json['userName'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
isOnline: json['isOnline'] as bool? ?? false,
);
}
// ============ 积分排行 ============
class UserRank {
const UserRank({
this.userName = '',
this.userId = 0,
this.userAvatar = '',
this.userClassName = '',
this.rank = 0,
this.points = 0,
});
final String userName;
final int userId;
final String userAvatar;
final String userClassName;
final int rank;
final double points;
factory UserRank.fromJson(Map<String, dynamic> json) => UserRank(
userName: json['userName'] as String? ?? '',
userId: _toInt(json['userId']),
userAvatar: json['userAvatar'] as String? ?? '',
userClassName: json['userClassName'] as String? ?? '',
rank: _toInt(json['rank']),
points: _toDouble(json['points']),
);
}
class UserRankPagedList {
const UserRankPagedList({
this.myRank,
this.userRanks = const [],
this.total = 0,
this.pageIndex = 1,
this.pageSize = 20,
});
final UserRank? myRank;
final List<UserRank> userRanks;
final int total;
final int pageIndex;
final int pageSize;
factory UserRankPagedList.fromJson(Map<String, dynamic> json) {
final payload = json['data'] is Map<String, dynamic>
? json['data'] as Map<String, dynamic>
: json;
// 兼容 userRanks / data 两种返回字段
final ranksList = payload['userRanks'] is List
? payload['userRanks'] as List<dynamic>
: (payload['data'] is List
? payload['data'] as List<dynamic>
: const []);
return UserRankPagedList(
myRank: payload['myRank'] is Map<String, dynamic>
? UserRank.fromJson(payload['myRank'] as Map<String, dynamic>)
: null,
userRanks: ranksList
.whereType<Map<String, dynamic>>()
.map(UserRank.fromJson)
.toList(),
total: _toInt(payload['total']),
pageIndex: _toInt(payload['pageIndex']),
pageSize: _toInt(payload['pageSize']),
);
}
}
class UserCountResult {
const UserCountResult({
this.classUserCount,
this.gradeUserCount,
this.cloudSchoolUserCount,
});
final int? classUserCount;
final int? gradeUserCount;
final int? cloudSchoolUserCount;
factory UserCountResult.fromJson(Map<String, dynamic> json) {
final payload = json['data'] is Map<String, dynamic>
? json['data'] as Map<String, dynamic>
: json;
return UserCountResult(
classUserCount: _toIntOrNull(payload['classUserCount']),
gradeUserCount: _toIntOrNull(payload['gradeUserCount']),
cloudSchoolUserCount: _toIntOrNull(payload['cloudSchoolUserCount']),
);
}
}
// ============ 类型转换工具 ============
int _toInt(dynamic v) {
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
double _toDouble(dynamic v) {
if (v is num) return v.toDouble();
if (v is String) return double.tryParse(v) ?? 0;
return 0;
}
int? _toIntOrNull(dynamic v) {
if (v == null) return null;
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v);
return null;
}