|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import React, { useState, useEffect, useCallback } from "react"; |
|
|
|
|
import React, { useState, useEffect, useCallback, useRef } from "react"; |
|
|
|
|
import { |
|
|
|
|
View, |
|
|
|
|
Text, |
|
|
|
@ -12,7 +12,6 @@ import {
|
|
|
|
|
ActivityIndicator, |
|
|
|
|
KeyboardAvoidingView, |
|
|
|
|
Platform, |
|
|
|
|
Keyboard, |
|
|
|
|
ScrollView, |
|
|
|
|
} from "react-native"; |
|
|
|
|
import Ionicons from "@expo/vector-icons/Ionicons"; |
|
|
|
@ -39,6 +38,56 @@ type SearchResultRouteParams = {
|
|
|
|
|
keyword: string; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 懒加载图片组件
|
|
|
|
|
const LazyImage = React.memo(({ uri, style, resizeMode }: { uri: string, style: any, resizeMode: any }) => { |
|
|
|
|
const [isVisible, setIsVisible] = useState(false); |
|
|
|
|
const [hasError, setHasError] = useState(false); |
|
|
|
|
|
|
|
|
|
// // 缩略图处理 - 为原图创建更低质量的缩略版本以更快加载
|
|
|
|
|
// const getThumbnailUrl = useCallback((originalUrl: string) => {
|
|
|
|
|
// // 如果有可能,使用CDN参数来获取更小的图片
|
|
|
|
|
// // 这里是一个简单的实现,实际上需要根据具体的CDN服务来调整
|
|
|
|
|
// if (originalUrl.includes('?')) {
|
|
|
|
|
// return `${originalUrl}&quality=10&width=100`;
|
|
|
|
|
// }
|
|
|
|
|
// return `${originalUrl}?quality=10&width=100`;
|
|
|
|
|
// }, []);
|
|
|
|
|
|
|
|
|
|
const onError = useCallback(() => { |
|
|
|
|
setHasError(true); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 使用IntersectionObserver的替代方案,在组件挂载时显示图片
|
|
|
|
|
useEffect(() => { |
|
|
|
|
// 延迟一小段时间后开始加载图片
|
|
|
|
|
const timer = setTimeout(() => { |
|
|
|
|
setIsVisible(true); |
|
|
|
|
}, 100); |
|
|
|
|
|
|
|
|
|
return () => clearTimeout(timer); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<View style={[style, { backgroundColor: '#f9f9f9', overflow: 'hidden' }]}>
|
|
|
|
|
{hasError && ( |
|
|
|
|
<View style={[style, { justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5' }]}> |
|
|
|
|
<IconComponent name="image-outline" size={24} color="#999" /> |
|
|
|
|
<Text style={{ fontSize: 12, color: '#999', marginTop: 4 }}>加载失败</Text> |
|
|
|
|
</View> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{isVisible && !hasError && ( |
|
|
|
|
<Image |
|
|
|
|
source={{ uri }} |
|
|
|
|
style={style} |
|
|
|
|
resizeMode={resizeMode} |
|
|
|
|
onError={onError} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
</View> |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 产品项组件 - 使用React.memo优化渲染
|
|
|
|
|
const ProductItem = React.memo( |
|
|
|
|
({ |
|
|
|
@ -57,8 +106,8 @@ const ProductItem = React.memo(
|
|
|
|
|
> |
|
|
|
|
<View style={styles.productImageContainer}> |
|
|
|
|
{product.product_image_urls[0] ? ( |
|
|
|
|
<Image |
|
|
|
|
source={{ uri: product.product_image_urls[0] }} |
|
|
|
|
<LazyImage |
|
|
|
|
uri={product.product_image_urls[0]} |
|
|
|
|
style={styles.productImage} |
|
|
|
|
resizeMode="cover" |
|
|
|
|
/> |
|
|
|
@ -66,15 +115,20 @@ const ProductItem = React.memo(
|
|
|
|
|
<Text style={styles.placeholderText}>product picture</Text> |
|
|
|
|
)} |
|
|
|
|
</View> |
|
|
|
|
{/* 价格 */} |
|
|
|
|
{/* 产品分类 */} |
|
|
|
|
<View style={styles.productInfo}> |
|
|
|
|
<Text style={styles.productPrice}> |
|
|
|
|
{product?.min_price?.toFixed(2)} |
|
|
|
|
<Text style={styles.categoryText} numberOfLines={2}> |
|
|
|
|
{product.subject_trans} |
|
|
|
|
</Text> |
|
|
|
|
{/* 产品标题 */} |
|
|
|
|
<Text style={styles.productTitle} numberOfLines={2}> |
|
|
|
|
{product.subject} |
|
|
|
|
{/* 价格信息 */} |
|
|
|
|
<View style={styles.priceRow}> |
|
|
|
|
<Text style={styles.currentPrice}> |
|
|
|
|
{product?.min_price?.toFixed(0)} <Text style={styles.currency}>FCFA</Text> |
|
|
|
|
</Text> |
|
|
|
|
<Text style={styles.originalPrice}> |
|
|
|
|
3000<Text style={styles.currencySmall}>FCFA</Text> |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
{/* 销售量 */} |
|
|
|
|
<Text style={styles.productSales}> |
|
|
|
|
{t('monthlySales')}: {product.sold_out} |
|
|
|
@ -91,24 +145,22 @@ export const SearchResultScreen = () => {
|
|
|
|
|
useRoute<RouteProp<Record<string, SearchResultRouteParams>, string>>(); |
|
|
|
|
const [searchText, setSearchText] = useState(""); |
|
|
|
|
const [products, setProducts] = useState<Product[]>([]); |
|
|
|
|
const [originalProducts, setOriginalProducts] = 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 flatListRef = useRef<FlatList>(null); |
|
|
|
|
const [activeTab, setActiveTab] = useState<"default" | "volume" | "price">("default"); |
|
|
|
|
|
|
|
|
|
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", |
|
|
|
|
}); |
|
|
|
@ -136,6 +188,8 @@ export const SearchResultScreen = () => {
|
|
|
|
|
setProducts((prev) => [...prev, ...res.products]); |
|
|
|
|
} else { |
|
|
|
|
setProducts(res.products); |
|
|
|
|
// 保存原始排序的数据,以便默认排序时恢复
|
|
|
|
|
setOriginalProducts(res.products); |
|
|
|
|
} |
|
|
|
|
// 如果返回的数据少于页面大小,说明没有更多数据了
|
|
|
|
|
setHasMore(res.products.length === params.page_size); |
|
|
|
@ -155,6 +209,8 @@ export const SearchResultScreen = () => {
|
|
|
|
|
// 重置排序状态
|
|
|
|
|
setSortField("price"); |
|
|
|
|
setSortOrder(null); |
|
|
|
|
// 重置到默认标签
|
|
|
|
|
setActiveTab("default"); |
|
|
|
|
|
|
|
|
|
const newParams = { |
|
|
|
|
...searchParams, |
|
|
|
@ -166,43 +222,6 @@ export const SearchResultScreen = () => {
|
|
|
|
|
} |
|
|
|
|
}, [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); |
|
|
|
@ -250,6 +269,9 @@ export const SearchResultScreen = () => {
|
|
|
|
|
[handleProductPress, t] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 创建产品列表项的key提取器
|
|
|
|
|
const keyExtractor = useCallback((item: Product) => String(item.offer_id), []); |
|
|
|
|
|
|
|
|
|
// 处理排序
|
|
|
|
|
const handleSort = useCallback( |
|
|
|
|
(field: "price" | "time", order: "asc" | "desc") => { |
|
|
|
@ -327,6 +349,40 @@ export const SearchResultScreen = () => {
|
|
|
|
|
flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 处理标签切换
|
|
|
|
|
const handleTabChange = useCallback((tab: "default" | "volume" | "price") => { |
|
|
|
|
// 如果点击的是已经激活的价格标签,则切换排序顺序
|
|
|
|
|
if (tab === "price" && activeTab === "price") { |
|
|
|
|
// 如果当前是价格升序,则切换为降序;如果是降序或未设置,则切换为升序
|
|
|
|
|
const newOrder = sortOrder === "asc" ? "desc" : "asc"; |
|
|
|
|
handleSort("price", newOrder); |
|
|
|
|
scrollToTop(); |
|
|
|
|
} else { |
|
|
|
|
setActiveTab(tab); |
|
|
|
|
|
|
|
|
|
// 根据标签类型设置排序规则
|
|
|
|
|
if (tab === "price") { |
|
|
|
|
// 默认价格从低到高
|
|
|
|
|
handleSort("price", "asc"); |
|
|
|
|
scrollToTop(); |
|
|
|
|
} else if (tab === "volume") {
|
|
|
|
|
// 按销量排序
|
|
|
|
|
const sortedProducts = [...originalProducts]; |
|
|
|
|
sortedProducts.sort((a, b) => { |
|
|
|
|
const volumeA = a.sold_out || 0; |
|
|
|
|
const volumeB = b.sold_out || 0; |
|
|
|
|
return volumeB - volumeA; // 从高到低排序
|
|
|
|
|
}); |
|
|
|
|
setProducts(sortedProducts); |
|
|
|
|
scrollToTop(); |
|
|
|
|
} else { |
|
|
|
|
// 默认排序 - 恢复到原始数据顺序
|
|
|
|
|
setProducts([...originalProducts]); |
|
|
|
|
scrollToTop(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, [handleSort, activeTab, sortOrder, originalProducts, scrollToTop]); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<SafeAreaView style={styles.safeArea}> |
|
|
|
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
|
|
@ -340,7 +396,7 @@ export const SearchResultScreen = () => {
|
|
|
|
|
<IconComponent name="arrow-back" size={24} color="#333" /> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
<View style={styles.searchBar}> |
|
|
|
|
<IconComponent name="search-outline" size={20} color="#999" /> |
|
|
|
|
<IconComponent name="search-outline" size={18} color="#999" /> |
|
|
|
|
<TextInput |
|
|
|
|
style={styles.searchInput} |
|
|
|
|
placeholder={t('searchProducts')} |
|
|
|
@ -351,67 +407,62 @@ export const SearchResultScreen = () => {
|
|
|
|
|
onSubmitEditing={handleSearch} |
|
|
|
|
/> |
|
|
|
|
{searchText.length > 0 && ( |
|
|
|
|
<TouchableOpacity onPress={() => setSearchText("")}> |
|
|
|
|
<IconComponent name="close-circle" size={20} color="#999" /> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
onPress={() => setSearchText("")} |
|
|
|
|
style={styles.clearButton} |
|
|
|
|
> |
|
|
|
|
<IconComponent name="close-circle" size={18} color="#999" /> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)} |
|
|
|
|
</View> |
|
|
|
|
<TouchableOpacity style={styles.filterButton} onPress={toggleFilter}> |
|
|
|
|
<IconComponent |
|
|
|
|
name={isFilterVisible ? "options" : "options-outline"} |
|
|
|
|
size={24} |
|
|
|
|
color="#333" |
|
|
|
|
/> |
|
|
|
|
<TouchableOpacity style={styles.searchButton} onPress={handleSearch}> |
|
|
|
|
<Text style={styles.searchButtonText}>{t('cancel')}</Text> |
|
|
|
|
</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}> |
|
|
|
|
{/* 标签筛选 */} |
|
|
|
|
<View style={styles.tabContainer}> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.resetButton} |
|
|
|
|
onPress={resetPriceFilter} |
|
|
|
|
style={[styles.tabButton, activeTab === "default" && styles.activeTabButton]}
|
|
|
|
|
onPress={() => handleTabChange("default")} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.resetButtonText}>{t('reset')}</Text> |
|
|
|
|
<Text style={[styles.tabText, activeTab === "default" && styles.activeTabText]}> |
|
|
|
|
{t('default')} |
|
|
|
|
</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.applyButton} |
|
|
|
|
onPress={handlePriceFilter} |
|
|
|
|
style={[styles.tabButton, activeTab === "volume" && styles.activeTabButton]}
|
|
|
|
|
onPress={() => handleTabChange("volume")} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.applyButtonText}>{t('apply')}</Text> |
|
|
|
|
<Text style={[styles.tabText, activeTab === "volume" && styles.activeTabText]}> |
|
|
|
|
{t('volume')} |
|
|
|
|
</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
</View> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={[styles.tabButton, activeTab === "price" && styles.activeTabButton]}
|
|
|
|
|
onPress={() => handleTabChange("price")} |
|
|
|
|
> |
|
|
|
|
<View style={styles.tabButtonContent}> |
|
|
|
|
<Text style={[styles.tabText, activeTab === "price" && styles.activeTabText]}> |
|
|
|
|
{t('price')} |
|
|
|
|
</Text> |
|
|
|
|
{activeTab === "price" && ( |
|
|
|
|
<View style={styles.tabIcon}> |
|
|
|
|
<IconComponent
|
|
|
|
|
name={sortOrder === "desc" ? "chevron-down" : "chevron-up"}
|
|
|
|
|
size={16}
|
|
|
|
|
color="#000" |
|
|
|
|
/> |
|
|
|
|
</View> |
|
|
|
|
)} |
|
|
|
|
</View> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
</View> |
|
|
|
|
|
|
|
|
|
{/* 搜索结果 */} |
|
|
|
|
<View style={styles.resultsContainer}> |
|
|
|
|
{/* 搜索结果标题栏和排序选项 */} |
|
|
|
|
{isFilterVisible && ( |
|
|
|
|
<View style={styles.resultsHeader}> |
|
|
|
|
<ScrollView |
|
|
|
|
horizontal |
|
|
|
@ -541,6 +592,7 @@ export const SearchResultScreen = () => {
|
|
|
|
|
</View> |
|
|
|
|
</ScrollView> |
|
|
|
|
</View> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{/* 加载指示器 */} |
|
|
|
|
{loading ? ( |
|
|
|
@ -553,18 +605,19 @@ export const SearchResultScreen = () => {
|
|
|
|
|
ref={flatListRef} |
|
|
|
|
data={products} |
|
|
|
|
renderItem={renderProductItem} |
|
|
|
|
keyExtractor={(item) => String(item.offer_id)} |
|
|
|
|
keyExtractor={keyExtractor} |
|
|
|
|
numColumns={2} |
|
|
|
|
contentContainerStyle={styles.productGrid} |
|
|
|
|
ListEmptyComponent={renderEmptyList} |
|
|
|
|
ListFooterComponent={renderFooter} |
|
|
|
|
showsVerticalScrollIndicator={false} |
|
|
|
|
initialNumToRender={8} |
|
|
|
|
maxToRenderPerBatch={10} |
|
|
|
|
windowSize={5} |
|
|
|
|
removeClippedSubviews={true} |
|
|
|
|
initialNumToRender={4} |
|
|
|
|
maxToRenderPerBatch={8} |
|
|
|
|
windowSize={3} |
|
|
|
|
removeClippedSubviews={Platform.OS !== 'web'} |
|
|
|
|
updateCellsBatchingPeriod={50} |
|
|
|
|
onEndReached={handleLoadMore} |
|
|
|
|
onEndReachedThreshold={0.2} |
|
|
|
|
onEndReachedThreshold={0.5} |
|
|
|
|
onScroll={handleScroll} |
|
|
|
|
scrollEventThrottle={16} |
|
|
|
|
/> |
|
|
|
@ -597,15 +650,14 @@ const styles = StyleSheet.create({
|
|
|
|
|
searchHeader: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
alignItems: "center", |
|
|
|
|
paddingHorizontal: 15, |
|
|
|
|
paddingVertical: 10, |
|
|
|
|
paddingHorizontal: 12, |
|
|
|
|
paddingVertical: 8, |
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
borderBottomWidth: 1, |
|
|
|
|
borderBottomColor: "#f0f0f0", |
|
|
|
|
}, |
|
|
|
|
backButton: { |
|
|
|
|
marginRight: 10, |
|
|
|
|
padding: 5, |
|
|
|
|
padding: 4, |
|
|
|
|
}, |
|
|
|
|
searchBar: { |
|
|
|
|
flex: 1, |
|
|
|
@ -613,34 +665,75 @@ const styles = StyleSheet.create({
|
|
|
|
|
alignItems: "center", |
|
|
|
|
backgroundColor: "#f5f5f5", |
|
|
|
|
borderRadius: 20, |
|
|
|
|
paddingHorizontal: 15, |
|
|
|
|
paddingHorizontal: 8, |
|
|
|
|
height: 40, |
|
|
|
|
marginHorizontal: 8, |
|
|
|
|
position: "relative", |
|
|
|
|
}, |
|
|
|
|
searchInput: { |
|
|
|
|
flex: 1, |
|
|
|
|
marginLeft: 8, |
|
|
|
|
marginLeft: 4, |
|
|
|
|
fontSize: 16, |
|
|
|
|
color: "#333", |
|
|
|
|
height: 40, |
|
|
|
|
paddingRight: 32, |
|
|
|
|
}, |
|
|
|
|
resultsContainer: { |
|
|
|
|
flex: 1, |
|
|
|
|
clearButton: { |
|
|
|
|
position: "absolute", |
|
|
|
|
right: 8, |
|
|
|
|
top: "50%", |
|
|
|
|
transform: [{ translateY: -9 }], |
|
|
|
|
padding: 4, |
|
|
|
|
}, |
|
|
|
|
resultsHeader: { |
|
|
|
|
searchButton: { |
|
|
|
|
paddingVertical: 4, |
|
|
|
|
paddingHorizontal: 8, |
|
|
|
|
}, |
|
|
|
|
searchButtonText: { |
|
|
|
|
fontSize: 16, |
|
|
|
|
color: "#333", |
|
|
|
|
fontWeight: "500", |
|
|
|
|
}, |
|
|
|
|
tabContainer: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
borderBottomWidth: 1, |
|
|
|
|
borderBottomColor: "#f0f0f0", |
|
|
|
|
paddingVertical: 10, |
|
|
|
|
}, |
|
|
|
|
resultsTitle: { |
|
|
|
|
fontSize: 16, |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
color: "#333", |
|
|
|
|
tabButton: { |
|
|
|
|
flex: 1, |
|
|
|
|
alignItems: "center", |
|
|
|
|
justifyContent: "center", |
|
|
|
|
paddingVertical: 12, |
|
|
|
|
position: "relative", |
|
|
|
|
}, |
|
|
|
|
resultsCount: { |
|
|
|
|
tabButtonContent: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
alignItems: "center", |
|
|
|
|
justifyContent: "center", |
|
|
|
|
}, |
|
|
|
|
tabIcon: { |
|
|
|
|
marginLeft: 4, |
|
|
|
|
}, |
|
|
|
|
tabText: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#999", |
|
|
|
|
color: "#666", |
|
|
|
|
}, |
|
|
|
|
activeTabText: { |
|
|
|
|
color: "#000", |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
}, |
|
|
|
|
activeTabButton: { |
|
|
|
|
// borderBottomColor: "#007AFF",
|
|
|
|
|
}, |
|
|
|
|
resultsContainer: { |
|
|
|
|
flex: 1, |
|
|
|
|
}, |
|
|
|
|
resultsHeader: { |
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
borderBottomWidth: 1, |
|
|
|
|
borderBottomColor: "#f0f0f0", |
|
|
|
|
paddingVertical: 8, |
|
|
|
|
}, |
|
|
|
|
loadingContainer: { |
|
|
|
|
flex: 1, |
|
|
|
@ -656,11 +749,14 @@ const styles = StyleSheet.create({
|
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
borderRadius: 8, |
|
|
|
|
overflow: "hidden", |
|
|
|
|
elevation: 2, // Android shadow
|
|
|
|
|
shadowColor: "#000", // iOS shadow
|
|
|
|
|
shadowOffset: { width: 0, height: 1 }, |
|
|
|
|
shadowColor: "#000", |
|
|
|
|
shadowOffset: { |
|
|
|
|
width: 0, |
|
|
|
|
height: 2, |
|
|
|
|
}, |
|
|
|
|
shadowOpacity: 0.1, |
|
|
|
|
shadowRadius: 2, |
|
|
|
|
shadowRadius: 4, |
|
|
|
|
elevation: 3, |
|
|
|
|
}, |
|
|
|
|
productImageContainer: { |
|
|
|
|
height: 150, |
|
|
|
@ -677,107 +773,47 @@ const styles = StyleSheet.create({
|
|
|
|
|
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", |
|
|
|
|
padding: 8, |
|
|
|
|
}, |
|
|
|
|
priceFilterRow: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
alignItems: "center", |
|
|
|
|
marginBottom: 10, |
|
|
|
|
categoryText: { |
|
|
|
|
fontSize: 10, |
|
|
|
|
color: "#000000", |
|
|
|
|
fontWeight: "600", |
|
|
|
|
marginBottom: 4, |
|
|
|
|
fontFamily: 'PingFang SC' |
|
|
|
|
}, |
|
|
|
|
filterLabel: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
color: "#333", |
|
|
|
|
marginRight: 10, |
|
|
|
|
}, |
|
|
|
|
priceInputContainer: { |
|
|
|
|
flex: 1, |
|
|
|
|
priceRow: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
alignItems: "center", |
|
|
|
|
alignItems: "baseline", |
|
|
|
|
marginBottom: 4, |
|
|
|
|
}, |
|
|
|
|
priceInput: { |
|
|
|
|
flex: 1, |
|
|
|
|
height: 36, |
|
|
|
|
backgroundColor: "#f5f5f5", |
|
|
|
|
borderRadius: 4, |
|
|
|
|
paddingHorizontal: 10, |
|
|
|
|
currentPrice: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#333", |
|
|
|
|
}, |
|
|
|
|
priceDivider: { |
|
|
|
|
marginHorizontal: 8, |
|
|
|
|
color: "#333", |
|
|
|
|
fontWeight: "600", |
|
|
|
|
color: "#ff6600", |
|
|
|
|
marginRight: 4, |
|
|
|
|
}, |
|
|
|
|
filterButtons: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
justifyContent: "flex-end", |
|
|
|
|
currency: { |
|
|
|
|
fontSize: 10, |
|
|
|
|
fontWeight: "600", |
|
|
|
|
fontFamily: 'PingFang SC', |
|
|
|
|
}, |
|
|
|
|
resetButton: { |
|
|
|
|
paddingHorizontal: 15, |
|
|
|
|
paddingVertical: 6, |
|
|
|
|
borderRadius: 4, |
|
|
|
|
backgroundColor: "#f5f5f5", |
|
|
|
|
marginRight: 10, |
|
|
|
|
originalPrice: { |
|
|
|
|
fontSize: 10, |
|
|
|
|
color: "#999", |
|
|
|
|
textDecorationLine: "line-through", |
|
|
|
|
}, |
|
|
|
|
resetButtonText: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#666", |
|
|
|
|
currencySmall: { |
|
|
|
|
fontSize: 10, |
|
|
|
|
color: '#9a9a9a', |
|
|
|
|
fontWeight: "600", |
|
|
|
|
fontFamily: 'PingFang SC', |
|
|
|
|
}, |
|
|
|
|
applyButton: { |
|
|
|
|
paddingHorizontal: 15, |
|
|
|
|
paddingVertical: 6, |
|
|
|
|
borderRadius: 4, |
|
|
|
|
backgroundColor: "#0066FF", |
|
|
|
|
}, |
|
|
|
|
applyButtonText: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#fff", |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
productSales: { |
|
|
|
|
fontSize: 10, |
|
|
|
|
fontWeight: "600", |
|
|
|
|
fontFamily: 'PingFang SC',
|
|
|
|
|
color: "#7c7c7c", |
|
|
|
|
}, |
|
|
|
|
sortScrollView: { |
|
|
|
|
flexGrow: 0, |
|
|
|
@ -785,7 +821,7 @@ const styles = StyleSheet.create({
|
|
|
|
|
sortGroup: { |
|
|
|
|
flexDirection: "row", |
|
|
|
|
alignItems: "center", |
|
|
|
|
paddingHorizontal: 15, |
|
|
|
|
paddingHorizontal: 16, |
|
|
|
|
}, |
|
|
|
|
sortLabel: { |
|
|
|
|
fontSize: 14, |
|
|
|
@ -802,7 +838,7 @@ const styles = StyleSheet.create({
|
|
|
|
|
paddingVertical: 4, |
|
|
|
|
paddingHorizontal: 8, |
|
|
|
|
borderRadius: 4, |
|
|
|
|
marginLeft: 6, |
|
|
|
|
marginLeft: 4, |
|
|
|
|
borderWidth: 1, |
|
|
|
|
borderColor: "#e0e0e0", |
|
|
|
|
}, |
|
|
|
@ -822,10 +858,10 @@ const styles = StyleSheet.create({
|
|
|
|
|
width: 1, |
|
|
|
|
height: 20, |
|
|
|
|
backgroundColor: "#e0e0e0", |
|
|
|
|
marginHorizontal: 15, |
|
|
|
|
marginHorizontal: 16, |
|
|
|
|
}, |
|
|
|
|
footerContainer: { |
|
|
|
|
padding: 15, |
|
|
|
|
padding: 16, |
|
|
|
|
alignItems: "center", |
|
|
|
|
flexDirection: "row", |
|
|
|
|
justifyContent: "center", |
|
|
|
@ -848,10 +884,51 @@ const styles = StyleSheet.create({
|
|
|
|
|
backgroundColor: "#0066FF", |
|
|
|
|
justifyContent: "center", |
|
|
|
|
alignItems: "center", |
|
|
|
|
elevation: 5, |
|
|
|
|
shadowColor: "#000", |
|
|
|
|
shadowOffset: { width: 0, height: 2 }, |
|
|
|
|
shadowOpacity: 0.3, |
|
|
|
|
shadowRadius: 3, |
|
|
|
|
shadowOffset: { |
|
|
|
|
width: 0, |
|
|
|
|
height: 2, |
|
|
|
|
}, |
|
|
|
|
shadowOpacity: 0.25, |
|
|
|
|
shadowRadius: 3.84, |
|
|
|
|
elevation: 5, |
|
|
|
|
}, |
|
|
|
|
emptyContainer: { |
|
|
|
|
flex: 1, |
|
|
|
|
minHeight: 300, |
|
|
|
|
justifyContent: "center", |
|
|
|
|
alignItems: "center", |
|
|
|
|
padding: 16, |
|
|
|
|
}, |
|
|
|
|
emptyText: { |
|
|
|
|
fontSize: 16, |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
color: "#333", |
|
|
|
|
marginTop: 16, |
|
|
|
|
marginBottom: 8, |
|
|
|
|
}, |
|
|
|
|
emptySubtext: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#999", |
|
|
|
|
textAlign: "center", |
|
|
|
|
}, |
|
|
|
|
resultsTitle: { |
|
|
|
|
fontSize: 16, |
|
|
|
|
fontWeight: "bold", |
|
|
|
|
color: "#333", |
|
|
|
|
flex: 1, |
|
|
|
|
}, |
|
|
|
|
resultsCount: { |
|
|
|
|
fontSize: 14, |
|
|
|
|
color: "#999", |
|
|
|
|
}, |
|
|
|
|
filterButton: { |
|
|
|
|
marginLeft: 8, |
|
|
|
|
padding: 4, |
|
|
|
|
}, |
|
|
|
|
imagePlaceholder: { |
|
|
|
|
justifyContent: 'center', |
|
|
|
|
alignItems: 'center', |
|
|
|
|
backgroundColor: '#f5f5f5', |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|