Some checks failed
CI / Lint (push) Has been cancelled
## 问题 旧版 Flutter 项目在 /vcode/check 返回 30164 时展示二级密码输入界面; 新版完全缺失此路径,有二级密码的账号无法登录。 ## 改动 ### networks_sdk - `networks_sdk_method_channel_datasource.dart`:executeRequest 的 generic catch 改为 rethrow,允许 decodeResponse override 抛出 自定义业务异常(原为 ApiError.unknown 包裹导致数据丢失) ### 数据层 - `errors.dart`:新增 `secondaryPasscodeRequired = 30164` - `exceptions.dart`(新增):`SecondaryPasscodeRequiredException` 携带 vcodeToken / recoveryEmail / hint / resetStatus - `verify_otp_request.dart`:override decodeResponse,拦截 30164, 从响应 data 提取字段,throw SecondaryPasscodeRequiredException - `login_request.dart`:新增可选 password 字段 + toJson override (条件序列化,null 时不带 password 字段) - `auth_repository.dart`:新增 loginWithPasscode() 接口 - `auth_repository_impl.dart`:实现 loginWithPasscode() ### 业务层 - `login_usecase.dart`:新增 loginWithSecondaryPasscode() (MD5 哈希 passcode → 调 AuthRepository.loginWithPasscode) - `pubspec.yaml`:新增 crypto: ^3.0.6(用于 MD5) ### UI 层 - `login_state.dart`:新增 LoginStep.secondaryPasscode + vcodeToken / passcodeHint / recoveryEmail 字段 - `login_view_model.dart`:verifyAndLogin 捕获 SecondaryPasscodeRequiredException 跳转步骤 3;新增 verifyPasscode() - `login_secondary_passcode_step.dart`(新增):密码输入 UI(hint 显示、 obscured 输入框、错误提示、忘记密码占位) - `login_page.dart`:switch 路由接入 LoginStep.secondaryPasscode
84 lines
2.3 KiB
Dart
84 lines
2.3 KiB
Dart
/// 登录流程的当前步骤
|
||
enum LoginStep {
|
||
/// 步骤 1:输入手机号
|
||
phone,
|
||
|
||
/// 步骤 2:输入验证码
|
||
otp,
|
||
|
||
/// 步骤 3(可选):输入二级密码(账号已设置时触发)
|
||
secondaryPasscode,
|
||
}
|
||
|
||
/// 登录页面状态(手动 copyWith)
|
||
///
|
||
/// ViewModel 通过 `state = state.copyWith(...)` 更新状态,
|
||
/// View 通过 `ref.watch(loginViewModelProvider)` 自动响应变化。
|
||
class LoginState {
|
||
const LoginState({
|
||
this.step = LoginStep.phone,
|
||
this.countryCode = '+65',
|
||
this.contact = '',
|
||
this.isLoading = false,
|
||
this.error,
|
||
this.vcodeToken = '',
|
||
this.passcodeHint = '',
|
||
this.recoveryEmail = '',
|
||
});
|
||
|
||
/// 当前步骤(手机号输入 / 验证码输入 / 二级密码输入)
|
||
final LoginStep step;
|
||
|
||
/// 国家代码(默认 +65,暂不支持切换)
|
||
final String countryCode;
|
||
|
||
/// 已提交的手机号(步骤 2 / 3 用于显示和构建请求)
|
||
final String contact;
|
||
|
||
/// 是否正在请求中
|
||
final bool isLoading;
|
||
|
||
/// 错误信息(null = 无错误)
|
||
final String? error;
|
||
|
||
/// vcode_token(OTP 验证通过后服务端下发,二级密码步骤需携带)
|
||
final String vcodeToken;
|
||
|
||
/// 用户设置的二级密码提示语(步骤 3 显示)
|
||
final String passcodeHint;
|
||
|
||
/// 脱敏找回邮箱(步骤 3 "忘记密码" 提示用)
|
||
final String recoveryEmail;
|
||
|
||
LoginState copyWith({
|
||
LoginStep? step,
|
||
String? countryCode,
|
||
String? contact,
|
||
bool? isLoading,
|
||
String? error,
|
||
bool clearError = false,
|
||
String? vcodeToken,
|
||
String? passcodeHint,
|
||
String? recoveryEmail,
|
||
}) {
|
||
return LoginState(
|
||
step: step ?? this.step,
|
||
countryCode: countryCode ?? this.countryCode,
|
||
contact: contact ?? this.contact,
|
||
isLoading: isLoading ?? this.isLoading,
|
||
error: clearError ? null : (error ?? this.error),
|
||
vcodeToken: vcodeToken ?? this.vcodeToken,
|
||
passcodeHint: passcodeHint ?? this.passcodeHint,
|
||
recoveryEmail: recoveryEmail ?? this.recoveryEmail,
|
||
);
|
||
}
|
||
|
||
/// 步骤 2 显示的脱敏手机号,如 "138****0000"
|
||
String get maskedContact {
|
||
if (contact.length <= 4) return contact;
|
||
final tail = contact.substring(contact.length - 4);
|
||
final stars = '*' * (contact.length - 4);
|
||
return '$stars$tail';
|
||
}
|
||
}
|