/** * 搜索页 SearchScreen(响应式版本) * 胡萝卜BBS - 搜索帖子、用户 * 支持响应式布局,宽屏下显示更大的搜索结果区域 */ import React, { useState, useCallback } from 'react'; import { View, FlatList, StyleSheet, TouchableOpacity, ScrollView, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { colors, spacing, fontSizes, borderRadius } from '../../theme'; import { Post, User } from '../../types'; import { useUserStore } from '../../stores'; import { postService, authService } from '../../services'; import { PostCard, TabBar, SearchBar } from '../../components/business'; import { Avatar, EmptyState, Text, ResponsiveGrid } from '../../components/common'; import { HomeStackParamList } from '../../navigation/types'; import { useResponsive, useResponsiveSpacing, useResponsiveValue } from '../../hooks/useResponsive'; type NavigationProp = NativeStackNavigationProp; const TABS = ['帖子', '用户']; type SearchType = 'posts' | 'users'; export const SearchScreen: React.FC = () => { const navigation = useNavigation(); const insets = useSafeAreaInsets(); const { searchHistory: history, addSearchHistory, clearSearchHistory } = useUserStore(); // 使用响应式 hook const { width, isMobile, isTablet, isDesktop, isWideScreen } = useResponsive(); // 响应式间距 const responsivePadding = useResponsiveSpacing({ xs: 12, sm: 14, md: 16, lg: 20, xl: 24, '2xl': 32, '3xl': 40, '4xl': 48 }); const responsiveGap = useResponsiveSpacing({ xs: 8, sm: 10, md: 12, lg: 16, xl: 20, '2xl': 24, '3xl': 28, '4xl': 32 }); // 响应式搜索栏最大宽度(使用数字) const searchBarMaxWidth = useResponsiveValue({ xs: 600, sm: 600, md: 600, lg: 700, xl: 800, '2xl': 900, '3xl': 1000, '4xl': 1200, }); const [searchText, setSearchText] = useState(''); const [activeIndex, setActiveIndex] = useState(0); const [searchResults, setSearchResults] = useState<{ posts: Post[]; users: User[]; }>({ posts: [], users: [], }); const [hasSearched, setHasSearched] = useState(false); // 保存当前搜索关键词,用于Tab切换时重新搜索 const [currentKeyword, setCurrentKeyword] = useState(''); // 执行搜索 - 根据当前Tab执行对应类型的搜索 const performSearch = useCallback(async (keyword: string) => { if (!keyword.trim()) return; const trimmedKeyword = keyword.trim(); // 保存当前搜索关键词 setCurrentKeyword(trimmedKeyword); // 添加到搜索历史 addSearchHistory(trimmedKeyword); try { const searchType = getSearchType(); if (searchType === 'posts') { // 搜索帖子 const postsResponse = await postService.searchPosts(trimmedKeyword, 1, 20); setSearchResults(prev => ({ ...prev, posts: postsResponse.list })); } else if (searchType === 'users') { // 搜索用户 const usersResponse = await authService.searchUsers(trimmedKeyword, 1, 20); setSearchResults(prev => ({ ...prev, users: usersResponse.list })); } } catch (error) { console.error('搜索失败:', error); } setHasSearched(true); }, [addSearchHistory, activeIndex]); // 处理搜索提交 const handleSearch = () => { performSearch(searchText); }; // 处理搜索历史点击 const handleHistoryPress = (keyword: string) => { setSearchText(keyword); performSearch(keyword); }; // 清除搜索历史 const handleClearHistory = () => { clearSearchHistory(); }; // 跳转到帖子详情 const handlePostPress = (postId: string, scrollToComments: boolean = false) => { navigation.navigate('PostDetail', { postId, scrollToComments }); }; // 跳转到用户主页 const handleUserPress = (userId: string) => { navigation.navigate('UserProfile', { userId }); }; // 获取当前搜索类型 const getSearchType = (): SearchType => { switch (activeIndex) { case 0: return 'posts'; case 1: return 'users'; default: return 'posts'; } }; // 渲染帖子搜索结果(使用响应式网格) const renderPostResults = () => { const posts = searchResults.posts; if (posts.length === 0) { return ( ); } // 平板/桌面端使用网格布局 if (isTablet || isDesktop || isWideScreen) { return ( {posts.map(post => ( handlePostPress(post.id)} onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}} onLike={() => {}} onComment={() => handlePostPress(post.id, true)} onBookmark={() => {}} onShare={() => {}} compact={isMobile} /> ))} ); } // 移动端使用列表布局 return ( ( handlePostPress(item.id)} onUserPress={() => item.author ? handleUserPress(item.author.id) : () => {}} onLike={() => {}} onComment={() => handlePostPress(item.id, true)} onBookmark={() => {}} onShare={() => {}} compact /> )} keyExtractor={item => item.id} contentContainerStyle={{ paddingBottom: responsivePadding }} showsVerticalScrollIndicator={false} /> ); }; // 渲染用户搜索结果 const renderUserResults = () => { const users = searchResults.users; if (users.length === 0) { return ( ); } // 平板/桌面端使用网格布局 if (isTablet || isDesktop || isWideScreen) { return ( {users.map(item => ( handleUserPress(item.id)} > {item.nickname} {item.bio || '@' + item.username} {item.is_following && ( )} ))} ); } // 移动端使用列表布局 return ( ( handleUserPress(item.id)} > {item.nickname} {item.bio || '@' + item.username} {item.is_following && ( )} )} keyExtractor={item => item.id} contentContainerStyle={{ paddingVertical: responsiveGap }} showsVerticalScrollIndicator={false} /> ); }; // 渲染搜索结果 const renderSearchResults = () => { const searchType = getSearchType(); if (searchType === 'posts') { return renderPostResults(); } if (searchType === 'users') { return renderUserResults(); } return null; }; // 渲染搜索历史和热门搜索 const renderSearchSuggestions = () => { if (hasSearched) return null; return ( {/* 搜索历史 */} {history.length > 0 && ( 搜索历史 {history.map((keyword, index) => ( handleHistoryPress(keyword)} > {keyword} ))} )} ); }; return ( {/* 搜索输入框 */} navigation.goBack()} activeOpacity={0.85} > 取消 {/* Tab切换 */} { setActiveIndex(index); // 如果已经搜索过,切换Tab时重新搜索 if (currentKeyword && hasSearched) { performSearch(currentKeyword); } }} /> {/* 内容区域 */} {hasSearched ? renderSearchResults() : renderSearchSuggestions()} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background.default, }, searchHeader: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background.paper, borderBottomWidth: 1, borderBottomColor: `${colors.divider}70`, }, searchShell: { flex: 1, borderRadius: borderRadius.xl, backgroundColor: `${colors.primary.main}08`, paddingHorizontal: spacing.xs, paddingVertical: spacing.xs, }, cancelButton: { backgroundColor: `${colors.primary.main}14`, borderRadius: borderRadius.full, paddingHorizontal: spacing.md, paddingVertical: spacing.xs, }, cancelText: { fontSize: fontSizes.md, fontWeight: '600', }, tabWrapper: { backgroundColor: colors.background.paper, paddingTop: spacing.xs, paddingBottom: spacing.xs, }, suggestionsContainer: { flex: 1, }, section: { marginTop: spacing.lg, }, sectionHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.md, }, sectionTitle: { fontWeight: '600', color: colors.text.primary, }, tagContainer: { flexDirection: 'row', flexWrap: 'wrap', }, tag: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background.paper, borderRadius: borderRadius.md, }, tagText: { marginLeft: spacing.xs, }, // 移动端用户列表样式 userItem: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.background.paper, borderRadius: borderRadius.md, }, userInfo: { flex: 1, marginLeft: spacing.md, }, userName: { fontWeight: '600', color: colors.text.primary, }, followingBadge: { width: 24, height: 24, borderRadius: 12, backgroundColor: colors.primary.light + '30', alignItems: 'center', justifyContent: 'center', }, // 桌面端用户卡片样式 userCard: { backgroundColor: colors.background.paper, borderRadius: borderRadius.lg, alignItems: 'center', justifyContent: 'center', minHeight: 180, }, userCardInfo: { alignItems: 'center', marginTop: spacing.md, }, userCardName: { fontWeight: '600', color: colors.text.primary, marginBottom: spacing.xs, }, });