/// 登录 / 注册页的表单状态与提交逻辑。 /// /// 校验规则与后端一致(邮箱格式、密码 8-64 位且含字母数字),本地先拦一次是为了少一次网络往返、 /// 也让用户立刻看到哪个字段不合格;后端仍会再校验一遍(前端校验不作为安全边界)。 class AuthLogic { bool signUpMode; String email = ''; String password = ''; bool busy = false; String? errorMessage; AuthFieldError? fieldError; AuthLogic({this.signUpMode = false, this.errorMessage}); void toggleMode() { signUpMode = !signUpMode; errorMessage = null; fieldError = null; } /// 返回 null 表示校验通过,否则返回错误文案(由视图用 l10n 映射) AuthFieldError? validate() { final mail = email.trim(); if (mail.isEmpty) return _record(AuthFieldError.emailRequired); if (!_emailPattern.hasMatch(mail)) { return _record(AuthFieldError.emailInvalid); } if (password.isEmpty) return _record(AuthFieldError.passwordRequired); if (password.length < 8 || password.length > 64) { return _record(AuthFieldError.passwordLength); } if (!_hasLetter.hasMatch(password) || !_hasDigit.hasMatch(password)) { return _record(AuthFieldError.passwordWeak); } fieldError = null; return null; } /// 清除当前字段错误;输入变化时由页面调用。 void clearFieldError() { fieldError = null; } AuthFieldError _record(AuthFieldError error) { fieldError = error; return error; } String get normalizedEmail => email.trim().toLowerCase(); static final _emailPattern = RegExp( r'^[A-Za-z0-9._%+\-]{1,64}@[A-Za-z0-9]([A-Za-z0-9\-]{0,61}[A-Za-z0-9])?' r'(\.[A-Za-z0-9]([A-Za-z0-9\-]{0,61}[A-Za-z0-9])?)*\.[A-Za-z]{2,}$', ); static final _hasLetter = RegExp('[A-Za-z]'); static final _hasDigit = RegExp('[0-9]'); } enum AuthFieldError { emailRequired, emailInvalid, passwordRequired, passwordLength, passwordWeak, }