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

622 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/components/my_toast.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.parameters?['searchWords'] ?? '';
int _initialTabIndex = 0;
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
// 三个tab的数据
List<dynamic> _videoResults = [];
List<dynamic> _productResults = [];
List<dynamic> _userResults = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
// 初始化搜索控制器
_searchController.text = _searchQuery;
// 解析tab参数
final tabParam = Get.parameters?['tab'];
if (tabParam != null && tabParam.isNotEmpty) {
_initialTabIndex = int.tryParse(tabParam) ?? 0;
}
_tabController = TabController(
length: 4,
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);
}
}
// 执行搜索
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;
});
// 关闭键盘
_searchFocusNode.unfocus();
// 重新加载数据
_loadInitialData();
}
// 加载初始数据
Future<void> _loadInitialData() async {
try {
setState(() {
_isLoading = true;
});
// 加载第一个Tab的数据
await _loadTabData(_initialTabIndex);
} catch (e) {
print('加载数据失败: $e');
MyToast().tip(
title: '加载失败',
position: 'center',
type: 'error',
);
} finally {
setState(() {
_isLoading = false;
});
}
}
// 加载指定Tab的数据
Future<void> _loadTabData(int tabIndex) async {
try {
setState(() {
_isLoading = true;
});
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'] ?? [];
});
}
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'] ?? [];
});
}
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'] ?? [];
});
}
break;
}
} catch (e) {
print('加载Tab数据失败: $e');
MyToast().tip(
title: '加载失败',
position: 'center',
type: 'error',
);
} finally {
setState(() {
_isLoading = false;
});
}
}
@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,
// 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), // 添加左侧内边距
indicatorPadding: EdgeInsets.zero,
labelPadding: const EdgeInsets.symmetric(horizontal: 16), // 保持标签间距
indicatorSize: TabBarIndicatorSize.label, // 改为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) {
return _buildEmptyView('暂无视频结果');
}
return ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _videoResults.length,
itemBuilder: (context, index) {
final video = _videoResults[index] as Map<String, dynamic>;
return _buildVideoItem(video);
},
),
);
}
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,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.grey[200],
image: video['cover'] != null
? DecorationImage(
image: NetworkImage(video['cover'].toString()),
fit: BoxFit.cover,
)
: null,
),
child: video['cover'] == null
? const Icon(Icons.videocam, color: Colors.grey)
: null,
),
const SizedBox(width: 12),
// 视频信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
video['title']?.toString() ?? '无标题',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
video['author']?.toString() ?? '未知作者',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
'#${video['tag']?.toString() ?? '无标签'}',
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
),
const SizedBox(width: 8),
Text(
video['location']?.toString() ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
],
),
),
],
),
);
}
// 商品Tab
Widget _buildProductTab() {
if (_productResults.isEmpty) {
return _buildEmptyView('暂无商品结果');
}
return ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _productResults.length,
itemBuilder: (context, index) {
final product = _productResults[index] as Map<String, dynamic>;
return _buildProductItem(product);
},
),
);
}
Widget _buildProductItem(Map<String, dynamic> product) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
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: 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: [
Text(
product['name']?.toString() ?? '未知商品',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 6),
Text(
'¥${product['price']?.toString() ?? '0.00'}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.pink,
),
),
const SizedBox(height: 6),
Row(
children: [
Text(
'已售 ${product['sold']?.toString() ?? '0'}',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(width: 12),
Text(
'${product['comments']?.toString() ?? '0'}条评论',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
],
),
),
// 购买按钮
IconButton(
onPressed: () {
// 跳转到商品详情
},
icon: const Icon(Icons.shopping_cart, size: 20),
),
],
),
);
}
// 用户Tab
Widget _buildUserTab() {
if (_userResults.isEmpty) {
return _buildEmptyView('暂无用户结果');
}
return ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _userResults.length,
itemBuilder: (context, index) {
final user = _userResults[index] as Map<String, dynamic>;
return _buildUserItem(user);
},
),
);
}
Widget _buildUserItem(Map<String, dynamic> user) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
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: 50,
height: 50,
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)
: null,
),
const SizedBox(width: 12),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user['name']?.toString() ?? '未知用户',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
'粉丝: ${user['fans']?.toString() ?? '0'}',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
// 关注按钮
ElevatedButton(
onPressed: () {
// 关注用户
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.pink,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text(
'关注',
style: TextStyle(fontSize: 12),
),
),
],
)
);
}
// 空视图
Widget _buildEmptyView(String message) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.search_off, size: 60, color: Colors.grey),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
);
}
}