1、聚合搜索

2、公共视频详情页
This commit is contained in:
cuiyouliang 2025-08-30 16:49:21 +08:00
parent 0b99562766
commit ce0331e4c6
10 changed files with 2053 additions and 262 deletions

View File

@ -14,5 +14,8 @@ class CommonApi {
//
static const String dictionaryApi = '/app/sys/dict/type/';
//
static const String aggregationSearchApi = '/app/common/search';
///resource/oss/upload
}

View File

@ -8,9 +8,11 @@ class VideoApi {
// post
static const String myPublicList = '/app/vlog/myPublicList'; //
static const String myLikedList = '/app/vlog/myLikedList'; //
static const String videoCommentList = '/app/comment/list'; //
static const String videoCommentList = '/app/comment/page'; //
static const String doVideoComment = '/app/comment/publish'; //
static const String reportVideoApi = '/app/feedback/add'; //
static const String videoDetailApi = '/app/vlog/detail/'; // Id获取视频系详情

View File

@ -117,6 +117,7 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
});
final data = res['data']['records'];
print('商品返回数据------------------------->${data}');
tab.dataList.addAll(data);
// logger.w(res);

View File

@ -597,7 +597,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
),
onTap: () {
// ID
Get.toNamed('/goods');
// Get.toNamed('/goods');
Get.toNamed('/goods', arguments: {});
},
),
));

View File

