feat(app): 实现认证模块 — F2 Auth BLoC + 登录/注册/角色选择/班级码加入

新增文件 (10):
- data/models/user.dart — 用户+角色模型 (匹配后端 UserResp/RoleResp)
- data/models/auth_token.dart — 认证令牌模型 (匹配后端 LoginResp)
- data/repositories/auth_repository.dart — 认证仓库 (JWT 安全持久化 + PIPL 合规)
- features/auth/bloc/auth_bloc.dart — 认证 BLoC (8 种事件, 6 种状态)
- features/auth/bloc/auth_event.dart — 认证事件 (sealed class 穷尽匹配)
- features/auth/bloc/auth_state.dart — 认证状态 (Authenticated 含角色/班级码流程)
- features/auth/views/login_page.dart — 登录/注册页面 (重写占位页面)
- features/auth/views/role_selection_page.dart — 角色选择页 (4 种角色卡片)
- features/auth/views/class_code_join_page.dart — 班级码加入页 (6 位输入)

修改文件 (5):
- pubspec.yaml — 添加 flutter_secure_storage 依赖
- app.dart — 注入 AuthBloc + RepositoryProvider
- main.dart — 简化入口 (认证恢复在 BLoC 中处理)
- core/routing/app_router.dart — 添加认证路由守卫 + 2 新路由
- erp-diary/service/class_service.rs — 移除未使用的 PaginatorTrait import

验证: flutter analyze (0 error) + cargo check 通过
This commit is contained in:
iven
2026-06-01 01:22:53 +08:00
parent 232a53dbed
commit 0fe3bc705c
15 changed files with 1805 additions and 113 deletions

View File

@@ -0,0 +1,54 @@
// 认证令牌模型 — 匹配后端 LoginResp
//
// 管理访问令牌和刷新令牌,支持自动计算过期时间。
// 令牌通过 flutter_secure_storage 安全持久化PIPL 合规要求)。
/// 认证令牌 — 包含访问令牌、刷新令牌和过期信息
class AuthToken {
final String accessToken;
final String refreshToken;
final int expiresIn;
final DateTime expiresAt;
const AuthToken({
required this.accessToken,
required this.refreshToken,
required this.expiresIn,
required this.expiresAt,
});
/// 令牌是否已过期
bool get isExpired => DateTime.now().isAfter(expiresAt);
/// 令牌是否即将过期5 分钟内)
bool get isExpiringSoon =>
DateTime.now().isAfter(expiresAt.subtract(const Duration(minutes: 5)));
/// 从后端 LoginResp JSON 创建
factory AuthToken.fromJson(Map<String, dynamic> json) {
final expiresIn = (json['expires_in'] as int?) ?? 3600;
return AuthToken(
accessToken: json['access_token'] as String,
refreshToken: json['refresh_token'] as String,
expiresIn: expiresIn,
expiresAt: DateTime.now().add(Duration(seconds: expiresIn)),
);
}
Map<String, dynamic> toJson() => {
'access_token': accessToken,
'refresh_token': refreshToken,
'expires_in': expiresIn,
'expires_at': expiresAt.toIso8601String(),
};
/// 从持久化存储恢复(使用保存的过期时间)
factory AuthToken.fromStorage(Map<String, dynamic> json) => AuthToken(
accessToken: json['access_token'] as String,
refreshToken: json['refresh_token'] as String,
expiresIn: (json['expires_in'] as int?) ?? 3600,
expiresAt: json['expires_at'] != null
? DateTime.parse(json['expires_at'] as String)
: DateTime.now().add(const Duration(hours: 1)),
);
}