import 'package:chicken_farm/components/vb_select.dart'; import 'package:chicken_farm/core/utils/logger.dart'; import 'package:flutter/material.dart'; /// 搜索API函数签名 /// T 为数据类型 typedef SearchApiFunction = Future> Function({ required Map queryParams, }); /// 选项转换器,将数据类型T转换为SelectOption typedef SelectOptionConverter = SelectOption Function(T data); /// 搜索选择组件 class VberSearchSelect extends StatefulWidget { /// 搜索API函数 final SearchApiFunction searchApi; /// 数据转换方法,将原始数据转换为SelectOption final SelectOptionConverter converter; /// 根据value获取对应label的函数 final Future Function(String?)? getValueLabel; /// 当前选中的值 final String? value; /// 值改变回调 final ValueChanged? onChanged; /// 提示文字 final String? hint; /// 是否启用 final bool enabled; /// 每页数据条数 final int pageSize; /// 额外参数,用于传递给搜索API final Map? extraParams; const VberSearchSelect({ super.key, required this.searchApi, required this.converter, this.getValueLabel, this.value, this.onChanged, this.hint, this.enabled = true, this.pageSize = 3, this.extraParams, }); @override State> createState() => _VberSearchSelectState(); } class _VberSearchSelectState extends State> with SingleTickerProviderStateMixin { final TextEditingController _textController = TextEditingController(); final TextEditingController _searchController = TextEditingController(); final FocusNode _focusNode = FocusNode(); final FocusNode _searchFocusNode = FocusNode(); // 添加搜索框的FocusNode final LayerLink _layerLink = LayerLink(); OverlayEntry? _overlayEntry; List _items = []; bool _isLoading = false; bool _isOverlayVisible = false; String? _selectedValue; SelectOption? _selectedOption; // 新增:用来存储选中的选项 int _currentPage = 1; bool _hasMore = true; String _lastSearchText = ''; @override void initState() { super.initState(); _selectedValue = widget.value; _initTextController(); _focusNode.addListener(_onFocusChange); } /// 初始化文本控制器 void _initTextController() async { if (widget.value != null && widget.getValueLabel != null) { try { final label = await widget.getValueLabel!(widget.value); if (mounted) { setState(() { _textController.text = label ?? widget.value ?? ''; }); } } catch (e) { logger.e('Failed to get label for value: ${widget.value}', e); } } } @override void didUpdateWidget(covariant VberSearchSelect oldWidget) { super.didUpdateWidget(oldWidget); // 当外部传入的value发生变化时,同步更新内部状态 if (oldWidget.value != widget.value) { _syncValueAndText(widget.value); } } /// 同步外部value和内部_selectedValue以及_textController.text void _syncValueAndText(String? newValue) async { setState(() { _selectedValue = newValue; }); // 优先使用存储的选项 String? label; if (_selectedOption != null && _selectedOption!.value == newValue) { label = _selectedOption!.label; } // 尝试在现有选项中查找匹配的标签 if (label == null && newValue != null) { for (var item in _items) { final option = widget.converter(item); if (option.value == newValue) { label = option.label; break; } } } // 如果在现有选项中没找到,且提供了getValueLabel函数,则尝试通过它获取 if (label == null && newValue != null && widget.getValueLabel != null) { try { label = await widget.getValueLabel!(newValue); } catch (e) { logger.e('Failed to get label for value: $newValue', e); } } // 更新文本显示控件 if (mounted) { setState(() { // 优先级: 找到的label > value本身 > 空字符串 _textController.text = label ?? newValue ?? ''; }); } } @override void dispose() { _textController.dispose(); _searchController.dispose(); _focusNode.removeListener(_onFocusChange); _focusNode.dispose(); _searchFocusNode.dispose(); // 释放搜索框的FocusNode _overlayEntry?.remove(); super.dispose(); } void _onFocusChange() { // 只有当_focusNode获得焦点且组件启用时才显示弹出层 if (_focusNode.hasFocus && widget.enabled) { // 添加一个微小的延迟,避免焦点竞争 Future.microtask(() { if (_focusNode.hasFocus && mounted) { _showOverlay(); } }); } } void _showOverlay() { if (_isOverlayVisible) return; _overlayEntry = _createOverlayEntry(); Overlay.of(context).insert(_overlayEntry!); _isOverlayVisible = true; // 清空搜索框 _searchController.clear(); _items.clear(); _currentPage = 1; _hasMore = true; _lastSearchText = ''; _performSearch('', (callback) => setState(() => callback())); WidgetsBinding.instance.addPostFrameCallback((_) { if (_searchFocusNode.canRequestFocus && mounted) { _searchFocusNode.requestFocus(); } }); } void _hideOverlay() { _overlayEntry?.remove(); _overlayEntry = null; _isOverlayVisible = false; _searchFocusNode.unfocus(); _lastSearchText = ''; _items.clear(); } OverlayEntry _createOverlayEntry() { // 获取选择框的大小和位置 final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; final offset = renderBox.localToGlobal(Offset.zero); return OverlayEntry( builder: (context) => Positioned( left: offset.dx, top: offset.dy + size.height + 5, width: size.width, child: Material( elevation: 4, borderRadius: BorderRadius.circular(4), child: Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.4, ), child: StatefulBuilder( builder: (context, setState) { return Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16.0), child: TextField( key: ValueKey('search_text_field_${widget.hint ?? ''}_${UniqueKey().toString()}'), controller: _searchController, focusNode: _searchFocusNode, autofocus: false, decoration: InputDecoration( hintText: '请输入${widget.hint}搜索关键词', contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), ), isDense: true, ), onChanged: (value) { Future.delayed(const Duration(milliseconds: 300), () { if (_lastSearchText != _searchController.text) { _lastSearchText = _searchController.text; _items.clear(); _currentPage = 1; _hasMore = true; _performSearch(_searchController.text, setState); } else {} }); }, ), ), if (_isLoading && _items.isEmpty) Padding( padding: const EdgeInsets.all(16.0), child: Center(child: CircularProgressIndicator()), ) else if (!_isLoading && _items.isEmpty) Padding( padding: const EdgeInsets.all(16.0), child: Center( child: Text( '没有查询到结果', style: TextStyle(color: Colors.grey, fontSize: 14), ), ), ) else Flexible( child: NotificationListener( onNotification: (notification) { if (notification.metrics.pixels == notification.metrics.maxScrollExtent && _hasMore && !_isLoading) { _loadMore(setState); return true; } return false; }, child: Builder( builder: (BuildContext context) { return ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: _items.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == _items.length) { // 显示加载更多指示器 return Padding( padding: const EdgeInsets.all(12.0), child: Center( child: _isLoading ? CircularProgressIndicator() : Text('上拉加载更多'), ), ); } final item = _items[index]; final option = widget.converter(item); return ListTile( title: Text(option.label), onTap: () => _selectItem(option, item), dense: true, contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: 4, ), visualDensity: VisualDensity.compact, ); }, ); }, ), ), ), ], ); }, ), ), ), ), ); } void _loadMore(StateSetter overlaySetState) async { if (!_hasMore || _isLoading) return; overlaySetState(() { _isLoading = true; }); try { _currentPage++; final result = await _fetchData(_currentPage); final List rows = result['rows'] is List ? result['rows'] : []; final int total = result['total'] is int ? result['total'] : 0; final newItems = rows.cast().toList(); if (mounted) { overlaySetState(() { _items.addAll(newItems); _hasMore = _items.length < total; _isLoading = false; }); } } catch (e) { logger.e('加载更多失败:$e'); if (mounted) { overlaySetState(() { _currentPage--; // 回退页码 _isLoading = false; }); } } } void _performSearch(String keyword, StateSetter overlaySetState) async { overlaySetState(() { _isLoading = true; }); try { _currentPage = 1; _hasMore = true; final result = await _fetchData(_currentPage, keyword: keyword); final List rows = result['rows'] is List ? result['rows'] : []; final int total = result['total'] is int ? result['total'] : 0; final newItems = rows.cast().toList(); if (mounted) { overlaySetState(() { _items = newItems; _hasMore = _items.length < total; _isLoading = false; }); } } catch (e) { logger.e('搜索失败:$e'); if (mounted) { overlaySetState(() { _items = []; _hasMore = false; _isLoading = false; }); } } } Future> _fetchData( int pageNum, { String? keyword, }) async { final Map queryParams = { 'pageSize': widget.pageSize, 'pageNum': pageNum, }; if (keyword != null) { queryParams['keyword'] = keyword; } else if (keyword == null && _searchController.text.isNotEmpty) { // 如果keyword参数为null但_searchController中有内容,则使用该内容 queryParams['keyword'] = _searchController.text; } if (widget.extraParams != null) { queryParams.addAll(widget.extraParams!); } final r = await widget.searchApi(queryParams: queryParams); return r; } void _selectItem(SelectOption option, T item) { setState(() { _textController.text = option.label; _selectedValue = option.value; _selectedOption = option; // 保存选中的选项 // 检查当前选中的项是否已经在列表中,如果不在则添加到列表开头 // 这样可以在后续通过value同步label时找到对应的项 bool itemExists = _items.any((element) { final elementOption = widget.converter(element); return elementOption.value == option.value; }); if (!itemExists) { // 将选中项插入到列表开头,方便快速查找 _items.insert(0, item); } }); widget.onChanged?.call(option.value); _hideOverlay(); // 不需要在下一帧失去焦点,立即失去焦点可能会导致一些问题 // 使用post frame callback确保overlay完全隐藏后再失去焦点 WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _focusNode.unfocus(); } }); } void _clearSelection() { setState(() { _textController.clear(); _selectedValue = null; _selectedOption = null; // 清除保存的选项 }); widget.onChanged?.call(null); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () { // 点击时请求焦点 if (widget.enabled) { _focusNode.requestFocus(); } }, child: CompositedTransformTarget( link: _layerLink, child: TextField( controller: _textController, focusNode: _focusNode, readOnly: true, enabled: widget.enabled, decoration: InputDecoration( hintText: '请选择${widget.hint ?? ""}...', suffixIcon: _selectedValue != null ? IconButton( icon: Icon(Icons.clear, size: 20), onPressed: _clearSelection, ) : Icon(Icons.arrow_drop_down), ), ), ), ); } }