实现了入门测/出门测报告的完整链路: 1. 新增UserInfo模型的出门测报告标记字段与相关接口 2. 新增全局报告路由拦截器,自动在登录后/应用回前台时检查报告状态 3. 新增报告相关API、页面与路由配置,包含欢迎页、H5壳子、AI计算页 4. 适配登录成功后的报告跳转逻辑,更新用户状态管理方法
165 lines
6.4 KiB
Dart
165 lines
6.4 KiB
Dart
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},
|
||
);
|
||
}
|
||
|
||
/// 注销账号 → POST /api/v1/User/CloseAccount
|
||
///
|
||
/// 对齐 Vue api/login: `closeAccount = () => request.post('/api/v1/User/CloseAccount')`
|
||
/// 调用方负责弹确认框 + 注销后清理本地状态。
|
||
static Future<void> closeAccount() async {
|
||
await DioClient.instance.post('/api/v1/User/CloseAccount');
|
||
}
|
||
|
||
// ==================== 注册 ====================
|
||
|
||
/// 注册 → POST /api/v1/User/Register
|
||
///
|
||
/// 对齐 Vue api/login `register`: { account, password },密码同样 MD5 加密
|
||
/// Vue RegisterResponse = { code, message }(业务响应壳),Flutter 端 DioClient
|
||
/// 已自动处理成功/失败分支,成功不关心 body,失败直接抛 ApiException。
|
||
static Future<void> register({
|
||
required String account,
|
||
required String password,
|
||
}) async {
|
||
await DioClient.instance.post(
|
||
'/api/v1/User/Register',
|
||
data: {'account': account.trim(), 'password': _md5(password.trim())},
|
||
);
|
||
}
|
||
|
||
// ==================== 审核模式 ====================
|
||
|
||
/// 获取审核模式(决定是否展示"去注册"入口)→ GET /api/v1/Public/GetIsAuditMode
|
||
///
|
||
/// 对齐 Vue api/login: `getAuditMode()`,返回值本身就是 boolean
|
||
/// (request 拦截器已解包),接口不可用时安全回退 false(不展示注册入口)。
|
||
static Future<bool> getAuditMode() async {
|
||
try {
|
||
final data = await DioClient.instance.get(
|
||
'/api/v1/Public/GetIsAuditMode',
|
||
);
|
||
if (data is bool) return data;
|
||
if (data is Map) {
|
||
// 兼容 { isAuditMode: true } 包装
|
||
final v = data['isAuditMode'];
|
||
if (v is bool) return v;
|
||
}
|
||
return false;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ==================== 报告状态更新 ====================
|
||
|
||
/// 更新入门测试报告显示状态(看完后置回 false)
|
||
/// → POST /api/v1/User/ChangeShowReport
|
||
///
|
||
/// 对齐 Vue api/user/index.ts 的 UpdateReportStatus(),
|
||
/// 在 reportComputation finishedPopup 点"立即开启学习蝶变"后调用。
|
||
static Future<void> changeShowReport() async {
|
||
await DioClient.instance.post('/api/v1/User/ChangeShowReport');
|
||
}
|
||
|
||
/// 更新出门测试报告显示状态(看完后置回 false)
|
||
/// → POST /api/v1/User/ChangeShowAfterReport
|
||
///
|
||
/// 对齐 H5 壳子 api/mock/index.ts 的 updateAfterReportStatus(),
|
||
/// 在出门测 H5 壳子 navigateReport() 里由 Flutter JS Bridge 拦截后调用。
|
||
static Future<void> changeShowAfterReport() async {
|
||
await DioClient.instance.post('/api/v1/User/ChangeShowAfterReport');
|
||
}
|
||
|
||
/// MD5 加密(UniApp stringToMd5 的等价实现)
|
||
static String _md5(String text) {
|
||
return md5.convert(utf8.encode(text)).toString();
|
||
}
|
||
}
|