flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:new_learn/core/api/auth/model.dart';
|
||||
import 'package:new_learn/core/models/user_model.dart';
|
||||
import 'package:new_learn/core/network/dio_client.dart';
|
||||
import 'package:new_learn/core/storage/user_storage.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【UserApi】用户相关接口(对齐 Vue 端 api/user/index.ts)
|
||||
///
|
||||
/// 分层职责(像 Vue 项目把接口收敛到 api 目录一样):
|
||||
/// Page(UI) → UserApi(业务方法) → DioClient(Dio 封装) → 服务器
|
||||
///
|
||||
/// 一、已实现:
|
||||
/// login → POST /api/v1/User/Login
|
||||
/// getCurrentUserExtension → GET /api/v1/User/GetCurrentUserExtension
|
||||
/// (登录后拿完整用户信息:昵称/头像/班级/等级等)
|
||||
/// getCurrentUser → 统一入口(本地缓存优先)
|
||||
/// updateUserAvatar → 更改用户头像
|
||||
///
|
||||
/// 二、后续继续写(对齐 Vue,用到再加):
|
||||
/// checkStudyPackStatus / setChangeUserOnline
|
||||
/// getUserPointLevel / UpdateGuideStatus / UpdateReportStatus
|
||||
///
|
||||
/// 三、高频坑:
|
||||
/// 1. 登录密码 MD5 加密(对齐 Vue stringToMd5),提交前在 Api 层完成;
|
||||
/// 2. getCurrentUserExtension 必须带 Token 才有效 ——
|
||||
/// 登录成功已把 Token 写进 DioClient.accessToken,自动带上;
|
||||
/// 3. 不要在这里 catch ApiException —— 交给页面统一处理展示。
|
||||
/// ============================================================
|
||||
class UserApi {
|
||||
// ==================== 登录 ====================
|
||||
|
||||
/// 登录 → POST /api/v1/User/Login
|
||||
/// 密码规则对齐 Vue 端 stringToMd5:MD5 加密后再提交
|
||||
static Future<LoginResponse> login({
|
||||
required String account,
|
||||
required String password,
|
||||
}) async {
|
||||
final request = LoginRequest(
|
||||
account: account.trim(),
|
||||
password: _md5(password.trim()),
|
||||
);
|
||||
final data = await DioClient.instance.post(
|
||||
'/api/v1/User/Login',
|
||||
data: request.toJson(),
|
||||
);
|
||||
return LoginResponse.fromJson(data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
/// 登录后拉取完整用户信息 → GET /api/v1/User/GetCurrentUserExtension
|
||||
static Future<CurrentUserExtension> getCurrentUserExtension() async {
|
||||
final data = await DioClient.instance.get(
|
||||
'/api/v1/User/GetCurrentUserExtension',
|
||||
);
|
||||
return CurrentUserExtension.fromJson(data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
/// 获取当前用户(统一入口,缓存优先):
|
||||
/// 1. 有本地缓存 → 直接返回(不费流量不转圈);
|
||||
/// 2. 无缓存或 forceRefresh → 调 GetCurrentUserExtension 拉最新
|
||||
/// (Token 由 Dio 拦截器自动带进 header)并写回缓存;
|
||||
/// 3. 接口失败 → 回退返回本地缓存,缓存也没有才抛异常。
|
||||
/// 页面展示用户信息一律走这里,别直接调 getCurrentUserExtension。
|
||||
static Future<CurrentUserExtension> getCurrentUser({
|
||||
bool forceRefresh = false,
|
||||
}) async {
|
||||
final cached = await UserStorage.instance.readUserInfo();
|
||||
if (!forceRefresh && cached != null) return cached;
|
||||
|
||||
try {
|
||||
final ext = await getCurrentUserExtension();
|
||||
await UserStorage.instance.saveUserInfo(ext);
|
||||
return ext;
|
||||
} catch (_) {
|
||||
if (cached != null) return cached; // 网络失败回退本地缓存
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 更改用户头像 → POST /api/v1/User/UpdateUserAvatar
|
||||
///
|
||||
/// 对齐 UniApp:头像先传 OSS,拿到 url 后再把 {avatar: url} 提交给业务接口。
|
||||
static Future<void> updateUserAvatar(String avatar) async {
|
||||
await DioClient.instance.post(
|
||||
'/api/v1/User/UpdateUserAvatar',
|
||||
data: {'avatar': avatar},
|
||||
);
|
||||
}
|
||||
|
||||
/// MD5 加密(UniApp stringToMd5 的等价实现)
|
||||
static String _md5(String text) {
|
||||
return md5.convert(utf8.encode(text)).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/// ============================================================
|
||||
/// 【auth 模块模型】登录相关实体 —— 对照 Vue 项目 src/api/login.ts
|
||||
///
|
||||
/// LoginRequest → 登录请求体(toJson 提交给服务器)
|
||||
/// LoginResponse → 登录响应(token + 用户信息)
|
||||
/// UserInfo → 用户信息子模型(info 字段)
|
||||
///
|
||||
/// 高频坑:
|
||||
/// 1. fromJson 全部用 ?? 兜底,字段缺失/类型漂移不会闪崩;
|
||||
/// 2. 数字用 (json['x'] as num?)?.toInt() 防 int/double 不一致;
|
||||
/// 3. 兼容业务响应壳 {code, msg, data: {token, info}}。
|
||||
/// ============================================================
|
||||
library;
|
||||
|
||||
class LoginRequest {
|
||||
const LoginRequest({required this.account, required this.password});
|
||||
|
||||
final String account;
|
||||
final String password;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'account': account,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LoginResponse {
|
||||
const LoginResponse({required this.token, required this.info});
|
||||
|
||||
final String token;
|
||||
final UserInfo info;
|
||||
|
||||
factory LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
// 兼容常见包装结构:后端可能直接返回 {token, info},
|
||||
// 也可能包一层 {code, msg, data: {token, info}}(业务响应通用壳)。
|
||||
// data 存在就取 data,否则直接用 json,两种都能解析到。
|
||||
final payload = (json['data'] as Map<String, dynamic>?) ?? json;
|
||||
return LoginResponse(
|
||||
token: payload['token'] as String? ?? '',
|
||||
info: UserInfo.fromJson(
|
||||
(payload['info'] as Map<String, dynamic>?) ?? const {},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户信息(对齐 Vue 端 getUserInfo 的字段,未知的用 ?? 兜底)
|
||||
class UserInfo {
|
||||
const UserInfo({
|
||||
this.userId = 0,
|
||||
this.userName = '',
|
||||
this.account = '',
|
||||
this.avatar = '',
|
||||
this.phone = '',
|
||||
this.isShowReport = false,
|
||||
});
|
||||
|
||||
final int userId;
|
||||
final String userName;
|
||||
final String account;
|
||||
final String avatar;
|
||||
final String phone;
|
||||
final bool isShowReport;
|
||||
|
||||
factory UserInfo.fromJson(Map<String, dynamic> json) {
|
||||
return UserInfo(
|
||||
// userId 可能是字符串(后端统一风格),不能直接 as num
|
||||
userId: _toInt(json['userId']),
|
||||
userName: json['userName'] as String? ?? '',
|
||||
account: json['account'] as String? ?? '',
|
||||
avatar: json['avatar'] as String? ?? '',
|
||||
phone: json['phone'] as String? ?? '',
|
||||
isShowReport: json['isShowReport'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 兼容"字符串数字"(后端 userId 返回 "820466383020136")
|
||||
int _toInt(dynamic v) {
|
||||
if (v is num) return v.toInt();
|
||||
if (v is String) return int.tryParse(v) ?? 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 积分排行榜接口
|
||||
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>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/// ============================================================
|
||||
/// 【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,
|
||||
});
|
||||
|
||||
final int userId;
|
||||
final String userName;
|
||||
final String content;
|
||||
final String createTime;
|
||||
final int id;
|
||||
|
||||
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']),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:new_learn/core/api/my_point/model.dart';
|
||||
import 'package:new_learn/core/network/dio_client.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【my_point 模块接口】积分接口 —— 对标 Vue3 端 api/userPoint/index.ts
|
||||
///
|
||||
/// getRecordPage —— 积分记录分页(页码分页)
|
||||
/// GET /api/v1/UserPointsRecord/GetUserPointRecordPagedList
|
||||
/// 参数:pageIndex / pageSize
|
||||
///
|
||||
/// 说明:排行榜相关接口(getUserCount / getUserRankPagedList)归属班级模块,
|
||||
/// 统一收在 core/api/class/api.dart。
|
||||
/// ============================================================
|
||||
class UserPointApi {
|
||||
UserPointApi._();
|
||||
|
||||
static final UserPointApi instance = UserPointApi._();
|
||||
|
||||
/// 积分记录分页(页码分页:pageIndex / pageSize)
|
||||
Future<UserPointRecordPage> getRecordPage({
|
||||
int pageIndex = 1,
|
||||
int pageSize = 10,
|
||||
}) async {
|
||||
final data = await DioClient.instance.get(
|
||||
'/api/v1/UserPointsRecord/GetUserPointRecordPagedList',
|
||||
query: {'pageIndex': pageIndex, 'pageSize': pageSize},
|
||||
);
|
||||
return UserPointRecordPage.fromJson(data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/// ============================================================
|
||||
/// 【my_point 模块模型】积分实体 —— 对标 Vue3 端 api/userPoint/type.ts
|
||||
///
|
||||
/// UserPointRecord(积分变动记录)/ UserPointRecordPage(分页响应)
|
||||
///
|
||||
/// 序列化:手动 fromJson(兼容 {code, message, data: {...}} 响应壳),
|
||||
/// 等价原 point_model_original_version.dart 的自包含实现,不依赖 build_runner。
|
||||
/// ============================================================
|
||||
library;
|
||||
|
||||
/// 单条积分变动记录
|
||||
class UserPointRecord {
|
||||
const UserPointRecord({
|
||||
this.points = 0,
|
||||
this.pointMessage = '',
|
||||
this.type = 0,
|
||||
this.bizTime = '',
|
||||
});
|
||||
|
||||
final int points; // 积分变动值(正=加分,负=扣分)
|
||||
final String pointMessage; // 变动说明
|
||||
final int type; // 类型
|
||||
final String bizTime; // 变动时间字符串
|
||||
|
||||
factory UserPointRecord.fromJson(Map<String, dynamic> json) {
|
||||
return UserPointRecord(
|
||||
points: (json['points'] as num?)?.toInt() ?? 0,
|
||||
pointMessage: json['pointMessage'] as String? ?? '',
|
||||
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||
bizTime: json['bizTime'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 积分记录分页响应
|
||||
class UserPointRecordPage {
|
||||
const UserPointRecordPage({
|
||||
this.data = const [],
|
||||
this.total = 0,
|
||||
this.pageIndex = 1,
|
||||
this.pageSize = 10,
|
||||
});
|
||||
|
||||
final List<UserPointRecord> data; // 当前页记录
|
||||
final int total; // 总记录数
|
||||
final int pageIndex;
|
||||
final int pageSize;
|
||||
|
||||
/// 兼容业务响应壳 {code, message, data: {...}}:data 存在就取 data
|
||||
factory UserPointRecordPage.fromJson(Map<String, dynamic> json) {
|
||||
final payload = (json['data'] as Map<String, dynamic>?) ?? json;
|
||||
return UserPointRecordPage(
|
||||
data: (payload['data'] as List<dynamic>? ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(UserPointRecord.fromJson)
|
||||
.toList(),
|
||||
total: (payload['total'] as num?)?.toInt() ?? 0,
|
||||
pageIndex: (payload['pageIndex'] as num?)?.toInt() ?? 1,
|
||||
pageSize: (payload['pageSize'] as num?)?.toInt() ?? 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user