fix(client): 修复登录页候选词点击后未填入输入框

点击候选词时 pointer-down 先触发 TextField 失焦,focus listener
立即移除 Overlay,导致 InkWell.onTap (pointer-up) 执行时 widget
已不存在。改为失焦后延迟 150ms 关闭下拉框,给 onTap 足够时间填值。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-14 00:02:50 +08:00
parent 40a620c270
commit d520fc55bc
+77 -25
View File
@@ -45,7 +45,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
_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<LoginScreen> {
_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<LoginScreen> {
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<LoginScreen> {
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,
),
),
],
),
),
),
),
);
}
}