diff --git a/apps/im_app/lib/features/login/presentation/login_view_model.dart b/apps/im_app/lib/features/login/presentation/login_view_model.dart index 89f1adb..7552100 100644 --- a/apps/im_app/lib/features/login/presentation/login_view_model.dart +++ b/apps/im_app/lib/features/login/presentation/login_view_model.dart @@ -59,6 +59,10 @@ class LoginViewModel extends _$LoginViewModel { /// 仅用于演示路由守卫行为,正式 UI 就绪后删除此方法,改用 [login]。 /// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。 Future demoLogin() async { + // 防止连点重入:第一次调用未完成前忽略后续调用 + if (state.isLoading) return; + state = state.copyWith(isLoading: true, error: null); + try { final storageApi = ref.read(storageSdkProvider); final storageLifeCycle = storageApi as StorageSdkLifecycle; @@ -72,11 +76,15 @@ class LoginViewModel extends _$LoginViewModel { // 先完成 DB 操作,再标记登录状态(失败时不会误标为已登录) await storageLifeCycle.openDatabase(user.uid); final userCompanion = UserDto.fromEntity(user).toCompanion(); - await storageApi.insert(userCompanion); + await storageApi.insertOrReplace(userCompanion); // 全部成功后再更新登录状态,触发路由守卫重定向 + // 注意:login() 触发导航后 provider 随即被 dispose,之后不能再写 state + if (!ref.mounted) return; ref.read(authNotifierProvider).login(); } catch (e) { + // 导航已发生时 provider 已被 dispose,静默丢弃,不再写 state + if (!ref.mounted) return; state = state.copyWith(error: e.toString(), isLoading: false); } } @@ -94,15 +102,19 @@ class LoginViewModel extends _$LoginViewModel { .read(loginUseCaseProvider) .execute(email: email, password: password); + if (!ref.mounted) return; state = state.copyWith(user: user, isLoading: false); } on FormatException catch (e) { // 格式校验失败(UseCase 层抛出) + if (!ref.mounted) return; state = state.copyWith(error: e.message, isLoading: false); } on ApiError catch (e) { // 网络 / 服务端错误(Repository → SDK 透传) + if (!ref.mounted) return; state = state.copyWith(error: e.displayMessage, isLoading: false); } catch (e) { // 兜底:防止未预期的异常导致 isLoading 死锁 + if (!ref.mounted) return; state = state.copyWith(error: e.toString(), isLoading: false); } } diff --git a/apps/im_app/lib/features/login/view/login_page.dart b/apps/im_app/lib/features/login/view/login_page.dart index dcfe9ab..c907581 100644 --- a/apps/im_app/lib/features/login/view/login_page.dart +++ b/apps/im_app/lib/features/login/view/login_page.dart @@ -15,13 +15,12 @@ class LoginPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + // ref.watch 保持 loginViewModelProvider 存活(AutoDispose 需要至少一个监听者) + final state = ref.watch(loginViewModelProvider); final s = context.styles; return Scaffold( - appBar: AppBar( - title: const Text('登录'), - automaticallyImplyLeading: false, - ), + appBar: AppBar(title: const Text('登录'), automaticallyImplyLeading: false), body: Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -34,7 +33,9 @@ class LoginPage extends ConsumerWidget { ), const SizedBox(height: 32), FilledButton( - onPressed: () => ref.read(loginViewModelProvider.notifier).demoLogin(), + onPressed: state.isLoading + ? null + : () => ref.read(loginViewModelProvider.notifier).demoLogin(), child: const Text('登录'), ), ],