You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
11 KiB
362 lines
11 KiB
import React, { useState, useEffect, useCallback } from 'react'; |
|
import { |
|
View, |
|
Text, |
|
StyleSheet, |
|
TextInput, |
|
TouchableOpacity, |
|
FlatList, |
|
Keyboard, |
|
Platform, |
|
SafeAreaView, |
|
StatusBar |
|
} from 'react-native'; |
|
import AsyncStorage from '@react-native-async-storage/async-storage'; |
|
import Ionicons from '@expo/vector-icons/Ionicons'; |
|
import { useNavigation, useFocusEffect } from '@react-navigation/native'; |
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
|
|
|
// 图标组件 - 使用React.memo优化渲染 |
|
const IconComponent = React.memo(({ name, size, color }: { name: string; size: number; color: string }) => { |
|
const Icon = Ionicons as any; |
|
return <Icon name={name} size={size} color={color} />; |
|
}); |
|
|
|
// 搜索历史存储键 |
|
const SEARCH_HISTORY_KEY = 'search_history'; |
|
|
|
// 历史记录项组件 - 使用React.memo优化渲染 |
|
const HistoryItem = React.memo(({ |
|
item, |
|
onPress, |
|
onRemove |
|
}: { |
|
item: string; |
|
onPress: (item: string) => void; |
|
onRemove: (item: string) => void |
|
}) => ( |
|
<View style={styles.historyItemContainer}> |
|
<TouchableOpacity |
|
style={styles.historyItem} |
|
onPress={() => onPress(item)} |
|
> |
|
<IconComponent name="time-outline" size={18} color="#999" /> |
|
<Text style={styles.historyItemText}>{item}</Text> |
|
</TouchableOpacity> |
|
<TouchableOpacity onPress={() => onRemove(item)}> |
|
<IconComponent name="close" size={18} color="#999" /> |
|
</TouchableOpacity> |
|
</View> |
|
)); |
|
|
|
// 热门标签组件 - 使用React.memo优化渲染 |
|
const HotTagItem = React.memo(({ |
|
tag, |
|
onPress |
|
}: { |
|
tag: string; |
|
onPress: (tag: string) => void |
|
}) => ( |
|
<TouchableOpacity |
|
style={styles.hotSearchTag} |
|
onPress={() => onPress(tag)} |
|
> |
|
<Text style={styles.hotSearchTagText}>{tag}</Text> |
|
</TouchableOpacity> |
|
)); |
|
|
|
export const SearchScreen = () => { |
|
const [searchText, setSearchText] = useState(''); |
|
const [searchHistory, setSearchHistory] = useState<string[]>([]); |
|
const [isLoading, setIsLoading] = useState(true); |
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
|
|
// 预设热门搜索标签 |
|
const hotSearchTags = ['手机', '耳机', '电脑', '平板', '手表', '相机', '家电', '食品']; |
|
|
|
// 只在页面聚焦时加载历史记录,而不是每次组件挂载 |
|
useFocusEffect( |
|
useCallback(() => { |
|
loadSearchHistory(); |
|
}, []) |
|
); |
|
|
|
// 从AsyncStorage加载搜索历史 - 优化异步操作 |
|
const loadSearchHistory = async () => { |
|
try { |
|
setIsLoading(true); |
|
const historyJson = await AsyncStorage.getItem(SEARCH_HISTORY_KEY); |
|
if (historyJson) { |
|
const history = JSON.parse(historyJson); |
|
setSearchHistory(history); |
|
} |
|
} catch (error) { |
|
console.error('Failed to load search history:', error); |
|
} finally { |
|
setIsLoading(false); |
|
} |
|
}; |
|
|
|
// 保存搜索历史 - 使用useCallback优化函数引用 |
|
const saveSearchHistory = useCallback(async (searchTerm: string) => { |
|
try { |
|
// 如果搜索词为空,不保存 |
|
if (!searchTerm.trim()) return; |
|
|
|
// 创建新的历史记录,将新搜索词放在最前面 |
|
const newHistory = [searchTerm, ...searchHistory.filter(item => item !== searchTerm)]; |
|
|
|
// 只保留最近10条 |
|
const trimmedHistory = newHistory.length > 10 ? newHistory.slice(0, 10) : newHistory; |
|
|
|
// 先更新UI状态,再进行异步存储操作 |
|
setSearchHistory(trimmedHistory); |
|
|
|
// 异步存储操作不阻塞UI |
|
AsyncStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(trimmedHistory)) |
|
.catch(error => console.error('Failed to save search history:', error)); |
|
} catch (error) { |
|
console.error('Failed to process search history:', error); |
|
} |
|
}, [searchHistory]); |
|
|
|
// 清除指定的搜索历史 - 使用useCallback优化函数引用 |
|
const removeSearchHistoryItem = useCallback(async (searchTerm: string) => { |
|
try { |
|
const newHistory = searchHistory.filter(item => item !== searchTerm); |
|
|
|
// 先更新UI状态,再进行异步存储操作 |
|
setSearchHistory(newHistory); |
|
|
|
// 异步存储操作不阻塞UI |
|
AsyncStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory)) |
|
.catch(error => console.error('Failed to remove search history item:', error)); |
|
} catch (error) { |
|
console.error('Failed to remove search history item:', error); |
|
} |
|
}, [searchHistory]); |
|
|
|
// 清除所有搜索历史 - 使用useCallback优化函数引用 |
|
const clearAllSearchHistory = useCallback(async () => { |
|
try { |
|
// 先更新UI状态,再进行异步存储操作 |
|
setSearchHistory([]); |
|
|
|
// 异步存储操作不阻塞UI |
|
AsyncStorage.removeItem(SEARCH_HISTORY_KEY) |
|
.catch(error => console.error('Failed to clear search history:', error)); |
|
} catch (error) { |
|
console.error('Failed to clear search history:', error); |
|
} |
|
}, []); |
|
|
|
// 处理搜索提交 - 使用useCallback优化函数引用 |
|
const handleSearch = useCallback(() => { |
|
if (searchText.trim()) { |
|
saveSearchHistory(searchText.trim()); |
|
Keyboard.dismiss(); |
|
|
|
// 导航到搜索结果页面,并传递搜索关键词 |
|
navigation.navigate('SearchResult', { keyword: searchText.trim() }); |
|
} |
|
}, [searchText, saveSearchHistory, navigation]); |
|
|
|
// 点击历史记录项 - 使用useCallback优化函数引用 |
|
const handleHistoryItemPress = useCallback((item: string) => { |
|
setSearchText(item); |
|
saveSearchHistory(item); |
|
|
|
// 导航到搜索结果页面,并传递搜索关键词 |
|
navigation.navigate('SearchResult', { keyword: item }); |
|
}, [saveSearchHistory, navigation]); |
|
|
|
// 渲染历史记录项 - 使用useCallback优化函数引用 |
|
const renderHistoryItem = useCallback(({ item }: { item: string }) => ( |
|
<HistoryItem |
|
item={item} |
|
onPress={handleHistoryItemPress} |
|
onRemove={removeSearchHistoryItem} |
|
/> |
|
), [handleHistoryItemPress, removeSearchHistoryItem]); |
|
|
|
// 处理热门标签点击 - 使用useCallback优化函数引用 |
|
const handleHotTagPress = useCallback((tag: string) => { |
|
setSearchText(tag); |
|
saveSearchHistory(tag); |
|
|
|
// 导航到搜索结果页面,并传递搜索关键词 |
|
navigation.navigate('SearchResult', { keyword: tag }); |
|
}, [saveSearchHistory, navigation]); |
|
|
|
return ( |
|
<SafeAreaView style={styles.safeArea}> |
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
<View style={styles.container}> |
|
{/* 搜索栏 */} |
|
<View style={styles.searchHeader}> |
|
<View style={styles.searchBar}> |
|
<IconComponent name="search-outline" size={20} color="#999" /> |
|
<TextInput |
|
style={styles.searchInput} |
|
placeholder="搜索商品" |
|
placeholderTextColor="#999" |
|
value={searchText} |
|
onChangeText={setSearchText} |
|
returnKeyType="search" |
|
onSubmitEditing={handleSearch} |
|
autoFocus={true} |
|
/> |
|
{searchText.length > 0 && ( |
|
<TouchableOpacity onPress={() => setSearchText('')}> |
|
<IconComponent name="close-circle" size={20} color="#999" /> |
|
</TouchableOpacity> |
|
)} |
|
</View> |
|
<TouchableOpacity style={styles.cancelButton} onPress={() => navigation.goBack()}> |
|
<Text style={styles.cancelButtonText}>取消</Text> |
|
</TouchableOpacity> |
|
</View> |
|
|
|
{/* 历史搜索记录 */} |
|
{!isLoading && searchHistory.length > 0 && ( |
|
<View style={styles.historyContainer}> |
|
<View style={styles.historyHeader}> |
|
<Text style={styles.historyTitle}>历史搜索</Text> |
|
<TouchableOpacity onPress={clearAllSearchHistory}> |
|
<IconComponent name="trash-outline" size={20} color="#999" /> |
|
</TouchableOpacity> |
|
</View> |
|
<FlatList |
|
data={searchHistory} |
|
keyExtractor={(item, index) => `history-${index}`} |
|
renderItem={renderHistoryItem} |
|
style={styles.historyList} |
|
keyboardShouldPersistTaps="handled" |
|
initialNumToRender={5} |
|
maxToRenderPerBatch={10} |
|
removeClippedSubviews={true} |
|
windowSize={5} |
|
/> |
|
</View> |
|
)} |
|
|
|
{/* 热门搜索 */} |
|
<View style={styles.hotSearchContainer}> |
|
<Text style={styles.hotSearchTitle}>热门搜索</Text> |
|
<View style={styles.hotSearchTags}> |
|
{hotSearchTags.map((tag, index) => ( |
|
<HotTagItem |
|
key={`hot-${index}`} |
|
tag={tag} |
|
onPress={handleHotTagPress} |
|
/> |
|
))} |
|
</View> |
|
</View> |
|
</View> |
|
</SafeAreaView> |
|
); |
|
}; |
|
|
|
const styles = StyleSheet.create({ |
|
safeArea: { |
|
flex: 1, |
|
backgroundColor: '#ffffff', |
|
}, |
|
container: { |
|
flex: 1, |
|
backgroundColor: '#ffffff', |
|
}, |
|
searchHeader: { |
|
flexDirection: 'row', |
|
alignItems: 'center', |
|
paddingHorizontal: 15, |
|
paddingVertical: 10, |
|
borderBottomWidth: 1, |
|
borderBottomColor: '#f0f0f0', |
|
}, |
|
searchBar: { |
|
flex: 1, |
|
flexDirection: 'row', |
|
alignItems: 'center', |
|
backgroundColor: '#f5f5f5', |
|
borderRadius: 20, |
|
paddingHorizontal: 15, |
|
height: 40, |
|
}, |
|
searchInput: { |
|
flex: 1, |
|
marginLeft: 8, |
|
fontSize: 16, |
|
color: '#333', |
|
height: 40, |
|
}, |
|
cancelButton: { |
|
marginLeft: 10, |
|
padding: 5, |
|
}, |
|
cancelButtonText: { |
|
fontSize: 16, |
|
color: '#333', |
|
}, |
|
historyContainer: { |
|
padding: 15, |
|
}, |
|
historyHeader: { |
|
flexDirection: 'row', |
|
justifyContent: 'space-between', |
|
alignItems: 'center', |
|
marginBottom: 10, |
|
}, |
|
historyTitle: { |
|
fontSize: 16, |
|
fontWeight: 'bold', |
|
color: '#333', |
|
}, |
|
historyList: { |
|
marginTop: 5, |
|
}, |
|
historyItemContainer: { |
|
flexDirection: 'row', |
|
justifyContent: 'space-between', |
|
alignItems: 'center', |
|
paddingVertical: 10, |
|
borderBottomWidth: 1, |
|
borderBottomColor: '#f0f0f0', |
|
}, |
|
historyItem: { |
|
flex: 1, |
|
flexDirection: 'row', |
|
alignItems: 'center', |
|
}, |
|
historyItemText: { |
|
fontSize: 14, |
|
color: '#333', |
|
marginLeft: 10, |
|
}, |
|
hotSearchContainer: { |
|
padding: 15, |
|
}, |
|
hotSearchTitle: { |
|
fontSize: 16, |
|
fontWeight: 'bold', |
|
color: '#333', |
|
marginBottom: 10, |
|
}, |
|
hotSearchTags: { |
|
flexDirection: 'row', |
|
flexWrap: 'wrap', |
|
}, |
|
hotSearchTag: { |
|
backgroundColor: '#f5f5f5', |
|
paddingHorizontal: 12, |
|
paddingVertical: 6, |
|
borderRadius: 15, |
|
marginRight: 10, |
|
marginBottom: 10, |
|
}, |
|
hotSearchTagText: { |
|
fontSize: 14, |
|
color: '#333', |
|
}, |
|
});
|