@ -48,13 +48,13 @@ class _GoodsState extends State<Goods> {
@override
void initState() {
super.initState();
final shopId = Get.arguments;
final goodsId = Get.arguments['goodsId'];
scrollController.addListener(() {
setState(() {
scrollOffset = scrollController.offset;
});
});
shopDetail(shopId);
shopDetail(goodsId);
}
@override
@ -64,9 +64,9 @@ class _GoodsState extends State<Goods> {
}
///
void shopDetail(shopId) async {
void shopDetail(goodsId) async {
try {
final res = await Http.get('${ShopApi.shopDetail}/$shopId');
final res = await Http.get('${ShopApi.shopDetail}/$goodsId');
logger.e(res['data']);
setState(() {
shopObj = res['data']; // data

View File

@ -104,7 +104,8 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
),
),
onTap: () {
Get.toNamed('/goods', arguments: item['id']);
// Get.toNamed('/goods', arguments: item['id']);
Get.toNamed('/goods', arguments: {'goodsId': item['id']});
},
);
}

View File

@ -1,6 +1,12 @@
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';
@ -18,11 +24,24 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
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() {
@ -61,7 +80,7 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
void _onTabChanged() {
if (_tabController.indexIsChanging) {
// Tab的数据
_loadTabData(_tabController.index);
_loadTabData(_tabController.index, isRefresh: true);
}
}
@ -84,6 +103,8 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
setState(() {
_searchQuery = newQuery;
_isLoading = true;
//
_resetPagination();
});
//
@ -93,6 +114,19 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
_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 {
@ -100,8 +134,8 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
_isLoading = true;
});
// Tab的数据
await _loadTabData(_initialTabIndex);
// Tab的数据
await _loadTabData(_tabController.index, isRefresh: true);
} catch (e) {
print('加载数据失败: $e');
MyToast().tip(
@ -117,64 +151,210 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
}
// Tab的数据
Future<void> _loadTabData(int tabIndex) async {
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: //
final res = await Http.get('/api/search/videos', params: {
'keyword': _searchQuery,
'page': 1,
'current': 20,
});
if (res['code'] == 200) {
setState(() {
_videoResults = res['data']['list'] ?? [];
});
}
currentPage = isRefresh ? 1 : _videoCurrentPage + 1;
currentResults = _videoResults;
hasMore = _videoHasMore;
break;
case 1: //
final res = await Http.get('/api/search/products', params: {
'keyword': _searchQuery,
'page': 1,
'current': 20,
});
if (res['code'] == 200) {
setState(() {
_productResults = res['data']['list'] ?? [];
});
}
currentPage = isRefresh ? 1 : _productCurrentPage + 1;
currentResults = _productResults;
hasMore = _productHasMore;
break;
case 2: //
final res = await Http.get('/api/search/users', params: {
'keyword': _searchQuery,
'page': 1,
'current': 20,
});
if (res['code'] == 200) {
setState(() {
_userResults = res['data']['list'] ?? [];
});
}
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: '加载失败',
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(
@ -251,16 +431,15 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
padding: const EdgeInsets.only(top: 8),
child: TabBar(
controller: _tabController,
// isScrollable: true,
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), //
padding: const EdgeInsets.only(left: 16),
indicatorPadding: EdgeInsets.zero,
labelPadding: const EdgeInsets.symmetric(horizontal: 16), //
indicatorSize: TabBarIndicatorSize.label, // label宽度
labelPadding: const EdgeInsets.symmetric(horizontal: 16),
indicatorSize: TabBarIndicatorSize.label,
dividerColor: Colors.transparent,
tabs: const [
Tab(text: '视频'),
@ -292,85 +471,166 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
// Tab
Widget _buildVideoTab() {
if (_videoResults.isEmpty) {
if (_videoResults.isEmpty && !_isLoading) {
return _buildEmptyView('暂无视频结果');
}
return ScrollConfiguration(
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: ListView.builder(
padding: const EdgeInsets.all(12),
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 Container(
margin: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Container(
width: 120,
height: 80,
return GestureDetector(
onTap: () {
//
print('点击了视频: ${video['id']}');
Get.toNamed('/videoDetail', arguments: {'videoId': video['id']});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.grey[200],
image: video['cover'] != null
? DecorationImage(
image: NetworkImage(video['cover'].toString()),
fit: BoxFit.cover,
)
: null,
borderRadius: BorderRadius.circular(_itemCornerRadius),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
child: video['cover'] == null
? const Icon(Icons.videocam, color: Colors.grey)
: null,
],
),
const SizedBox(width: 12),
//
Expanded(
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: 14,
fontSize: 11,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
video['author']?.toString() ?? '未知作者',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
maxLines: 1, // 1
overflow: TextOverflow.ellipsis, //
),
const SizedBox(height: 4),
Row(
children: [
Text(
'#${video['tag']?.toString() ?? '无标签'}',
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: 12,
color: Colors.blue,
fontSize: 9,
color: Colors.grey,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
const SizedBox(width: 6),
Row(
children: [
const Icon(Icons.favorite_border, size: 10, color: Colors.grey),
const SizedBox(width: 2),
Text(
video['location']?.toString() ?? '',
Utils().formatLikeCount(video['likeCounts'] ?? 0),
style: const TextStyle(
fontSize: 12,
fontSize: 9,
color: Colors.grey,
),
),
@ -378,38 +638,135 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
),
],
),
],
),
),
),
],
),
),
);
}
//
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) {
if (_productResults.isEmpty && !_isLoading) {
return _buildEmptyView('暂无商品结果');
}
return ScrollConfiguration(
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: ListView.builder(
padding: const EdgeInsets.all(12),
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 Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
return GestureDetector(
onTap: () {
//
print('点击了商品: ${product['id']}');
Get.toNamed('/goods', arguments: {'goodsId': product['id']});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_itemCornerRadius),
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
@ -418,65 +775,64 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
),
],
),
child: Row(
children: [
//
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.grey[200],
image: product['image'] != null
? DecorationImage(
image: NetworkImage(product['image'].toString()),
fit: BoxFit.cover,
)
: null,
),
child: product['image'] == null
? const Icon(Icons.shopping_bag, color: Colors.grey)
: null,
),
const SizedBox(width: 12),
//
Expanded(
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: 14,
fontSize: 11,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 6),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'¥${product['price']?.toString() ?? '0.00'}',
style: const TextStyle(
fontSize: 16,
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.pink,
),
),
const SizedBox(height: 6),
Row(
children: [
Text(
'已售 ${product['sold']?.toString() ?? '0'}',
'已售 ${product['sales']?.toString() ?? '0'}',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(width: 12),
Text(
'${product['comments']?.toString() ?? '0'}条评论',
style: const TextStyle(
fontSize: 12,
fontSize: 9,
color: Colors.grey,
),
),
@ -485,41 +841,52 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
],
),
),
//
IconButton(
onPressed: () {
//
},
icon: const Icon(Icons.shopping_cart, size: 20),
),
],
),
),
);
}
}
// Tab
Widget _buildUserTab() {
if (_userResults.isEmpty) {
if (_userResults.isEmpty && !_isLoading) {
return _buildEmptyView('暂无用户结果');
}
return ScrollConfiguration(
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(12),
itemCount: _userResults.length,
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);
return _buildUserItem(user,index);
},
),
),
);
}
Widget _buildUserItem(Map<String, dynamic> user) {
Widget _buildUserItem(Map<String, dynamic> user, int index) {
//
bool isFollowing = user['doIFollowVloger'] ?? false;
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
@ -533,10 +900,9 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
),
child: Row(
children: [
//
Container(
width: 50,
height: 50,
width: 45,
height: 45,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
@ -548,53 +914,55 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
: null,
),
child: user['avatar'] == null
? const Icon(Icons.person, color: Colors.grey)
? const Icon(Icons.person, color: Colors.grey, size: 20)
: null,
),
const SizedBox(width: 12),
//
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user['name']?.toString() ?? '未知用户',
user['nickname']?.toString() ?? '未知用户',
style: const TextStyle(
fontSize: 14,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
const SizedBox(height: 2),
Text(
'粉丝: ${user['fans']?.toString() ?? '0'}',
'粉丝: ${user['fansCount']?.toString() ?? '0'}',
style: const TextStyle(
fontSize: 12,
fontSize: 11,
color: Colors.grey,
),
),
],
),
),
//
ElevatedButton(
onPressed: () {
//
onPressed: () async {
await onFocusBtnClick(user, index);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.pink,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
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(20),
borderRadius: BorderRadius.circular(16),
),
//
side: isFollowing
? BorderSide(color: Colors.grey[400]!, width: 0.5)
: BorderSide.none,
),
child: const Text(
'关注',
style: TextStyle(fontSize: 12),
child: Text(
isFollowing ? '已关注' : '关注',
style: const TextStyle(fontSize: 11),
),
),
],
)
),
);
}
@ -604,12 +972,12 @@ class _SearchResultPageState extends State<SearchResultPage> with SingleTickerPr
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.search_off, size: 60, color: Colors.grey),
const SizedBox(height: 16),
const Icon(Icons.search_off, size: 50, color: Colors.grey),
const SizedBox(height: 12),
Text(
message,
style: const TextStyle(
fontSize: 16,
fontSize: 14,
color: Colors.grey,
),
),

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import 'package:loopin/pages/my/vloger.dart';
import 'package:loopin/pages/video/report.dart';
import 'package:loopin/pages/search/index.dart';
import 'package:loopin/pages/search/search-result.dart';
import 'package:loopin/pages/video/commonVideo.dart';
import '../layouts/index.dart';
/* 引入路由页面 */
@ -40,6 +41,7 @@ final Map<String, Widget> routes = {
'/order/detail': const OrderDetail(),
'/vloger': const Vloger(),
'/report': const ReportPage(),
'/videoDetail': const VideoDetailPage(),
'/search': const SearchPage(),
'/search-result': const SearchResultPage(),
//settins

View File

@ -199,4 +199,13 @@ class Utils {
const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
return weekdays[(weekday - 1) % 7];
}
//
String formatLikeCount(int count) {
if (count >= 10000) {
return '${(count / 10000).toStringAsFixed(1)}w';
} else if (count >= 1000) {
return '${(count / 1000).toStringAsFixed(1)}k';
}
return count.toString();
}
}