976 lines
30 KiB
Dart
976 lines
30 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:loopin/IM/im_service.dart' hide logger;
|
||
import 'package:loopin/api/common_api.dart';
|
||
import 'package:loopin/components/my_toast.dart';
|
||
import 'package:loopin/service/http.dart';
|
||
import 'package:loopin/utils/index.dart';
|
||
|
||
import '../../behavior/custom_scroll_behavior.dart';
|
||
|
||
class SearchResultPage extends StatefulWidget {
|
||
const SearchResultPage({super.key});
|
||
|
||
@override
|
||
State<SearchResultPage> createState() => _SearchResultPageState();
|
||
}
|
||
|
||
class _SearchResultPageState extends State<SearchResultPage> 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<dynamic> _videoResults = [];
|
||
List<dynamic> _productResults = [];
|
||
List<dynamic> _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<void> _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<void> _loadTabData(int tabIndex, {bool isRefresh = false}) async {
|
||
try {
|
||
if (isRefresh) {
|
||
setState(() {
|
||
_isLoading = true;
|
||
});
|
||
} else {
|
||
setState(() {
|
||
_isLoadingMore = true;
|
||
});
|
||
}
|
||
|
||
int currentPage;
|
||
List<dynamic> 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');
|
||
print('此用户UserId------------->$vlogerId');
|
||
if (doIFollowVloger == false) {
|
||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||
print('关注结果------------->${res.success}');
|
||
if (res.success) {
|
||
setState(() {
|
||
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
|
||
});
|
||
}
|
||
} else {
|
||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||
print('取消关注结果------------->${res.success}');
|
||
if (res.success) {
|
||
setState(() {
|
||
_userResults[index]['doIFollowVloger'] = !_userResults[index]['doIFollowVloger'];
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构建加载更多组件
|
||
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<dynamic> _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<ScrollNotification>(
|
||
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<String, dynamic>;
|
||
return _buildVideoItem(video);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
_buildVideoLoadMoreWidget(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// 视频项构建
|
||
Widget _buildVideoItem(Map<String, dynamic> 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.graceNumber(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<ScrollNotification>(
|
||
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<String, dynamic>;
|
||
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<String, dynamic> 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,
|
||
)
|
||
: SizedBox(
|
||
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<ScrollNotification>(
|
||
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<String, dynamic>;
|
||
return _buildUserItem(user, index);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildUserItem(Map<String, dynamic> 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,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|