Files
App.K12.Student.Miniapp_Flu…/lib/core/api/auth/api.dart
T
崔宝新 955d6455d8 feat(auth): 实现注册模块与登录页优化
1. 新增用户注册接口与页面,对齐Vue端业务逻辑
2. 新增居中Toast组件,替换原有SnackBar轻提示
3. 完善登录历史本地存储与下拉选择交互
4. 新增审核模式接口适配,动态控制注册入口显示
5. 优化登录页UI布局与交互细节
2026-09-08 17:12:26 +08:00

145 lines
5.5 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;
}
}
/// MD5 加密(UniApp stringToMd5 的等价实现)
static String _md5(String text) {
return md5.convert(utf8.encode(text)).toString();
}
}