Files
App.K12.Student.Miniapp_Flu…/lib/core/api/auth/api.dart
T
崔宝新 5e4cfe9ac6 feat: 新增诊断报告全流程功能,适配登录后报告跳转逻辑
实现了入门测/出门测报告的完整链路:
1. 新增UserInfo模型的出门测报告标记字段与相关接口
2. 新增全局报告路由拦截器,自动在登录后/应用回前台时检查报告状态
3. 新增报告相关API、页面与路由配置,包含欢迎页、H5壳子、AI计算页
4. 适配登录成功后的报告跳转逻辑,更新用户状态管理方法
2026-09-09 16:49:51 +08:00

165 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}