flutter/lib/pages/search/search-result.dart

976 lines
30 KiB
Dart
Raw Normal View History

2025-08-27 18:14:45 +08:00
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';
2025-08-27 18:14:45 +08:00
import 'package:loopin/components/my_toast.dart';
2025-09-03 11:25:31 +08:00
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
2025-08-27 18:14:45 +08:00
import '../../behavior/custom_scroll_behavior.dart';
class SearchResultPage extends StatefulWidget {
const SearchResultPage({super.key});
@override
State<SearchResultPage> createState() => _SearchResultPageState();
}
2025-08-28 16:58:47 +08:00
class _SearchResultPageState extends State<SearchResultPage> with SingleTickerProviderStateMixin {
2025-08-27 18:14:45 +08:00
late TabController _tabController;
2025-08-28 16:58:47 +08:00
String _searchQuery = Get.arguments?['searchWords'] ?? '';
2025-08-27 18:14:45 +08:00
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;
2025-08-27 18:14:45 +08:00
// 三个tab的数据
List<dynamic> _videoResults = [];
List<dynamic> _productResults = [];
List<dynamic> _userResults = [];
bool _isLoading = true;
bool _isLoadingMore = false;
// 统一的高度常量
static const double _itemCornerRadius = 8;
2025-08-27 18:14:45 +08:00
@override
void initState() {
super.initState();
// 初始化搜索控制器
_searchController.text = _searchQuery;
// 解析tab参数
2025-08-28 16:58:47 +08:00
final tabParam = Get.arguments?['tab'];
if (tabParam != null) {
_initialTabIndex = tabParam ?? 0;
2025-08-27 18:14:45 +08:00
}
_tabController = TabController(
2025-08-28 16:58:47 +08:00
length: 3,
2025-08-27 18:14:45 +08:00
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);
2025-08-27 18:14:45 +08:00
}
}
// 执行搜索
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();
2025-08-27 18:14:45 +08:00
});
// 关闭键盘
_searchFocusNode.unfocus();
// 重新加载数据
_loadInitialData();
}
// 重置分页状态
void _resetPagination() {
_videoCurrentPage = 1;
_productCurrentPage = 1;
_userCurrentPage = 1;
_videoHasMore = true;
_productHasMore = true;
_userHasMore = true;
_videoResults.clear();
_productResults.clear();
_userResults.clear();
}
2025-08-27 18:14:45 +08:00
// 加载初始数据
Future<void> _loadInitialData() async {
try {
setState(() {
_isLoading = true;
});
// 加载当前Tab的数据
await _loadTabData(_tabController.index, isRefresh: true);
2025-08-27 18:14:45 +08:00
} 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 {
2025-08-27 18:14:45 +08:00
try {
if (isRefresh) {
setState(() {
_isLoading = true;
});
} else {
setState(() {
_isLoadingMore = true;
});
}
int currentPage;
List<dynamic> currentResults;
bool hasMore;
2025-08-27 18:14:45 +08:00
switch (tabIndex) {
case 0: // 视频
currentPage = isRefresh ? 1 : _videoCurrentPage + 1;
currentResults = _videoResults;
hasMore = _videoHasMore;
2025-08-27 18:14:45 +08:00
break;
case 1: // 商品
currentPage = isRefresh ? 1 : _productCurrentPage + 1;
currentResults = _productResults;
hasMore = _productHasMore;
2025-08-27 18:14:45 +08:00
break;
case 2: // 用户
currentPage = isRefresh ? 1 : _userCurrentPage + 1;
currentResults = _userResults;
hasMore = _userHasMore;
2025-08-27 18:14:45 +08:00
break;
default:
return;
}
// 如果没有更多数据,直接返回
if (!hasMore && !isRefresh) {
setState(() {
_isLoadingMore = false;
});
return;
}
final data = {
'title': _searchQuery,
'size': _pageSize,
2025-09-03 11:25:31 +08:00
'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;
}
});
2025-08-27 18:14:45 +08:00
}
} catch (e) {
print('加载Tab数据失败: $e');
if (!isRefresh) {
MyToast().tip(
title: '加载更多失败',
position: 'center',
type: 'error',
);
}
2025-08-27 18:14:45 +08:00
} finally {
setState(() {
if (isRefresh) {
_isLoading = false;
} else {
_isLoadingMore = false;
}
2025-08-27 18:14:45 +08:00
});
}
}
// 加载更多数据
void _loadMoreData(int tabIndex) {
if (_isLoadingMore) return;
_loadTabData(tabIndex, isRefresh: false);
}
// 点击关注按钮
2025-09-03 11:25:31 +08:00
onFocusBtnClick(user, index) async {
final vlogerId = user['id'];
final doIFollowVloger = user['doIFollowVloger'];
print('是否关注此用户------------->$doIFollowVloger');
print('此用户UserId------------->$vlogerId');
2025-09-03 11:47:50 +08:00
if (doIFollowVloger == false || doIFollowVloger == null) {
2025-09-03 11:25:31 +08:00
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
print('关注结果------------->${res.success}');
if (res.success) {
setState(() {
2025-09-03 11:47:50 +08:00
_userResults[index]['doIFollowVloger'] = true;
2025-09-03 11:25:31 +08:00
});
}
} else {
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
print('取消关注结果------------->${res.success}');
if (res.success) {
setState(() {
2025-09-03 11:47:50 +08:00
_userResults[index]['doIFollowVloger'] = false;
2025-09-03 11:25:31 +08:00
});
}
}
}
2025-09-03 11:25:31 +08:00
// 构建加载更多组件
Widget _buildLoadMoreWidget(int tabIndex) {
2025-09-03 11:25:31 +08:00
if (_isLoadingMore) {
return const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0), // 减少垂直间距
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
2025-09-03 11:25:31 +08:00
);
}
2025-09-03 11:25:31 +08:00
bool hasMore;
switch (tabIndex) {
case 0:
hasMore = _videoHasMore;
break;
case 1:
hasMore = _productHasMore;
break;
case 2:
hasMore = _userHasMore;
break;
default:
hasMore = false;
}
2025-09-03 11:25:31 +08:00
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,
),
),
),
2025-09-03 11:25:31 +08:00
);
}
2025-09-03 11:25:31 +08:00
return Container();
}
// 获取当前Tab的数据
List<dynamic> _getCurrentResults(int tabIndex) {
switch (tabIndex) {
case 0:
return _videoResults;
case 1:
return _productResults;
case 2:
return _userResults;
default:
return [];
}
}
2025-08-27 18:14:45 +08:00
@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: '用户'),
],
2025-08-27 18:14:45 +08:00
),
),
// Tab内容区域
2025-08-27 18:14:45 +08:00
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: TabBarView(
controller: _tabController,
children: [
// 视频Tab
_buildVideoTab(),
// 商品Tab
_buildProductTab(),
// 用户Tab
_buildUserTab(),
],
),
),
],
),
);
}
// 视频Tab
Widget _buildVideoTab() {
if (_videoResults.isEmpty && !_isLoading) {
2025-08-27 18:14:45 +08:00
return _buildEmptyView('暂无视频结果');
}
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
2025-09-03 11:25:31 +08:00
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(),
],
2025-08-27 18:14:45 +08:00
),
);
}
// 视频项构建
2025-08-27 18:14:45 +08:00
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),
2025-08-27 18:14:45 +08:00
),
],
),
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],
2025-09-17 16:50:23 +08:00
child: video['firstFrameImg'] != null && video['firstFrameImg'].toString().isNotEmpty
? Image.network(
2025-09-17 16:50:23 +08:00
video['firstFrameImg'].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, // 内容靠底部对齐
2025-08-27 18:14:45 +08:00
children: [
2025-09-03 11:25:31 +08:00
Text(
video['title']?.toString() ?? '无标题',
2025-08-27 18:14:45 +08:00
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
2025-08-27 18:14:45 +08:00
),
2025-09-03 11:25:31 +08:00
maxLines: 1, // 改为 1限制为单行
overflow: TextOverflow.ellipsis, // 超出部分显示省略号
2025-08-27 18:14:45 +08:00
),
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,
),
2025-09-03 11:25:31 +08:00
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(
2025-09-03 11:25:31 +08:00
Utils.graceNumber(video['likeCounts'] ?? 0),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
),
],
),
],
2025-08-27 18:14:45 +08:00
),
],
),
),
2025-08-27 18:14:45 +08:00
),
],
),
2025-08-27 18:14:45 +08:00
),
);
}
// 视频加载更多组件
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();
}
2025-08-27 18:14:45 +08:00
// 商品Tab
Widget _buildProductTab() {
if (_productResults.isEmpty && !_isLoading) {
2025-08-27 18:14:45 +08:00
return _buildEmptyView('暂无商品结果');
}
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
2025-09-03 11:25:31 +08:00
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(),
],
2025-08-27 18:14:45 +08:00
),
);
}
// 商品加载更多组件
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();
}
2025-09-03 11:25:31 +08:00
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),
),
2025-09-03 11:25:31 +08:00
],
),
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),
),
),
2025-09-03 11:25:31 +08:00
),
2025-08-27 18:14:45 +08:00
),
2025-09-03 11:25:31 +08:00
// 商品信息 - 固定在容器底部
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,
),
2025-09-03 11:25:31 +08:00
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,
),
),
2025-09-03 11:25:31 +08:00
Text(
'已售 ${product['sales']?.toString() ?? '0'}',
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
),
2025-09-03 11:25:31 +08:00
],
),
],
),
),
2025-08-27 18:14:45 +08:00
),
2025-09-03 11:25:31 +08:00
],
),
2025-08-27 18:14:45 +08:00
),
2025-09-03 11:25:31 +08:00
);
}
2025-08-27 18:14:45 +08:00
// 用户Tab
Widget _buildUserTab() {
if (_userResults.isEmpty && !_isLoading) {
2025-08-27 18:14:45 +08:00
return _buildEmptyView('暂无用户结果');
}
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
2025-09-03 11:25:31 +08:00
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>;
2025-09-03 11:25:31 +08:00
return _buildUserItem(user, index);
},
),
2025-08-27 18:14:45 +08:00
),
);
}
Widget _buildUserItem(Map<String, dynamic> user, int index) {
// 判断当前用户是否已被关注
bool isFollowing = user['doIFollowVloger'] ?? false;
2025-09-03 11:25:31 +08:00
print('111111111111111111111111$isFollowing');
2025-08-27 18:14:45 +08:00
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(10),
2025-08-27 18:14:45 +08:00
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,
2025-08-27 18:14:45 +08:00
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
image: user['avatar'] != null
? DecorationImage(
image: NetworkImage(user['avatar'].toString()),
fit: BoxFit.cover,
)
: null,
),
2025-09-03 11:25:31 +08:00
child: user['avatar'] == null ? const Icon(Icons.person, color: Colors.grey, size: 20) : null,
2025-08-27 18:14:45 +08:00
),
const SizedBox(width: 10),
2025-08-27 18:14:45 +08:00
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user['nickname']?.toString() ?? '未知用户',
2025-08-27 18:14:45 +08:00
style: const TextStyle(
fontSize: 13,
2025-08-27 18:14:45 +08:00
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
2025-08-27 18:14:45 +08:00
Text(
'粉丝: ${user['fansCount']?.toString() ?? '0'}',
2025-08-27 18:14:45 +08:00
style: const TextStyle(
fontSize: 11,
2025-08-27 18:14:45 +08:00
color: Colors.grey,
),
),
],
),
),
ElevatedButton(
onPressed: () async {
await onFocusBtnClick(user, index);
2025-08-27 18:14:45 +08:00
},
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),
2025-08-27 18:14:45 +08:00
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
2025-08-27 18:14:45 +08:00
),
// 添加边框样式
2025-09-03 11:25:31 +08:00
side: isFollowing ? BorderSide(color: Colors.grey[400]!, width: 0.5) : BorderSide.none,
2025-08-27 18:14:45 +08:00
),
child: Text(
isFollowing ? '已关注' : '关注',
style: const TextStyle(fontSize: 11),
2025-08-27 18:14:45 +08:00
),
),
],
),
2025-08-27 18:14:45 +08:00
);
}
// 空视图
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),
2025-08-27 18:14:45 +08:00
Text(
message,
style: const TextStyle(
fontSize: 14,
2025-08-27 18:14:45 +08:00
color: Colors.grey,
),
),
],
),
);
}
2025-09-03 11:25:31 +08:00
}