import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_core.dart'; import 'package:loopin/IM/im_message.dart'; import 'package:loopin/IM/im_service.dart' hide logger; import 'package:loopin/service/http.dart'; import 'package:loopin/api/common_api.dart'; import 'package:loopin/utils/index.dart'; import 'package:loopin/components/my_toast.dart'; import '../../behavior/custom_scroll_behavior.dart'; class SearchResultPage extends StatefulWidget { const SearchResultPage({super.key}); @override State createState() => _SearchResultPageState(); } class _SearchResultPageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; String _searchQuery = Get.arguments?['searchWords'] ?? ''; int _initialTabIndex = 0; final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); // 分页参数 static const int _pageSize = 20; int _videoCurrentPage = 1; int _productCurrentPage = 1; int _userCurrentPage = 1; bool _videoHasMore = true; bool _productHasMore = true; bool _userHasMore = true; // 三个tab的数据 List _videoResults = []; List _productResults = []; List _userResults = []; bool _isLoading = true; bool _isLoadingMore = false; // 统一的高度常量 static const double _itemCornerRadius = 8; @override void initState() { super.initState(); // 初始化搜索控制器 _searchController.text = _searchQuery; // 解析tab参数 final tabParam = Get.arguments?['tab']; if (tabParam != null) { _initialTabIndex = tabParam ?? 0; } _tabController = TabController( length: 3, vsync: this, initialIndex: _initialTabIndex, ); // 监听Tab切换 _tabController.addListener(_onTabChanged); // 加载初始数据 _loadInitialData(); } @override void dispose() { _tabController.removeListener(_onTabChanged); _tabController.dispose(); _searchController.dispose(); _searchFocusNode.dispose(); super.dispose(); } void _onTabChanged() { if (_tabController.indexIsChanging) { // 加载对应Tab的数据 _loadTabData(_tabController.index, isRefresh: true); } } // 执行搜索 void _performSearch() { final newQuery = _searchController.text.trim(); if (newQuery.isEmpty) { MyToast().tip( title: '请输入搜索关键词', position: 'center', type: 'warning', ); return; } if (newQuery == _searchQuery) { return; // 搜索关键词相同,不需要重新搜索 } setState(() { _searchQuery = newQuery; _isLoading = true; // 重置分页状态 _resetPagination(); }); // 关闭键盘 _searchFocusNode.unfocus(); // 重新加载数据 _loadInitialData(); } // 重置分页状态 void _resetPagination() { _videoCurrentPage = 1; _productCurrentPage = 1; _userCurrentPage = 1; _videoHasMore = true; _productHasMore = true; _userHasMore = true; _videoResults.clear(); _productResults.clear(); _userResults.clear(); } // 加载初始数据 Future _loadInitialData() async { try { setState(() { _isLoading = true; }); // 加载当前Tab的数据 await _loadTabData(_tabController.index, isRefresh: true); } catch (e) { print('加载数据失败: $e'); MyToast().tip( title: '加载失败', position: 'center', type: 'error', ); } finally { setState(() { _isLoading = false; }); } } // 加载指定Tab的数据 Future _loadTabData(int tabIndex, {bool isRefresh = false}) async { try { if (isRefresh) { setState(() { _isLoading = true; }); } else { setState(() { _isLoadingMore = true; }); } int currentPage; List currentResults; bool hasMore; switch (tabIndex) { case 0: // 视频 currentPage = isRefresh ? 1 : _videoCurrentPage + 1; currentResults = _videoResults; hasMore = _videoHasMore; break; case 1: // 商品 currentPage = isRefresh ? 1 : _productCurrentPage + 1; currentResults = _productResults; hasMore = _productHasMore; break; case 2: // 用户 currentPage = isRefresh ? 1 : _userCurrentPage + 1; currentResults = _userResults; hasMore = _userHasMore; break; default: return; } // 如果没有更多数据,直接返回 if (!hasMore && !isRefresh) { setState(() { _isLoadingMore = false; }); return; } final data = { 'title': _searchQuery, 'size': _pageSize, 'type': tabIndex+1, // 1-视频;2-商品;3-用户 'current': currentPage, }; final res = await Http.post(CommonApi.aggregationSearchApi, data: data); if (res['code'] == 200) { final newData = res['data']['records'] ?? []; final total = res['data']['total'] ?? 0; print('搜索数据结果$newData'); print('搜索数据参数$data'); setState(() { switch (tabIndex) { case 0: if (isRefresh) { _videoResults = newData; _videoCurrentPage = 1; } else { _videoResults.addAll(newData); _videoCurrentPage = currentPage; } _videoHasMore = _videoResults.length < total; break; case 1: if (isRefresh) { _productResults = newData; _productCurrentPage = 1; } else { _productResults.addAll(newData); _productCurrentPage = currentPage; } _productHasMore = _productResults.length < total; break; case 2: if (isRefresh) { _userResults = newData; _userCurrentPage = 1; } else { _userResults.addAll(newData); _userCurrentPage = currentPage; } _userHasMore = _userResults.length < total; break; } }); } } catch (e) { print('加载Tab数据失败: $e'); if (!isRefresh) { MyToast().tip( title: '加载更多失败', position: 'center', type: 'error', ); } } finally { setState(() { if (isRefresh) { _isLoading = false; } else { _isLoadingMore = false; } }); } } // 加载更多数据 void _loadMoreData(int tabIndex) { if (_isLoadingMore) return; _loadTabData(tabIndex, isRefresh: false); } // 点击关注按钮 onFocusBtnClick (user,index) async { final vlogerId = user['id']; final doIFollowVloger = user['doIFollowVloger']; print('是否关注此用户------------->${doIFollowVloger}'); if (doIFollowVloger == null || doIFollowVloger == false) { final res = await ImService.instance.followUser(userIDList: [vlogerId]); print('关注结果------------->${res.success}'); if (res.success) { setState(() { _userResults[index]['doIFollowVloger'] = true; }); } }else{ final res = await ImService.instance.unfollowUser(userIDList: [vlogerId]); print('取消关注结果------------->${res.success}'); if (res.success) { setState(() { _userResults[index]['doIFollowVloger'] = false; }); } } } // 构建加载更多组件 Widget _buildLoadMoreWidget(int tabIndex) { if (_isLoadingMore) { return const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距 child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ), ); } bool hasMore; switch (tabIndex) { case 0: hasMore = _videoHasMore; break; case 1: hasMore = _productHasMore; break; case 2: hasMore = _userHasMore; break; default: hasMore = false; } if (!hasMore && _getCurrentResults(tabIndex).isNotEmpty) { return const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距 child: Text( '没有更多数据了', style: TextStyle( fontSize: 12, // 减小字体 color: Colors.grey, ), ), ), ); } return Container(); } // 获取当前Tab的数据 List _getCurrentResults(int tabIndex) { switch (tabIndex) { case 0: return _videoResults; case 1: return _productResults; case 2: return _userResults; default: return []; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Container( height: 36, decoration: BoxDecoration( color: Colors.grey[800], borderRadius: BorderRadius.circular(18), ), child: Row( children: [ const SizedBox(width: 12), const Icon(Icons.search, color: Colors.grey, size: 20), const SizedBox(width: 8), Expanded( child: TextField( controller: _searchController, focusNode: _searchFocusNode, style: const TextStyle( color: Colors.white, fontSize: 14, height: 1.2, ), decoration: const InputDecoration( hintText: '搜索视频、商品、用户、团购...', hintStyle: TextStyle( color: Colors.grey, fontSize: 14, height: 1.2, ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 8), isDense: true, ), onSubmitted: (_) => _performSearch(), ), ), IconButton( icon: const Icon(Icons.close, color: Colors.grey, size: 18), onPressed: () { _searchController.clear(); }, padding: const EdgeInsets.all(4), constraints: const BoxConstraints(), ), const SizedBox(width: 4), ], ), ), backgroundColor: Colors.black, foregroundColor: Colors.white, elevation: 0.5, actions: [ TextButton( onPressed: _performSearch, child: const Text( '搜索', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 8), ], ), body: Column( children: [ Container( color: Colors.white, padding: const EdgeInsets.only(top: 8), child: TabBar( controller: _tabController, indicatorColor: Colors.pink, labelColor: Colors.pink, unselectedLabelColor: Colors.grey, labelStyle: const TextStyle(fontWeight: FontWeight.bold), unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.normal), padding: const EdgeInsets.only(left: 16), indicatorPadding: EdgeInsets.zero, labelPadding: const EdgeInsets.symmetric(horizontal: 16), indicatorSize: TabBarIndicatorSize.label, dividerColor: Colors.transparent, tabs: const [ Tab(text: '视频'), Tab(text: '商品'), Tab(text: '用户'), ], ), ), // Tab内容区域 Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : TabBarView( controller: _tabController, children: [ // 视频Tab _buildVideoTab(), // 商品Tab _buildProductTab(), // 用户Tab _buildUserTab(), ], ), ), ], ), ); } // 视频Tab Widget _buildVideoTab() { if (_videoResults.isEmpty && !_isLoading) { return _buildEmptyView('暂无视频结果'); } return NotificationListener( onNotification: (ScrollNotification scrollInfo) { if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _videoHasMore) { _loadMoreData(0); } return false; }, child: Column( children: [ Expanded( child: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 0.65, ), itemCount: _videoResults.length, itemBuilder: (context, index) { final video = _videoResults[index] as Map; return _buildVideoItem(video); }, ), ), ), _buildVideoLoadMoreWidget(), ], ), ); } // 视频项构建 Widget _buildVideoItem(Map video) { return GestureDetector( onTap: () { // 视频点击事件处理 print('点击了视频: ${video['id']}'); Get.toNamed('/videoDetail', arguments: {'videoId': video['id']}); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(_itemCornerRadius), color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 视频封面 - 宽度100%,高度自适应 ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(_itemCornerRadius), topRight: Radius.circular(_itemCornerRadius), ), child: Container( width: double.infinity, color: Colors.grey[200], child: video['cover'] != null && video['cover'].toString().isNotEmpty ? Image.network( video['cover'].toString(), fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 120, alignment: Alignment.center, child: const Icon( Icons.videocam, color: Colors.grey, size: 40, // 设置一个合理的默认大小 ), ); }, ) : Container( height: 200, alignment: Alignment.center, child: LayoutBuilder( builder: (context, constraints) { return Icon( Icons.videocam, color: Colors.grey, ); }, ), ), ), ), // 视频信息 - 固定在容器底部 Expanded( child: Padding( padding: const EdgeInsets.all(6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐 children: [ Text( video['title']?.toString() ?? '无标题', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w500, ), maxLines: 1, // 改为 1,限制为单行 overflow: TextOverflow.ellipsis, // 超出部分显示省略号 ), const SizedBox(height: 4), Row( children: [ Container( width: 18, height: 18, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.grey[300], image: video['avatar'] != null && video['avatar'].toString().isNotEmpty ? DecorationImage( image: NetworkImage(video['avatar'].toString()), fit: BoxFit.cover, ) : null, ), child: video['avatar'] == null || video['avatar'].toString().isEmpty ? const Icon(Icons.person, size: 10, color: Colors.grey) : null, ), const SizedBox(width: 4), Expanded( child: Text( video['nickname']?.toString() ?? '未知作者', style: const TextStyle( fontSize: 9, color: Colors.grey, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 6), Row( children: [ const Icon(Icons.favorite_border, size: 10, color: Colors.grey), const SizedBox(width: 2), Text( Utils().formatLikeCount(video['likeCounts'] ?? 0), style: const TextStyle( fontSize: 9, color: Colors.grey, ), ), ], ), ], ), ], ), ), ), ], ), ), ); } // 视频加载更多组件 Widget _buildVideoLoadMoreWidget() { if (_isLoadingMore) { return const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Center( child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ), ); } if (!_videoHasMore && _videoResults.isNotEmpty) { return const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Center( child: Text( '没有更多数据了', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ), ); } return Container(); } // 商品Tab Widget _buildProductTab() { if (_productResults.isEmpty && !_isLoading) { return _buildEmptyView('暂无商品结果'); } return NotificationListener( onNotification: (ScrollNotification scrollInfo) { if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _productHasMore) { _loadMoreData(1); } return false; }, child: Column( children: [ Expanded( child: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 0.55, ), itemCount: _productResults.length, itemBuilder: (context, index) { final product = _productResults[index] as Map; return _buildProductItem(product); }, ), ), ), _buildProductLoadMoreWidget(), ], ), ); } // 商品加载更多组件 Widget _buildProductLoadMoreWidget() { if (_isLoadingMore) { return const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Center( child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ), ); } if (!_productHasMore && _productResults.isNotEmpty) { return const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: Center( child: Text( '没有更多数据了', style: TextStyle( fontSize: 12, color: Colors.grey, ), ), ), ); } return Container(); } Widget _buildProductItem(Map product) { return GestureDetector( onTap: () { // 商品点击事件处理 print('点击了商品: ${product['id']}'); Get.toNamed('/goods', arguments: {'goodsId': product['id']}); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(_itemCornerRadius), color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 商品图片 - 宽度100%,高度自适应 ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(_itemCornerRadius), topRight: Radius.circular(_itemCornerRadius), ), child: Container( width: double.infinity, color: Colors.grey[200], child: product['pic'] != null ? Image.network( product['pic'].toString(), fit: BoxFit.cover, ) : Container( height: 110, child: const Center( child: Icon(Icons.shopping_bag, color: Colors.grey, size: 32), ), ), ), ), // 商品信息 - 固定在容器底部 Expanded( child: Padding( padding: const EdgeInsets.all(6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, // 内容靠底部对齐 children: [ Text( product['name']?.toString() ?? '未知商品', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w500, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '¥${product['price']?.toString() ?? '0.00'}', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.pink, ), ), Text( '已售 ${product['sales']?.toString() ?? '0'}', style: const TextStyle( fontSize: 9, color: Colors.grey, ), ), ], ), ], ), ), ), ], ), ), ); } // 用户Tab Widget _buildUserTab() { if (_userResults.isEmpty && !_isLoading) { return _buildEmptyView('暂无用户结果'); } return NotificationListener( onNotification: (ScrollNotification scrollInfo) { if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !_isLoadingMore && _userHasMore) { _loadMoreData(2); } return false; }, child: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: ListView.builder( padding: const EdgeInsets.all(8), itemCount: _userResults.length + 1, itemBuilder: (context, index) { if (index == _userResults.length) { return _buildLoadMoreWidget(2); } final user = _userResults[index] as Map; return _buildUserItem(user,index); }, ), ), ); } Widget _buildUserItem(Map user, int index) { // 判断当前用户是否已被关注 bool isFollowing = user['doIFollowVloger'] ?? false; print('111111111111111111111111${isFollowing}'); return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Container( width: 45, height: 45, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.grey[200], image: user['avatar'] != null ? DecorationImage( image: NetworkImage(user['avatar'].toString()), fit: BoxFit.cover, ) : null, ), child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user['nickname']?.toString() ?? '未知用户', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( '粉丝: ${user['fansCount']?.toString() ?? '0'}', style: const TextStyle( fontSize: 11, color: Colors.grey, ), ), ], ), ), ElevatedButton( onPressed: () async { await onFocusBtnClick(user, index); }, style: ElevatedButton.styleFrom( backgroundColor: isFollowing ? Colors.grey[300] : Colors.pink, foregroundColor: isFollowing ? Colors.grey[600] : Colors.white, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), // 添加边框样式 side: isFollowing ? BorderSide(color: Colors.grey[400]!, width: 0.5) : BorderSide.none, ), child: Text( isFollowing ? '已关注' : '关注', style: const TextStyle(fontSize: 11), ), ), ], ), ); } // 空视图 Widget _buildEmptyView(String message) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.search_off, size: 50, color: Colors.grey), const SizedBox(height: 12), Text( message, style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), ); } }