feat(client/auth): 登录/注册页 Enter 键导航与提交

登录:邮箱框回车 → 跳到密码框;密码框回车 → 直接登录(校验条件同主按钮)。
注册:邮箱框回车 → 发送验证码并聚焦验证码框;验证码满 6 位回车 → 进入设密码步
并聚焦密码框;密码框(≥6 位)回车 → 直接注册。

经 TextField.onSubmitted + FocusNode.requestFocus 实现;桌面端回车即触发。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-28 22:44:34 +08:00
parent 7dcbe04934
commit c550eea5d8
+42 -5
View File
@@ -128,6 +128,33 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
}
}
// ── Enter 键导航 / 提交 ───────────────────────────────────────────
// 登录:邮箱回车 → 跳密码;密码回车 → 直接登录(校验条件同主按钮 enabled)。
void _submitLogin() {
if (!_loading && _emailValid && _pw.text.isNotEmpty) _doLogin();
}
// 注册:邮箱回车 → 发码并跳验证码;验证码(6位)回车 → 进设密码步并聚焦密码;
// 密码(≥6位)回车 → 直接注册。
void _onRegisterEmailSubmit() {
if (_loading || !_emailValid) return;
if (!_sent) _sendCode();
_codeFocus.requestFocus();
}
void _onRegisterCodeSubmit() {
if (!_sent || _code.text.length != 6) return;
setState(() => _step = 1);
// step 1 的密码框在本帧之后才挂载,延到下一帧再聚焦。
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _pwFocus.requestFocus();
});
}
void _submitRegister() {
if (!_loading && _pw.text.length >= 6) _doRegister();
}
void _setMode(_AuthMode m) => setState(() {
_mode = m;
_step = 0;
@@ -335,12 +362,14 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
kBareInput.copyWith(contentPadding: const EdgeInsets.symmetric(vertical: 13));
// 密码输入 + 显示/隐藏切换(眼睛按钮)。
Widget _pwInput(PangolinScheme c, String hint) => Row(children: [
Widget _pwInput(PangolinScheme c, String hint, {VoidCallback? onSubmit}) => Row(children: [
Expanded(
child: TextField(
controller: _pw,
focusNode: _pwFocus,
obscureText: !_pwVisible,
textInputAction: TextInputAction.done,
onSubmitted: onSubmit == null ? null : (_) => onSubmit(),
decoration: _bare.copyWith(hintText: hint),
onChanged: (_) => setState(() {}),
),
@@ -370,9 +399,12 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
Widget _login(PangolinScheme c, AppText t) {
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
child: TextField(controller: _email, focusNode: _emailFocus, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
child: TextField(controller: _email, focusNode: _emailFocus,
textInputAction: TextInputAction.next,
onSubmitted: (_) => _pwFocus.requestFocus(),
decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
const SizedBox(height: 16),
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.pwPh)),
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.pwPh, onSubmit: _submitLogin)),
Align(
alignment: Alignment.centerRight,
child: Padding(
@@ -391,7 +423,10 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
child: Row(children: [
Expanded(child: TextField(controller: _email, focusNode: _emailFocus, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
Expanded(child: TextField(controller: _email, focusNode: _emailFocus,
textInputAction: TextInputAction.next,
onSubmitted: (_) => _onRegisterEmailSubmit(),
decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
GestureDetector(
onTap: (!_loading && _emailValid) ? _sendCode : null,
child: Text(_sent ? t.resend : t.sendCode,
@@ -410,6 +445,8 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
const SizedBox(height: 16),
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel, focused: _codeFocus.hasFocus,
child: TextField(controller: _code, focusNode: _codeFocus, keyboardType: TextInputType.number, maxLength: 6,
textInputAction: TextInputAction.next,
onSubmitted: (_) => _onRegisterCodeSubmit(),
style: PangolinText.mono.copyWith(letterSpacing: 6, color: c.fg1),
decoration: _bare.copyWith(counterText: '', hintText: ''), onChanged: (_) => setState(() {}))),
const SizedBox(height: 16),
@@ -434,7 +471,7 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
]),
),
const SizedBox(height: 16),
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.setPwPh)),
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.setPwPh, onSubmit: _submitRegister)),
const SizedBox(height: 18),
_primaryBtn(c, label: _loading ? '...' : t.doCreate,
onPressed: (!_loading && _pw.text.length >= 6) ? _doRegister : null),