新增文件 (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 通过
154 lines
4.3 KiB
Dart
154 lines
4.3 KiB
Dart
// 用户数据模型 — 匹配后端 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,
|
||
};
|
||
}
|