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.
857 lines
24 KiB
857 lines
24 KiB
import React, { useState, useEffect, useCallback } from "react"; |
|
import { |
|
View, |
|
Text, |
|
StyleSheet, |
|
FlatList, |
|
Image, |
|
TouchableOpacity, |
|
TextInput, |
|
SafeAreaView, |
|
StatusBar, |
|
ActivityIndicator, |
|
KeyboardAvoidingView, |
|
Platform, |
|
Keyboard, |
|
ScrollView, |
|
} from "react-native"; |
|
import Ionicons from "@expo/vector-icons/Ionicons"; |
|
import { useNavigation, useRoute } from "@react-navigation/native"; |
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
|
import { RouteProp } from "@react-navigation/native"; |
|
import { |
|
productApi, |
|
ProductParams, |
|
type Product, |
|
} from "../services/api/productApi"; |
|
import { useTranslation } from 'react-i18next'; |
|
|
|
// 图标组件 - 使用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} />; |
|
} |
|
); |
|
|
|
// 路由参数类型 |
|
type SearchResultRouteParams = { |
|
keyword: string; |
|
}; |
|
|
|
// 产品项组件 - 使用React.memo优化渲染 |
|
const ProductItem = React.memo( |
|
({ |
|
product, |
|
onPress, |
|
t, |
|
}: { |
|
product: Product; |
|
onPress: (product: Product) => void; |
|
t: any; |
|
}) => ( |
|
<TouchableOpacity |
|
style={styles.productCard} |
|
onPress={() => onPress(product)} |
|
activeOpacity={0.7} |
|
> |
|
<View style={styles.productImageContainer}> |
|
{product.product_image_urls[0] ? ( |
|
<Image |
|
source={{ uri: product.product_image_urls[0] }} |
|
style={styles.productImage} |
|
resizeMode="cover" |
|
/> |
|
) : ( |
|
<Text style={styles.placeholderText}>product picture</Text> |
|
)} |
|
</View> |
|
{/* 价格 */} |
|
<View style={styles.productInfo}> |
|
<Text style={styles.productPrice}> |
|
{product?.min_price?.toFixed(2)} |
|
</Text> |
|
{/* 产品标题 */} |
|
<Text style={styles.productTitle} numberOfLines={2}> |
|
{product.subject} |
|
</Text> |
|
{/* 销售量 */} |
|
<Text style={styles.productSales}> |
|
{t('monthlySales')}: {product.sold_out} |
|
</Text> |
|
</View> |
|
</TouchableOpacity> |
|
) |
|
); |
|
|
|
export const SearchResultScreen = () => { |
|
const { t } = useTranslation(); |
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
const route = |
|
useRoute<RouteProp<Record<string, SearchResultRouteParams>, string>>(); |
|
const [searchText, setSearchText] = useState(""); |
|
const [products, setProducts] = useState<Product[]>([]); |
|
const [loading, setLoading] = useState(true); |
|
const [hasMore, setHasMore] = useState(true); |
|
const [loadingMore, setLoadingMore] = useState(false); |
|
const [minPrice, setMinPrice] = useState<string>(""); |
|
const [maxPrice, setMaxPrice] = useState<string>(""); |
|
const [isFilterVisible, setIsFilterVisible] = useState(false); |
|
const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null); |
|
const [sortField, setSortField] = useState<"price" | "time">("price"); |
|
const [showBackToTop, setShowBackToTop] = useState(false); |
|
const flatListRef = React.useRef<FlatList>(null); |
|
|
|
const [searchParams, setSearchParams] = useState<ProductParams>({ |
|
keyword: route.params?.keyword || "", |
|
page: 1, |
|
page_size: 20, |
|
sort_order: "desc", |
|
max_price: null, |
|
min_price: null, |
|
category_id: null, |
|
sort_by: "create_date", |
|
}); |
|
|
|
// 初始化搜索关键字 |
|
useEffect(() => { |
|
if (route.params?.keyword) { |
|
setSearchText(route.params.keyword); |
|
const newParams = { |
|
...searchParams, |
|
keyword: route.params.keyword, |
|
}; |
|
setSearchParams(newParams); |
|
searchProducts(newParams); |
|
} |
|
}, [route.params?.keyword]); |
|
|
|
// 搜索产品的API调用 |
|
const searchProducts = useCallback( |
|
async (params: ProductParams, isLoadMore = false) => { |
|
if (!isLoadMore) setLoading(true); |
|
try { |
|
const res = await productApi.getSearchProducts(params); |
|
if (isLoadMore) { |
|
setProducts((prev) => [...prev, ...res.products]); |
|
} else { |
|
setProducts(res.products); |
|
} |
|
// 如果返回的数据少于页面大小,说明没有更多数据了 |
|
setHasMore(res.products.length === params.page_size); |
|
} catch (error) { |
|
console.error("Error searching products:", error); |
|
} finally { |
|
setLoading(false); |
|
setLoadingMore(false); |
|
} |
|
}, |
|
[] |
|
); |
|
|
|
// 处理搜索提交 |
|
const handleSearch = useCallback(() => { |
|
if (searchText.trim()) { |
|
// 重置排序状态 |
|
setSortField("price"); |
|
setSortOrder(null); |
|
|
|
const newParams = { |
|
...searchParams, |
|
keyword: searchText.trim(), |
|
page: 1, // 重置到第一页 |
|
}; |
|
setSearchParams(newParams); |
|
searchProducts(newParams); |
|
} |
|
}, [searchText, searchParams, searchProducts]); |
|
|
|
// 处理价格筛选 |
|
const handlePriceFilter = useCallback(() => { |
|
Keyboard.dismiss(); |
|
const newParams = { ...searchParams }; |
|
|
|
if (minPrice.trim()) { |
|
newParams.min_price = parseFloat(minPrice); |
|
} else { |
|
newParams.min_price = null; |
|
} |
|
|
|
if (maxPrice.trim()) { |
|
newParams.max_price = parseFloat(maxPrice); |
|
} else { |
|
newParams.max_price = null; |
|
} |
|
|
|
newParams.page = 1; // 重置到第一页 |
|
setSearchParams(newParams); |
|
searchProducts(newParams); |
|
console.log(newParams); |
|
}, [minPrice, maxPrice, searchParams, searchProducts]); |
|
|
|
// 重置价格筛选 |
|
const resetPriceFilter = useCallback(() => { |
|
setMinPrice(""); |
|
setMaxPrice(""); |
|
const newParams = { |
|
...searchParams, |
|
min_price: null, |
|
max_price: null, |
|
page: 1, |
|
}; |
|
setSearchParams(newParams); |
|
searchProducts(newParams); |
|
}, [searchParams, searchProducts]); |
|
|
|
// 切换筛选器显示状态 |
|
const toggleFilter = useCallback(() => { |
|
setIsFilterVisible(!isFilterVisible); |
|
}, [isFilterVisible]); |
|
|
|
// 处理点击产品 |
|
const handleProductPress = useCallback( |
|
(product: Product) => { |
|
// 导航到产品详情页,并传递产品信息 |
|
navigation.navigate("ProductDetail", { |
|
offer_id: product.offer_id, |
|
searchKeyword: searchText, |
|
price: product.min_price, |
|
}); |
|
}, |
|
[navigation, searchText] |
|
); |
|
|
|
// 返回上一页 |
|
const goBack = useCallback(() => { |
|
navigation.goBack(); |
|
}, [navigation]); |
|
|
|
// 渲染列表为空时的组件 |
|
const renderEmptyList = useCallback( |
|
() => ( |
|
<View style={styles.emptyContainer}> |
|
<IconComponent name="search-outline" size={48} color="#ccc" /> |
|
<Text style={styles.emptyText}> |
|
{t('noResults')} "{searchText}" |
|
</Text> |
|
<Text style={styles.emptySubtext}> |
|
{t('tryDifferentKeywords')} |
|
</Text> |
|
</View> |
|
), |
|
[searchText, t] |
|
); |
|
|
|
// 渲染产品项 |
|
const renderProductItem = useCallback( |
|
({ item }: { item: Product }) => ( |
|
<ProductItem product={item} onPress={handleProductPress} t={t} /> |
|
), |
|
[handleProductPress, t] |
|
); |
|
|
|
// 处理排序 |
|
const handleSort = useCallback( |
|
(field: "price" | "time", order: "asc" | "desc") => { |
|
setSortField(field); |
|
setSortOrder(order); |
|
|
|
// 本地排序,不发送API请求 |
|
setProducts((prevProducts) => { |
|
const sortedProducts = [...prevProducts]; |
|
|
|
if (field === "price") { |
|
sortedProducts.sort((a, b) => { |
|
const priceA = a.min_price || 0; |
|
const priceB = b.min_price || 0; |
|
return order === "asc" ? priceA - priceB : priceB - priceA; |
|
}); |
|
} else if (field === "time") { |
|
sortedProducts.sort((a, b) => { |
|
// 假设产品有create_time字段,如果没有可以使用其他时间相关字段 |
|
const timeA = new Date(a.create_date || 0).getTime(); |
|
const timeB = new Date(b.create_date || 0).getTime(); |
|
return order === "asc" ? timeA - timeB : timeB - timeA; |
|
}); |
|
} |
|
|
|
return sortedProducts; |
|
}); |
|
}, |
|
[] |
|
); |
|
|
|
// 处理加载更多 |
|
const handleLoadMore = useCallback(() => { |
|
if (loading || !hasMore || loadingMore) return; |
|
|
|
setLoadingMore(true); |
|
|
|
const newParams = { |
|
...searchParams, |
|
page: searchParams.page + 1, |
|
}; |
|
setSearchParams(newParams); |
|
searchProducts(newParams, true); |
|
}, [loading, hasMore, loadingMore, searchParams, searchProducts]); |
|
|
|
// 渲染底部加载指示器 |
|
const renderFooter = useCallback(() => { |
|
if (!hasMore) |
|
return ( |
|
<View style={styles.footerContainer}> |
|
<Text style={styles.footerText}>{t('noMoreData')}</Text> |
|
</View> |
|
); |
|
|
|
if (loadingMore) |
|
return ( |
|
<View style={styles.footerContainer}> |
|
<ActivityIndicator size="small" color="#0066FF" /> |
|
<Text style={styles.footerText}>{t('loadingMore')}</Text> |
|
</View> |
|
); |
|
|
|
return <View style={styles.footerSpace} />; |
|
}, [loadingMore, hasMore, t]); |
|
|
|
// 处理滚动事件 |
|
const handleScroll = useCallback((event: any) => { |
|
const offsetY = event.nativeEvent.contentOffset.y; |
|
// 当滚动超过屏幕高度的一半时显示回到顶部按钮 |
|
setShowBackToTop(offsetY > 300); |
|
}, []); |
|
|
|
// 回到顶部 |
|
const scrollToTop = useCallback(() => { |
|
flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); |
|
}, []); |
|
|
|
return ( |
|
<SafeAreaView style={styles.safeArea}> |
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
<KeyboardAvoidingView |
|
behavior={Platform.OS === "ios" ? "padding" : "height"} |
|
style={styles.container} |
|
> |
|
{/* 搜索栏 */} |
|
<View style={styles.searchHeader}> |
|
<TouchableOpacity style={styles.backButton} onPress={goBack}> |
|
<IconComponent name="arrow-back" size={24} color="#333" /> |
|
</TouchableOpacity> |
|
<View style={styles.searchBar}> |
|
<IconComponent name="search-outline" size={20} color="#999" /> |
|
<TextInput |
|
style={styles.searchInput} |
|
placeholder={t('searchProducts')} |
|
placeholderTextColor="#999" |
|
value={searchText} |
|
onChangeText={setSearchText} |
|
returnKeyType="search" |
|
onSubmitEditing={handleSearch} |
|
/> |
|
{searchText.length > 0 && ( |
|
<TouchableOpacity onPress={() => setSearchText("")}> |
|
<IconComponent name="close-circle" size={20} color="#999" /> |
|
</TouchableOpacity> |
|
)} |
|
</View> |
|
<TouchableOpacity style={styles.filterButton} onPress={toggleFilter}> |
|
<IconComponent |
|
name={isFilterVisible ? "options" : "options-outline"} |
|
size={24} |
|
color="#333" |
|
/> |
|
</TouchableOpacity> |
|
</View> |
|
|
|
{/* 搜索结果 */} |
|
<View style={styles.resultsContainer}> |
|
{/* 价格筛选器 */} |
|
{isFilterVisible && ( |
|
<View style={styles.filterContainer}> |
|
<View style={styles.priceFilterRow}> |
|
<Text style={styles.filterLabel}>{t('priceRange')}:</Text> |
|
<View style={styles.priceInputContainer}> |
|
<TextInput |
|
style={styles.priceInput} |
|
placeholder={t('minPrice')} |
|
placeholderTextColor="#999" |
|
value={minPrice} |
|
onChangeText={setMinPrice} |
|
keyboardType="numeric" |
|
returnKeyType="done" |
|
/> |
|
<Text style={styles.priceDivider}>-</Text> |
|
<TextInput |
|
style={styles.priceInput} |
|
placeholder={t('maxPrice')} |
|
placeholderTextColor="#999" |
|
value={maxPrice} |
|
onChangeText={setMaxPrice} |
|
keyboardType="numeric" |
|
returnKeyType="done" |
|
/> |
|
</View> |
|
</View> |
|
<View style={styles.filterButtons}> |
|
<TouchableOpacity |
|
style={styles.resetButton} |
|
onPress={resetPriceFilter} |
|
> |
|
<Text style={styles.resetButtonText}>{t('reset')}</Text> |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={styles.applyButton} |
|
onPress={handlePriceFilter} |
|
> |
|
<Text style={styles.applyButtonText}>{t('apply')}</Text> |
|
</TouchableOpacity> |
|
</View> |
|
</View> |
|
)} |
|
|
|
{/* 搜索结果标题栏和排序选项 */} |
|
<View style={styles.resultsHeader}> |
|
<ScrollView |
|
horizontal |
|
showsHorizontalScrollIndicator={false} |
|
style={styles.sortScrollView} |
|
> |
|
<View style={styles.sortGroup}> |
|
<Text style={styles.sortLabel}>{t('price')}:</Text> |
|
<View style={styles.sortButtons}> |
|
<TouchableOpacity |
|
style={[ |
|
styles.sortButton, |
|
sortField === "price" && sortOrder === "asc" |
|
? styles.sortButtonActive |
|
: {}, |
|
]} |
|
onPress={() => handleSort("price", "asc")} |
|
> |
|
<Text |
|
style={[ |
|
styles.sortButtonText, |
|
sortField === "price" && sortOrder === "asc" |
|
? styles.sortButtonTextActive |
|
: {}, |
|
]} |
|
> |
|
{t('lowToHigh')} |
|
</Text> |
|
{sortField === "price" && sortOrder === "asc" && ( |
|
<IconComponent |
|
name="chevron-up" |
|
size={16} |
|
color="#ff6600" |
|
/> |
|
)} |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={[ |
|
styles.sortButton, |
|
sortField === "price" && sortOrder === "desc" |
|
? styles.sortButtonActive |
|
: {}, |
|
]} |
|
onPress={() => handleSort("price", "desc")} |
|
> |
|
<Text |
|
style={[ |
|
styles.sortButtonText, |
|
sortField === "price" && sortOrder === "desc" |
|
? styles.sortButtonTextActive |
|
: {}, |
|
]} |
|
> |
|
{t('highToLow')} |
|
</Text> |
|
{sortField === "price" && sortOrder === "desc" && ( |
|
<IconComponent |
|
name="chevron-down" |
|
size={16} |
|
color="#ff6600" |
|
/> |
|
)} |
|
</TouchableOpacity> |
|
</View> |
|
</View> |
|
|
|
<View style={styles.sortDivider} /> |
|
|
|
<View style={styles.sortGroup}> |
|
<Text style={styles.sortLabel}>{t('time')}:</Text> |
|
<View style={styles.sortButtons}> |
|
<TouchableOpacity |
|
style={[ |
|
styles.sortButton, |
|
sortField === "time" && sortOrder === "asc" |
|
? styles.sortButtonActive |
|
: {}, |
|
]} |
|
onPress={() => handleSort("time", "asc")} |
|
> |
|
<Text |
|
style={[ |
|
styles.sortButtonText, |
|
sortField === "time" && sortOrder === "asc" |
|
? styles.sortButtonTextActive |
|
: {}, |
|
]} |
|
> |
|
{t('oldest')} |
|
</Text> |
|
{sortField === "time" && sortOrder === "asc" && ( |
|
<IconComponent |
|
name="chevron-up" |
|
size={16} |
|
color="#ff6600" |
|
/> |
|
)} |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={[ |
|
styles.sortButton, |
|
sortField === "time" && sortOrder === "desc" |
|
? styles.sortButtonActive |
|
: {}, |
|
]} |
|
onPress={() => handleSort("time", "desc")} |
|
> |
|
<Text |
|
style={[ |
|
styles.sortButtonText, |
|
sortField === "time" && sortOrder === "desc" |
|
? styles.sortButtonTextActive |
|
: {}, |
|
]} |
|
> |
|
{t('newest')} |
|
</Text> |
|
{sortField === "time" && sortOrder === "desc" && ( |
|
<IconComponent |
|
name="chevron-down" |
|
size={16} |
|
color="#ff6600" |
|
/> |
|
)} |
|
</TouchableOpacity> |
|
</View> |
|
</View> |
|
</ScrollView> |
|
</View> |
|
|
|
{/* 加载指示器 */} |
|
{loading ? ( |
|
<View style={styles.loadingContainer}> |
|
<ActivityIndicator size="large" color="#0066FF" /> |
|
</View> |
|
) : ( |
|
<> |
|
<FlatList |
|
ref={flatListRef} |
|
data={products} |
|
renderItem={renderProductItem} |
|
keyExtractor={(item) => String(item.offer_id)} |
|
numColumns={2} |
|
contentContainerStyle={styles.productGrid} |
|
ListEmptyComponent={renderEmptyList} |
|
ListFooterComponent={renderFooter} |
|
showsVerticalScrollIndicator={false} |
|
initialNumToRender={8} |
|
maxToRenderPerBatch={10} |
|
windowSize={5} |
|
removeClippedSubviews={true} |
|
onEndReached={handleLoadMore} |
|
onEndReachedThreshold={0.2} |
|
onScroll={handleScroll} |
|
scrollEventThrottle={16} |
|
/> |
|
{showBackToTop && ( |
|
<TouchableOpacity |
|
style={styles.backToTopButton} |
|
onPress={scrollToTop} |
|
activeOpacity={0.7} |
|
> |
|
<IconComponent name="arrow-up" size={24} color="#fff" /> |
|
</TouchableOpacity> |
|
)} |
|
</> |
|
)} |
|
</View> |
|
</KeyboardAvoidingView> |
|
</SafeAreaView> |
|
); |
|
}; |
|
|
|
const styles = StyleSheet.create({ |
|
safeArea: { |
|
flex: 1, |
|
backgroundColor: "#ffffff", |
|
}, |
|
container: { |
|
flex: 1, |
|
backgroundColor: "#f5f5f5", |
|
}, |
|
searchHeader: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
paddingHorizontal: 15, |
|
paddingVertical: 10, |
|
backgroundColor: "#fff", |
|
borderBottomWidth: 1, |
|
borderBottomColor: "#f0f0f0", |
|
}, |
|
backButton: { |
|
marginRight: 10, |
|
padding: 5, |
|
}, |
|
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, |
|
}, |
|
resultsContainer: { |
|
flex: 1, |
|
}, |
|
resultsHeader: { |
|
backgroundColor: "#fff", |
|
borderBottomWidth: 1, |
|
borderBottomColor: "#f0f0f0", |
|
paddingVertical: 10, |
|
}, |
|
resultsTitle: { |
|
fontSize: 16, |
|
fontWeight: "bold", |
|
color: "#333", |
|
flex: 1, |
|
}, |
|
resultsCount: { |
|
fontSize: 14, |
|
color: "#999", |
|
}, |
|
loadingContainer: { |
|
flex: 1, |
|
justifyContent: "center", |
|
alignItems: "center", |
|
}, |
|
productGrid: { |
|
padding: 8, |
|
}, |
|
productCard: { |
|
flex: 1, |
|
margin: 8, |
|
backgroundColor: "#fff", |
|
borderRadius: 8, |
|
overflow: "hidden", |
|
elevation: 2, // Android shadow |
|
shadowColor: "#000", // iOS shadow |
|
shadowOffset: { width: 0, height: 1 }, |
|
shadowOpacity: 0.1, |
|
shadowRadius: 2, |
|
}, |
|
productImageContainer: { |
|
height: 150, |
|
backgroundColor: "#f9f9f9", |
|
alignItems: "center", |
|
justifyContent: "center", |
|
}, |
|
productImage: { |
|
width: "100%", |
|
height: "100%", |
|
}, |
|
placeholderText: { |
|
color: "#999", |
|
fontSize: 16, |
|
}, |
|
productInfo: { |
|
padding: 10, |
|
}, |
|
productPrice: { |
|
fontSize: 18, |
|
fontWeight: "bold", |
|
color: "#ff6600", |
|
marginBottom: 5, |
|
}, |
|
productTitle: { |
|
fontSize: 14, |
|
color: "#333", |
|
marginBottom: 5, |
|
}, |
|
productSales: { |
|
fontSize: 12, |
|
color: "#999", |
|
}, |
|
emptyContainer: { |
|
flex: 1, |
|
minHeight: 300, |
|
justifyContent: "center", |
|
alignItems: "center", |
|
padding: 20, |
|
}, |
|
emptyText: { |
|
fontSize: 16, |
|
fontWeight: "bold", |
|
color: "#333", |
|
marginTop: 15, |
|
marginBottom: 8, |
|
}, |
|
emptySubtext: { |
|
fontSize: 14, |
|
color: "#999", |
|
textAlign: "center", |
|
}, |
|
filterButton: { |
|
marginLeft: 10, |
|
padding: 5, |
|
}, |
|
filterContainer: { |
|
backgroundColor: "#fff", |
|
paddingHorizontal: 15, |
|
paddingVertical: 10, |
|
borderBottomWidth: 1, |
|
borderBottomColor: "#f0f0f0", |
|
}, |
|
priceFilterRow: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
marginBottom: 10, |
|
}, |
|
filterLabel: { |
|
fontSize: 14, |
|
fontWeight: "bold", |
|
color: "#333", |
|
marginRight: 10, |
|
}, |
|
priceInputContainer: { |
|
flex: 1, |
|
flexDirection: "row", |
|
alignItems: "center", |
|
}, |
|
priceInput: { |
|
flex: 1, |
|
height: 36, |
|
backgroundColor: "#f5f5f5", |
|
borderRadius: 4, |
|
paddingHorizontal: 10, |
|
fontSize: 14, |
|
color: "#333", |
|
}, |
|
priceDivider: { |
|
marginHorizontal: 8, |
|
color: "#333", |
|
}, |
|
filterButtons: { |
|
flexDirection: "row", |
|
justifyContent: "flex-end", |
|
}, |
|
resetButton: { |
|
paddingHorizontal: 15, |
|
paddingVertical: 6, |
|
borderRadius: 4, |
|
backgroundColor: "#f5f5f5", |
|
marginRight: 10, |
|
}, |
|
resetButtonText: { |
|
fontSize: 14, |
|
color: "#666", |
|
}, |
|
applyButton: { |
|
paddingHorizontal: 15, |
|
paddingVertical: 6, |
|
borderRadius: 4, |
|
backgroundColor: "#0066FF", |
|
}, |
|
applyButtonText: { |
|
fontSize: 14, |
|
color: "#fff", |
|
fontWeight: "bold", |
|
}, |
|
sortScrollView: { |
|
flexGrow: 0, |
|
}, |
|
sortGroup: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
paddingHorizontal: 15, |
|
}, |
|
sortLabel: { |
|
fontSize: 14, |
|
color: "#666", |
|
marginRight: 8, |
|
}, |
|
sortButtons: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
}, |
|
sortButton: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
paddingVertical: 4, |
|
paddingHorizontal: 8, |
|
borderRadius: 4, |
|
marginLeft: 6, |
|
borderWidth: 1, |
|
borderColor: "#e0e0e0", |
|
}, |
|
sortButtonActive: { |
|
borderColor: "#ff6600", |
|
backgroundColor: "#fff8f5", |
|
}, |
|
sortButtonText: { |
|
fontSize: 12, |
|
color: "#666", |
|
}, |
|
sortButtonTextActive: { |
|
color: "#ff6600", |
|
fontWeight: "bold", |
|
}, |
|
sortDivider: { |
|
width: 1, |
|
height: 20, |
|
backgroundColor: "#e0e0e0", |
|
marginHorizontal: 15, |
|
}, |
|
footerContainer: { |
|
padding: 15, |
|
alignItems: "center", |
|
flexDirection: "row", |
|
justifyContent: "center", |
|
}, |
|
footerText: { |
|
fontSize: 14, |
|
color: "#666", |
|
marginLeft: 8, |
|
}, |
|
footerSpace: { |
|
height: 20, |
|
}, |
|
backToTopButton: { |
|
position: "absolute", |
|
bottom: 20, |
|
right: 20, |
|
width: 44, |
|
height: 44, |
|
borderRadius: 22, |
|
backgroundColor: "#0066FF", |
|
justifyContent: "center", |
|
alignItems: "center", |
|
elevation: 5, |
|
shadowColor: "#000", |
|
shadowOffset: { width: 0, height: 2 }, |
|
shadowOpacity: 0.3, |
|
shadowRadius: 3, |
|
}, |
|
});
|
|
|