feat(app): 班级码验证前后端联调 — AuthBloc接入API + 错误计数锁定UI

This commit is contained in:
iven
2026-06-01 22:42:33 +08:00
parent b3fc066aac
commit 55285b57a7
6 changed files with 261 additions and 70 deletions

View File

@@ -4,6 +4,7 @@
// ↕
// Authenticating → Authenticated/AuthError
import 'package:dio/dio.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:logger/logger.dart';
@@ -11,6 +12,7 @@ import '../../../data/models/auth_token.dart';
import '../../../data/models/user.dart';
import '../../../data/remote/api_client.dart';
import '../../../data/repositories/auth_repository.dart';
import '../../../data/repositories/class_repository.dart';
part 'auth_event.dart';
part 'auth_state.dart';
@@ -18,10 +20,14 @@ part 'auth_state.dart';
/// 认证 BLoC — 处理所有认证相关的状态转换
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository _authRepository;
final ClassRepository? _classRepository;
final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0));
AuthBloc({required AuthRepository authRepository})
: _authRepository = authRepository,
AuthBloc({
required AuthRepository authRepository,
ClassRepository? classRepository,
}) : _authRepository = authRepository,
_classRepository = classRepository,
super(const AuthInitial()) {
// 注册事件处理器
on<AppStarted>(_onAppStarted);
@@ -138,13 +144,64 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final currentState = state;
if (currentState is! Authenticated) return;
// TODO: 调用后端 API 验证班级码并加入班级
// 当前先标记为已完成,班级码验证在 F8 阶段完善
emit(currentState.copyWith(
needsClassCode: false,
));
// 如果没有 ClassRepository离线模式直接跳过
final classRepo = _classRepository;
if (classRepo == null) {
_logger.w('ClassRepository 不可用,跳过班级码验证');
emit(currentState.copyWith(needsClassCode: false));
return;
}
_logger.i('班级码加入: ${event.classCode}');
emit(currentState.copyWith(isLoading: true));
try {
// 调用后端 API 验证班级码并加入班级
await classRepo.joinClass(
event.classCode,
nickname: currentState.user.displayName,
);
// 成功 — 清除班级码需求
emit(currentState.copyWith(
needsClassCode: false,
isLoading: false,
));
_logger.i('班级码加入成功: ${event.classCode}');
} on DioException catch (e) {
final statusCode = e.response?.statusCode;
String errorMessage = '加入班级失败,请重试';
if (statusCode == 400) {
// 班级码无效或已过期
final body = e.response?.data;
if (body is Map && body['message'] is String) {
errorMessage = body['message'] as String;
} else {
errorMessage = '班级码无效,请检查后重新输入';
}
} else if (statusCode == 429) {
// 尝试次数过多 — 锁定
errorMessage = '尝试次数过多,请等待 30 分钟后再试';
}
_logger.w('班级码验证失败 ($statusCode): $errorMessage');
emit(currentState.copyWith(
isLoading: false,
classCodeError: errorMessage,
));
} on OfflineException {
emit(currentState.copyWith(
isLoading: false,
classCodeError: '网络不可用,请检查网络后重试',
));
} catch (e) {
_logger.e('班级码验证异常: $e');
emit(currentState.copyWith(
isLoading: false,
classCodeError: '加入班级失败,请稍后重试',
));
}
}
/// 用户登出

View File

@@ -40,21 +40,33 @@ final class Authenticated extends AuthState {
/// 是否需要班级码加入(学生/家长角色)
final bool needsClassCode;
/// 是否正在加载(班级码验证中)
final bool isLoading;
/// 班级码验证错误信息
final String? classCodeError;
const Authenticated({
required this.user,
this.needsRoleSelection = false,
this.needsClassCode = false,
this.isLoading = false,
this.classCodeError,
});
Authenticated copyWith({
User? user,
bool? needsRoleSelection,
bool? needsClassCode,
bool? isLoading,
String? classCodeError,
}) =>
Authenticated(
user: user ?? this.user,
needsRoleSelection: needsRoleSelection ?? this.needsRoleSelection,
needsClassCode: needsClassCode ?? this.needsClassCode,
isLoading: isLoading ?? this.isLoading,
classCodeError: classCodeError,
);
}