本次提交完成了班级模块的多项核心功能开发与优化: 1. 新增DELETE请求封装,支持业务删除接口且不自动重试 2. 完善UserPostCommonDto模型,新增头像字段适配详情接口 3. 重构路由架构,使用StatefulShellRoute实现Tab页面保活 4. 新增我的互动页面,包含我的动态/点赞/留言三个子页 5. 新增全部同学页面,展示班级全员信息与在线状态 6. 新增动态详情页面,支持查看完整评论与点赞头像 7. 抽离通用PostListBody组件,复用列表分页与交互逻辑 8. 优化班级列表页加载逻辑与图片预览交互 9. 新增评论删除、多图预览等细节交互功能
200 lines
6.3 KiB
Dart
200 lines
6.3 KiB
Dart
import 'package:new_learn/core/api/class/model.dart';
|
||
import 'package:new_learn/core/network/dio_client.dart';
|
||
|
||
/// ============================================================
|
||
/// 【class 模块接口】班级模块请求 —— 对标 Vue3 端 api/class/index.ts + api/userPoint/index.ts
|
||
///
|
||
/// ClassApi:班级动态/成员/点赞/评论
|
||
/// getUserClassPost → 班级动态分页(游标 pageIndex + prevPageId)
|
||
/// getClassUsers → 班级成员在线状态
|
||
/// addUserLike → 点赞 / 取消点赞
|
||
/// addUserComment → 评论
|
||
///
|
||
/// UserRankApi:积分排行榜
|
||
/// getUserCount → 排行榜总人数(本班级 / 同年级 / 同云校)
|
||
/// getRankPage → 排行榜分页(游标分页:pageIndex + prevPageId)
|
||
///
|
||
/// 注意:页面只调这里,不直接碰 Dio;异常不在这里 catch,交页面统一处理。
|
||
/// ============================================================
|
||
class ClassApi {
|
||
ClassApi._();
|
||
|
||
static final ClassApi instance = ClassApi._();
|
||
|
||
/// 班级动态列表(queryType: 0 我的动态 / 1 班级动态)
|
||
Future<UserPostResponse> getUserClassPost({
|
||
int queryType = 1,
|
||
int pageIndex = 1,
|
||
int pageSize = 10,
|
||
int prevPageId = 0,
|
||
}) async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserPost/GetUserClassPost',
|
||
query: {
|
||
'queryType': queryType,
|
||
'pageIndex': pageIndex,
|
||
'pageSize': pageSize,
|
||
'prevPageId': prevPageId,
|
||
},
|
||
);
|
||
return UserPostResponse.fromJson(data as Map<String, dynamic>);
|
||
}
|
||
|
||
/// 班级成员(在线状态)
|
||
Future<List<ClassUser>> getClassUsers() async {
|
||
final data = await DioClient.instance.get('/api/v1/User/GetClassUsers');
|
||
final list = data is Map<String, dynamic> ? data['data'] : data;
|
||
if (list is List) {
|
||
return list
|
||
.whereType<Map<String, dynamic>>()
|
||
.map(ClassUser.fromJson)
|
||
.toList();
|
||
}
|
||
return const [];
|
||
}
|
||
|
||
/// 点赞 / 取消点赞(likeStatus: true 点赞 / false 取消)
|
||
Future<void> addUserLike({
|
||
required int userPostId,
|
||
required bool likeStatus,
|
||
}) async {
|
||
await DioClient.instance.post('/api/v1/UserLike/AddUserLike', data: {
|
||
'userPostId': userPostId,
|
||
'likeStatus': likeStatus,
|
||
});
|
||
}
|
||
|
||
/// 评论动态
|
||
Future<void> addUserComment({
|
||
required int userPostId,
|
||
required String content,
|
||
}) async {
|
||
await DioClient.instance.post('/api/v1/UserPost/AddUserCommon', data: {
|
||
'userPostId': userPostId,
|
||
'content': content,
|
||
});
|
||
}
|
||
|
||
/// 我的点赞列表 —— GET /api/v1/UserLike/GetUserLike(游标分页)
|
||
///
|
||
/// 后端每项包一层 {userPost: {...}},这里统一解包成 UserPostDto,
|
||
/// 页面拿到的是与班级动态一致的结构,直接复用 PostCard。
|
||
Future<UserPostResponse> getMyLikePage({
|
||
int pageIndex = 1,
|
||
int pageSize = 10,
|
||
int prevPageId = 0,
|
||
}) async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserLike/GetUserLike',
|
||
query: {
|
||
'pageIndex': pageIndex,
|
||
'pageSize': pageSize,
|
||
'prevPageId': prevPageId,
|
||
},
|
||
);
|
||
return _unwrapUserPostResponse(data);
|
||
}
|
||
|
||
/// 我的留言列表 —— GET /api/v1/UserPost/GetUserCommon(游标分页)
|
||
Future<UserPostResponse> getMyCommentPage({
|
||
int pageIndex = 1,
|
||
int pageSize = 10,
|
||
int prevPageId = 0,
|
||
}) async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserPost/GetUserCommon',
|
||
query: {
|
||
'pageIndex': pageIndex,
|
||
'pageSize': pageSize,
|
||
'prevPageId': prevPageId,
|
||
},
|
||
);
|
||
return _unwrapUserPostResponse(data);
|
||
}
|
||
|
||
/// 动态详情(含全量留言 / 点赞头像)—— GET /api/v1/UserPost/GetUserPostDetail/{id}
|
||
Future<UserPostDto> getUserPostDetail(int id) async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserPost/GetUserPostDetail/$id',
|
||
);
|
||
return UserPostDto.fromJson(data as Map<String, dynamic>);
|
||
}
|
||
|
||
/// 删除评论 —— DELETE /api/v1/UserPost/DeleteUserCommon/{id}
|
||
Future<void> deleteUserCommon(int commentId) async {
|
||
await DioClient.instance.delete(
|
||
'/api/v1/UserPost/DeleteUserCommon/$commentId',
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 解包「我的点赞/留言」列表:每项若包了 userPost 则取内层,再统一转 UserPostDto
|
||
UserPostResponse _unwrapUserPostResponse(dynamic data) {
|
||
final raw = data is Map<String, dynamic> ? data : {'data': data};
|
||
final payload = raw['data'] is Map<String, dynamic>
|
||
? raw['data'] as Map<String, dynamic>
|
||
: raw;
|
||
final list = payload['data'];
|
||
final posts = <UserPostDto>[];
|
||
if (list is List) {
|
||
for (final e in list) {
|
||
if (e is! Map<String, dynamic>) continue;
|
||
final inner = e['userPost'] is Map<String, dynamic>
|
||
? e['userPost'] as Map<String, dynamic>
|
||
: e;
|
||
posts.add(UserPostDto.fromJson(inner));
|
||
}
|
||
}
|
||
return UserPostResponse(
|
||
data: posts,
|
||
total: _toInt(payload['total']),
|
||
pageIndex: _toInt(payload['pageIndex']),
|
||
pageSize: _toInt(payload['pageSize']),
|
||
);
|
||
}
|
||
|
||
int _toInt(dynamic v) {
|
||
if (v is num) return v.toInt();
|
||
if (v is String) return int.tryParse(v) ?? 0;
|
||
return 0;
|
||
}
|
||
|
||
/// 积分排行榜接口
|
||
class UserRankApi {
|
||
UserRankApi._();
|
||
|
||
static final UserRankApi instance = UserRankApi._();
|
||
|
||
/// 排行榜总人数(本班级 / 同年级 / 同云校)
|
||
Future<UserCountResult> getUserCount() async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserPointsRecord/GetUserCount',
|
||
);
|
||
return UserCountResult.fromJson(data as Map<String, dynamic>);
|
||
}
|
||
|
||
/// 排行榜分页(游标分页:pageIndex + prevPageId)
|
||
/// [userRankType] 0 总榜 / 1 本周 / 2 上周 / 3 今天 / 4 昨天
|
||
/// [userClass] 0 本班级 / 1 同年级 / 2 同云校
|
||
Future<UserRankPagedList> getRankPage({
|
||
required int pageIndex,
|
||
required int pageSize,
|
||
required int prevPageId,
|
||
required int userRankType,
|
||
required int userClass,
|
||
required bool desc,
|
||
}) async {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/UserPointsRecord/GetUserRank',
|
||
query: {
|
||
'pageIndex': pageIndex,
|
||
'pageSize': pageSize,
|
||
'prevPageId': prevPageId,
|
||
'UserRankType': userRankType,
|
||
'UserClass': userClass,
|
||
'desc': desc,
|
||
},
|
||
);
|
||
return UserRankPagedList.fromJson(data as Map<String, dynamic>);
|
||
}
|
||
} |