# 注册验证码流程 UX 优化实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 优化注册流程的界面响应速度和重发验证码按钮的交互体验 **Architecture:** 乐观跳转策略 + Timer 倒计时状态管理,后台异步发送验证码 **Tech Stack:** Flutter, dart:async Timer, flutter_bloc --- ## Task 1: 扩展 RegisterCubit 状态 **Files:** - Modify: `apps/lib/features/auth/presentation/cubits/register_cubit.dart` - Modify: `apps/test/features/auth/presentation/cubits/register_cubit_test.dart` **Step 1: 添加 isSending 状态字段** 在 `RegisterState` 中添加 `isSending` 字段: ```dart // RegisterState final bool isSending; const RegisterState({ // ... existing fields this.isSending = false, }); // copyWith bool? isSending, // copyWith return isSending: isSending ?? this.isSending, // props isSending, ``` **Step 2: 添加 sendCodeSilently 方法** 在 `RegisterCubit` 中添加不阻塞的发送方法: ```dart Future sendCodeSilently() async { if (!state.isStep1Valid) return; emit(state.copyWith(isSending: true)); try { final response = await _repository.signupStart( SignupStartRequest( username: state.username.value, email: state.email.value, password: state.password.value, ), ); emit( state.copyWith( isSending: false, pendingEmail: response.email, codeSent: true, errorMessage: null, ), ); } catch (e) { final message = e is ApiException ? e.message : '验证码发送失败,请重试'; emit( state.copyWith( isSending: false, errorMessage: message, ), ); } } ``` **Step 3: 添加测试** ```dart // 在 register_cubit_test.dart 添加 group('sendCodeSilently', () { blocTest( 'sets isSending to true then false on success', build: () => cubit, seed: () => RegisterState( username: const Username.dirty('testuser'), email: const Email.dirty('test@example.com'), password: const Password.dirty('password123'), ), setUp: () { when(() => mockRepository.signupStart(any())) .thenAnswer((_) async => SignupStartResponse(email: 'test@example.com')); }, act: (c) => c.sendCodeSilently(), verify: (_) { verify(() => mockRepository.signupStart(any())).called(1); }, ); }); ``` **Step 4: 运行测试** Run: `cd apps && flutter test test/features/auth/presentation/cubits/register_cubit_test.dart` Expected: PASS **Step 5: Commit** ```bash git add apps/lib/features/auth/presentation/cubits/register_cubit.dart apps/test/features/auth/presentation/cubits/register_cubit_test.dart git commit -m "feat(auth): add sendCodeSilently with isSending state" ``` --- ## Task 2: 实现乐观跳转 **Files:** - Modify: `apps/lib/features/auth/ui/screens/register_screen.dart` **Step 1: 修改 _handleNext 方法** 将同步等待改为乐观跳转: ```dart Future _handleNext() async { final cubit = context.read(); cubit.usernameChanged(_nicknameController.text); cubit.emailChanged(_emailController.text); cubit.passwordChanged(_passwordController.text); if (!cubit.state.isStep1Valid) { return; } // 乐观跳转:立即跳转到验证码界面 if (mounted) { context.push('/register/verification', extra: cubit); } // 后台发送验证码 cubit.sendCodeSilently(); } ``` **Step 2: Commit** ```bash git add apps/lib/features/auth/ui/screens/register_screen.dart git commit -m "feat(auth): optimistic navigation to verification screen" ``` --- ## Task 3: 实现倒计时状态管理 **Files:** - Modify: `apps/lib/features/auth/ui/screens/register_verification_screen.dart` **Step 1: 添加 Timer 状态** 在 `_RegisterVerificationViewState` 中添加: ```dart import 'dart:async'; // 状态变量 Timer? _countdownTimer; int _countdown = 60; @override void initState() { super.initState(); _startCountdown(); } @override void dispose() { _countdownTimer?.cancel(); _codeController.dispose(); super.dispose(); } void _startCountdown() { setState(() { _countdown = 60; }); _countdownTimer?.cancel(); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_countdown > 0) { setState(() { _countdown--; }); } else { timer.cancel(); } }); } ``` **Step 2: Commit** ```bash git add apps/lib/features/auth/ui/screens/register_verification_screen.dart git commit -m "feat(auth): add countdown timer for resend button" ``` --- ## Task 4: 优化重发按钮样式 **Files:** - Modify: `apps/lib/features/auth/ui/screens/register_verification_screen.dart` **Step 1: 重构 _buildCodeInput 方法** 替换重发按钮部分: ```dart Widget _buildCodeInput(RegisterState state) { final canResend = _countdown == 0 && state.status != FormzSubmissionStatus.inProgress; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '邮箱验证码', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF475569), ), ), const SizedBox(height: 6), Row( children: [ Expanded( child: SizedBox( height: 40, child: TextField( controller: _codeController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: '输入验证码', contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), ), ), ), ), const SizedBox(width: 8), _buildResendButton(canResend), ], ), ], ); } Widget _buildResendButton(bool canResend) { final borderColor = canResend ? AppColors.primary : AppColors.slate300; final textColor = canResend ? AppColors.primary : AppColors.slate400; final text = canResend ? '重新发送' : '$_countdown s'; return SizedBox( width: 90, height: 40, child: OutlinedButton( onPressed: canResend ? _handleResendCode : null, style: OutlinedButton.styleFrom( backgroundColor: AppColors.background, side: BorderSide(color: borderColor), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( text, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: textColor, ), ), ), ); } ``` **Step 2: Commit** ```bash git add apps/lib/features/auth/ui/screens/register_verification_screen.dart git commit -m "feat(auth): improve resend button style with countdown" ``` --- ## Task 5: 添加 Toast 错误提示 **Files:** - Modify: `apps/lib/features/auth/ui/screens/register_verification_screen.dart` **Step 1: 添加 BlocListener 监听错误** 用 `BlocConsumer` 替换 `BlocBuilder`,添加监听: ```dart import '../../../../shared/widgets/toast/toast.dart'; Widget _buildFormContainer() { return BlocConsumer( listener: (context, state) { // 监听后台发送验证码的错误 if (state.errorMessage != null && !state.isStep2Valid) { Toast.show(context, state.errorMessage!, type: ToastType.error); } }, builder: (context, state) { return SizedBox( width: 327, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildCodeInput(state), const SizedBox(height: 12), _buildStepIndicator(), const SizedBox(height: 12), AppButton( text: '完成注册', onPressed: state.status == FormzSubmissionStatus.inProgress ? null : _handleComplete, ), ], ), ); }, ); } ``` **Step 2: 修改 _handleResendCode 重置倒计时** ```dart Future _handleResendCode() async { final cubit = context.read(); await cubit.resendCode(); // 重发成功后重置倒计时 if (cubit.state.codeSent && mounted) { _startCountdown(); Toast.show(context, '验证码已发送', type: ToastType.success); } } ``` **Step 3: Commit** ```bash git add apps/lib/features/auth/ui/screens/register_verification_screen.dart git commit -m "feat(auth): add toast feedback for code sending" ``` --- ## Task 6: 集成测试与验收 **Step 1: 运行全部测试** Run: `cd apps && flutter test` Expected: All tests PASS **Step 2: 手动验收清单** - [ ] 点击"下一步"后立即跳转到验证码界面 - [ ] 验证码界面显示倒计时按钮 "60 s" ... "1 s" - [ ] 倒计时期间按钮禁用 - [ ] 倒计时结束后显示"重新发送",可点击 - [ ] 点击重发后重新开始 60 秒倒计时 - [ ] 发送失败时 Toast 提示错误 **Step 3: Final commit** ```bash git add -A git commit -m "feat(auth): complete register verification UX optimization" ``` --- ## 文件变更汇总 | 文件 | 变更 | |------|------| | `register_cubit.dart` | 添加 `isSending` 状态、`sendCodeSilently` 方法 | | `register_screen.dart` | 乐观跳转,后台发送 | | `register_verification_screen.dart` | Timer 倒计时、按钮样式、Toast 提示 | | `register_cubit_test.dart` | 新增 `sendCodeSilently` 测试 |