From d520fc55bcc5c1ab563ecba8db1e5fc6c042229b Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 14 Apr 2026 00:02:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E4=BF=AE=E5=A4=8D=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=E5=80=99=E9=80=89=E8=AF=8D=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E5=90=8E=E6=9C=AA=E5=A1=AB=E5=85=A5=E8=BE=93=E5=85=A5=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 点击候选词时 pointer-down 先触发 TextField 失焦,focus listener 立即移除 Overlay,导致 InkWell.onTap (pointer-up) 执行时 widget 已不存在。改为失焦后延迟 150ms 关闭下拉框,给 onTap 足够时间填值。 Co-Authored-By: Claude Sonnet 4.6 --- client/lib/screens/auth/login_screen.dart | 102 ++++++++++++++++------ 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/client/lib/screens/auth/login_screen.dart b/client/lib/screens/auth/login_screen.dart index 2c9ca48..9b28522 100644 --- a/client/lib/screens/auth/login_screen.dart +++ b/client/lib/screens/auth/login_screen.dart @@ -45,7 +45,8 @@ class _LoginScreenState extends ConsumerState { _openDropdown(_hotelLayerLink, _hotelCodeHistory, _shopCodeCtrl, isHotel: true); } else if (!_hotelCodeFocus.hasFocus) { - _closeHotel(); + // 延迟关闭:让候选词的 onTap(pointer-up)先执行,再关闭下拉框 + Future.delayed(const Duration(milliseconds: 150), _closeHotel); } }); _usernameFocus.addListener(() { @@ -53,7 +54,7 @@ class _LoginScreenState extends ConsumerState { _openDropdown(_usernameLayerLink, _usernameHistory, _usernameCtrl, isHotel: false); } else if (!_usernameFocus.hasFocus) { - _closeUsername(); + Future.delayed(const Duration(milliseconds: 150), _closeUsername); } }); } @@ -117,29 +118,10 @@ class _LoginScreenState extends ConsumerState { physics: const NeverScrollableScrollPhysics(), itemCount: items.length, separatorBuilder: (_, __) => const Divider(height: 1), - itemBuilder: (_, i) => GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (_) { - ctrl.text = items[i]; - ctrl.selection = TextSelection.collapsed( - offset: items[i].length); - if (isHotel) { _closeHotel(); } else { _closeUsername(); } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 10), - child: Row( - children: [ - const Icon(Icons.history, - size: 14, color: AppTheme.textSecondary), - const SizedBox(width: 8), - Text(items[i], - style: const TextStyle( - fontSize: 14, - color: AppTheme.textPrimary)), - ], - ), - ), + itemBuilder: (_, i) => _HistoryMenuItem( + value: items[i], + onSelected: () => + _selectHistoryItem(items[i], ctrl, isHotel: isHotel), ), ), ), @@ -158,6 +140,27 @@ class _LoginScreenState extends ConsumerState { setState(() {}); // refresh arrow icon } + void _selectHistoryItem( + String value, + TextEditingController ctrl, { + required bool isHotel, + }) { + ctrl.value = TextEditingValue( + text: value, + selection: TextSelection.collapsed(offset: value.length), + ); + + if (isHotel) { + _closeHotel(); + _hotelCodeFocus.unfocus(); + } else { + _closeUsername(); + _usernameFocus.unfocus(); + } + + setState(() {}); + } + void _closeHotel() { _hotelEntry?.remove(); _hotelEntry = null; @@ -472,3 +475,52 @@ class _BackgroundPainter extends CustomPainter { @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } + +class _HistoryMenuItem extends StatefulWidget { + final String value; + final VoidCallback onSelected; + + const _HistoryMenuItem({ + required this.value, + required this.onSelected, + }); + + @override + State<_HistoryMenuItem> createState() => _HistoryMenuItemState(); +} + +class _HistoryMenuItemState extends State<_HistoryMenuItem> { + bool _hovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _hovered = true), + onExit: (_) => setState(() => _hovered = false), + cursor: SystemMouseCursors.click, + child: Material( + color: _hovered ? const Color(0xFFF0F4FF) : Colors.white, + child: InkWell( + onTap: widget.onSelected, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + const Icon(Icons.history, + size: 14, color: AppTheme.textSecondary), + const SizedBox(width: 8), + Text( + widget.value, + style: const TextStyle( + fontSize: 14, + color: AppTheme.textPrimary, + ), + ), + ], + ), + ), + ), + ), + ); + } +}