Files
nj/app/lib/data/models/user.dart
iven 0fe3bc705c 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 通过
2026-06-01 01:22:53 +08:00

154 lines
4.3 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.
// 用户数据模型 — 匹配后端 UserResp / RoleResp
//
// 暖记用户包含四种角色:老师、学生、家长、独立用户。
// 角色决定用户可访问的功能模块和页面。
/// 用户角色枚举 — 对应后端 role code
enum UserRoleType {
teacher('teacher'),
student('student'),
parent('parent'),
independent('independent');
const UserRoleType(this.code);
final String code;
/// 从后端角色代码解析,未知代码默认为独立用户
static UserRoleType fromCode(String code) => UserRoleType.values.firstWhere(
(r) => r.code == code,
orElse: () => UserRoleType.independent,
);
}
/// 角色信息 — 匹配后端 RoleResp
class UserRole {
final String id;
final String name;
final String code;
final String? description;
final bool isSystem;
final int version;
const UserRole({
required this.id,
required this.name,
required this.code,
this.description,
this.isSystem = false,
this.version = 1,
});
/// 获取标准化的角色类型
UserRoleType get type => UserRoleType.fromCode(code);
factory UserRole.fromJson(Map<String, dynamic> json) => UserRole(
id: json['id'] as String,
name: json['name'] as String,
code: json['code'] as String,
description: json['description'] as String?,
isSystem: (json['is_system'] as bool?) ?? false,
version: (json['version'] as int?) ?? 1,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'code': code,
'description': description,
'is_system': isSystem,
'version': version,
};
}
/// 用户信息 — 匹配后端 UserResp
///
/// 包含用户基本信息和角色列表。
/// 角色由后端 RBAC 系统管理,前端据此控制页面可见性和功能访问。
class User {
final String id;
final String username;
final String? email;
final String? phone;
final String? displayName;
final String? avatarUrl;
final String status;
final List<UserRole> roles;
final int version;
const User({
required this.id,
required this.username,
this.email,
this.phone,
this.displayName,
this.avatarUrl,
this.status = 'active',
this.roles = const [],
this.version = 1,
});
User copyWith({
String? displayName,
String? avatarUrl,
List<UserRole>? roles,
int? version,
}) =>
User(
id: id,
username: username,
email: email,
phone: phone,
displayName: displayName ?? this.displayName,
avatarUrl: avatarUrl ?? this.avatarUrl,
status: status,
roles: roles ?? this.roles,
version: version ?? this.version,
);
/// 获取用户的主角色类型(第一个角色的类型)
UserRoleType get primaryRoleType =>
roles.isNotEmpty ? roles.first.type : UserRoleType.independent;
/// 用户是否为老师
bool get isTeacher => roles.any((r) => r.type == UserRoleType.teacher);
/// 用户是否为学生
bool get isStudent => roles.any((r) => r.type == UserRoleType.student);
/// 用户是否为家长
bool get isParent => roles.any((r) => r.type == UserRoleType.parent);
/// 用户是否已完成角色选择
bool get hasRole => roles.isNotEmpty;
/// 显示名称:优先使用 displayName回退到 username
String get displayLabel => displayName ?? username;
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as String,
username: json['username'] as String,
email: json['email'] as String?,
phone: json['phone'] as String?,
displayName: json['display_name'] as String?,
avatarUrl: json['avatar_url'] as String?,
status: (json['status'] as String?) ?? 'active',
roles: (json['roles'] as List?)
?.map((r) => UserRole.fromJson(r as Map<String, dynamic>))
.toList() ??
[],
version: (json['version'] as int?) ?? 1,
);
Map<String, dynamic> toJson() => {
'id': id,
'username': username,
'email': email,
'phone': phone,
'display_name': displayName,
'avatar_url': avatarUrl,
'status': status,
'roles': roles.map((r) => r.toJson()).toList(),
'version': version,
};
}