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.
1609 lines
45 KiB
1609 lines
45 KiB
import React, { useCallback, useState, useRef, useEffect } from "react"; |
|
import { |
|
View, |
|
Text, |
|
StyleSheet, |
|
TouchableOpacity, |
|
FlatList, |
|
InteractionManager, |
|
Image, |
|
ScrollView, |
|
Modal, |
|
RefreshControl, |
|
Dimensions, |
|
Animated, |
|
Platform, |
|
StatusBar, |
|
SafeAreaView, |
|
ViewStyle, |
|
TextStyle, |
|
ImageStyle, |
|
Linking, |
|
Alert, |
|
} from "react-native"; |
|
import { |
|
productApi, |
|
ProductParams, |
|
type Product, |
|
} from "../services/api/productApi"; |
|
import Carousel from "react-native-reanimated-carousel"; |
|
import Ionicons from "@expo/vector-icons/Ionicons"; |
|
import { useNavigation } from "@react-navigation/native"; |
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
|
import { useTranslation } from "react-i18next"; |
|
import widthUtils from "../utils/widthUtils"; |
|
import DownArrowIcon from "../components/DownArrowIcon"; |
|
import { LinearGradient } from "expo-linear-gradient"; |
|
import fontSize from "../utils/fontsizeUtils"; |
|
import CloseIcon from "../components/CloseIcon"; |
|
import CheckmarkIcon from "../components/CheckmarkIcon"; |
|
import { getSubjectTransLanguage } from "../utils/languageUtils"; |
|
import useUserStore from "../store/user"; |
|
import * as ImagePicker from "expo-image-picker"; |
|
import * as FileSystem from "expo-file-system"; |
|
// 为图标定义类型 |
|
type IconProps = { |
|
name: string; |
|
size: number; |
|
color: string; |
|
}; |
|
// 图标组件辅助函数 - 使用React.memo优化渲染 |
|
const IconComponent = React.memo(({ name, size, color }: IconProps) => { |
|
const Icon = Ionicons as any; |
|
return <Icon name={name} size={size} color={color} />; |
|
}); |
|
type SubcategoryItem = { |
|
id: string | number; |
|
title: string; |
|
icon: string; |
|
}; |
|
type CategoryContentType = { |
|
[key: string]: SubcategoryItem[]; |
|
}; |
|
|
|
// 懒加载图片组件 - 改进版本 |
|
const LazyImage = React.memo( |
|
({ |
|
uri, |
|
style, |
|
resizeMode, |
|
}: { |
|
uri: string; |
|
style: any; |
|
resizeMode: any; |
|
}) => { |
|
const [isLoaded, setIsLoaded] = useState(false); |
|
const [hasError, setHasError] = useState(false); |
|
|
|
const onLoad = useCallback(() => { |
|
setIsLoaded(true); |
|
}, []); |
|
|
|
const onError = useCallback(() => { |
|
setHasError(true); |
|
setIsLoaded(true); // Also mark as loaded on error to remove placeholder |
|
}, []); |
|
|
|
return ( |
|
<View style={[style, { overflow: "hidden" }]}> |
|
{/* Show placeholder while image is loading */} |
|
{!isLoaded && !hasError && ( |
|
<View |
|
style={[ |
|
style, |
|
styles.imagePlaceholder, |
|
{ position: "absolute", zIndex: 1 }, |
|
]} |
|
/> |
|
)} |
|
|
|
{/* Show error state if image failed to load */} |
|
{hasError && ( |
|
<View |
|
style={[ |
|
style, |
|
styles.imagePlaceholder, |
|
{ position: "absolute", zIndex: 1 }, |
|
]} |
|
> |
|
<IconComponent name="image-outline" size={24} color="#999" /> |
|
<Text |
|
style={{ fontSize: fontSize(12), color: "#999", marginTop: 4 }} |
|
> |
|
加载失败 |
|
</Text> |
|
</View> |
|
)} |
|
|
|
{/* Actual image */} |
|
<Image |
|
source={{ uri }} |
|
style={[style, { opacity: isLoaded ? 1 : 0 }]} |
|
resizeMode={resizeMode} |
|
onLoad={onLoad} |
|
onError={onError} |
|
/> |
|
</View> |
|
); |
|
} |
|
); |
|
|
|
// 产品骨架屏组件 - 用于加载状态 |
|
const ProductSkeleton = React.memo(() => { |
|
// 创建动画值 |
|
const shimmerAnim = useRef(new Animated.Value(0)).current; |
|
|
|
// 设置动画效果 |
|
useEffect(() => { |
|
const shimmerAnimation = Animated.loop( |
|
Animated.timing(shimmerAnim, { |
|
toValue: 1, |
|
duration: 1500, |
|
useNativeDriver: true, |
|
}) |
|
); |
|
|
|
shimmerAnimation.start(); |
|
|
|
return () => { |
|
shimmerAnimation.stop(); |
|
}; |
|
}, []); |
|
|
|
// 定义动画插值 |
|
const shimmerTranslate = shimmerAnim.interpolate({ |
|
inputRange: [0, 1], |
|
outputRange: [-200, 200], |
|
}); |
|
|
|
return ( |
|
<View style={styles.beautyProductCard1}> |
|
<View style={styles.skeletonImage}> |
|
<Animated.View |
|
style={[ |
|
styles.shimmer, |
|
{ |
|
transform: [{ translateX: shimmerTranslate }], |
|
}, |
|
]} |
|
/> |
|
</View> |
|
<View style={styles.beautyProductCard}> |
|
<View style={styles.skeletonTitle}> |
|
<Animated.View |
|
style={[ |
|
styles.shimmer, |
|
{ |
|
transform: [{ translateX: shimmerTranslate }], |
|
}, |
|
]} |
|
/> |
|
</View> |
|
<View style={[styles.skeletonTitle, { width: "60%" }]}> |
|
<Animated.View |
|
style={[ |
|
styles.shimmer, |
|
{ |
|
transform: [{ translateX: shimmerTranslate }], |
|
}, |
|
]} |
|
/> |
|
</View> |
|
<View style={styles.beautyProductInfoRow}> |
|
<View style={styles.flexRowCentered}> |
|
<View style={styles.skeletonPrice}> |
|
<Animated.View |
|
style={[ |
|
styles.shimmer, |
|
{ |
|
transform: [{ translateX: shimmerTranslate }], |
|
}, |
|
]} |
|
/> |
|
</View> |
|
</View> |
|
</View> |
|
<View style={styles.skeletonSales}> |
|
<Animated.View |
|
style={[ |
|
styles.shimmer, |
|
{ |
|
transform: [{ translateX: shimmerTranslate }], |
|
}, |
|
]} |
|
/> |
|
</View> |
|
</View> |
|
</View> |
|
); |
|
}); |
|
|
|
// Define the styles type to fix TypeScript errors |
|
type StylesType = { |
|
safeArea: ViewStyle; |
|
safeAreaContent: ViewStyle; |
|
container: ViewStyle; |
|
swpImg: ImageStyle; |
|
searchOverlay: ViewStyle; |
|
searchBar: ViewStyle; |
|
searchPlaceholder: TextStyle; |
|
cameraButton: ViewStyle; |
|
bannerContainer: ViewStyle; |
|
leftContainer: ViewStyle; |
|
leftTopItem: ViewStyle; |
|
leftBottomItem: ViewStyle; |
|
rightContainer: ViewStyle; |
|
bannerIcon: ImageStyle; |
|
bigbannerIcon: ImageStyle; |
|
category: ViewStyle; |
|
categoryScrollContainer: ViewStyle; |
|
categoryScroll: ViewStyle; |
|
categoryItem: ViewStyle; |
|
categoryItemActive: ViewStyle; |
|
categoryText: TextStyle; |
|
categoryTextActive: TextStyle; |
|
swiperContainer: ViewStyle; |
|
swiper: ViewStyle; |
|
dot: ViewStyle; |
|
activeDot: ViewStyle; |
|
slide: ViewStyle; |
|
slideImage: ImageStyle; |
|
fadeGradient: ViewStyle; |
|
categoryArrowContainer: ViewStyle; |
|
modalOverlay: ViewStyle; |
|
modalContent: ViewStyle; |
|
modalHeader: ViewStyle; |
|
modalTitleContainer: ViewStyle; |
|
modalTitle: TextStyle; |
|
closeButton: ViewStyle; |
|
closeButtonText: TextStyle; |
|
modalScrollView: ViewStyle; |
|
categoryModalItem: ViewStyle; |
|
categoryModalText: TextStyle; |
|
selectedCategoryText: TextStyle; |
|
subcategoryContainer: ViewStyle; |
|
subcategoryScroll: ViewStyle; |
|
subcategoryContent: ViewStyle; |
|
subcategoryItem: ViewStyle; |
|
subcategoryImagePlaceholder: ViewStyle; |
|
subcategoryText: TextStyle; |
|
productContainer: ViewStyle; |
|
productCardList: ViewStyle; |
|
productCardGroup: ViewStyle; |
|
beautyProductCard1: ViewStyle; |
|
beautyCardContainer1: ViewStyle; |
|
vipButtonContainer: ViewStyle; |
|
vipButton: ViewStyle; |
|
vipButtonText: TextStyle; |
|
vipLabelBold: TextStyle; |
|
beautyProductCard: ViewStyle; |
|
beautyProductTitle: TextStyle; |
|
beautyProductInfoRow: ViewStyle; |
|
flexRowCentered: ViewStyle; |
|
priceContainer: ViewStyle; |
|
highlightedText: TextStyle; |
|
highlightedText1: TextStyle; |
|
priceContainer1: ViewStyle; |
|
priceLabel1: TextStyle; |
|
beautySalesInfo: TextStyle; |
|
indicatorContainer: ViewStyle; |
|
indicator: ViewStyle; |
|
activeIndicator: ViewStyle; |
|
inactiveIndicator: ViewStyle; |
|
skeletonContainer: ViewStyle; |
|
skeletonImage: ViewStyle; |
|
skeletonTitle: ViewStyle; |
|
skeletonPrice: ViewStyle; |
|
skeletonSales: ViewStyle; |
|
shimmer: ViewStyle; |
|
imagePlaceholder: ViewStyle; |
|
productImage: ImageStyle; |
|
imagePickerOverlay: ViewStyle; |
|
imagePickerContent: ViewStyle; |
|
imagePickerOption: ViewStyle; |
|
imagePickerText: TextStyle; |
|
imagePickerDivider: ViewStyle; |
|
imagePickerCancelButton: ViewStyle; |
|
imagePickerCancelText: TextStyle; |
|
}; |
|
|
|
export const HomeScreen = () => { |
|
const [activeIndex, setActiveIndex] = useState(0); |
|
const screenWidth = Dimensions.get("window").width; |
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
const { t } = useTranslation(); |
|
const [showCategoryModal, setShowCategoryModal] = useState(false); |
|
const [showImagePickerModal, setShowImagePickerModal] = useState(false); |
|
const [selectedCategory, setSelectedCategory] = useState("Bijoux"); |
|
const [selectedHorizontalCategory, setSelectedHorizontalCategory] = |
|
useState("Tous"); |
|
const [refreshing, setRefreshing] = useState(false); |
|
const [loading, setLoading] = useState(true); // 添加加载状态 |
|
const horizontalScrollRef = useRef<ScrollView>(null); |
|
const userStore = useUserStore(); |
|
const [searchParams, setSearchParams] = useState<ProductParams>({ |
|
keyword: "pen", |
|
page: 1, |
|
page_size: 10, |
|
sort_order: "desc", |
|
sort_by: "default", |
|
language: "en", |
|
}); |
|
const [products, setProducts] = useState<Product[]>([]); |
|
const [hasMore, setHasMore] = useState(true); |
|
const [isLoadingMore, setIsLoadingMore] = useState(false); |
|
const [totalProducts, setTotalProducts] = useState(0); |
|
const [initialLoadComplete, setInitialLoadComplete] = useState(false); |
|
const [currentPage, setCurrentPage] = useState(1); // 添加当前页码状态 |
|
const flatListRef = useRef<FlatList>(null); |
|
const data = [ |
|
{ |
|
imgUrl: require("../../assets/img/banner en (5)_compressed.png"), |
|
add: "TikTokScreen", |
|
}, |
|
{ |
|
imgUrl: require("../../assets/img/banner en (3)_compressed.png"), |
|
add: "MemberIntroduction", |
|
}, |
|
{ |
|
imgUrl: require("../../assets/img/banner en (4)_compressed.png"), |
|
add: "CompanyScreen", |
|
}, |
|
]; |
|
const [galleryUsed, setGalleryUsed] = useState(false); // 标记是否使用过相册 |
|
const [loadingPlaceholders, setLoadingPlaceholders] = useState(0); // 添加占位符数量状态 |
|
const getProductData = async (isLoadMore = false) => { |
|
if (isLoadMore) { |
|
setIsLoadingMore(true); |
|
} else { |
|
setLoading(true); |
|
} |
|
|
|
try { |
|
const currentParams = { |
|
...searchParams, |
|
page: isLoadMore ? currentPage + 1 : 1, |
|
...(userStore.user?.user_id ? { user_id: userStore.user.user_id } : {}), |
|
}; |
|
const res = await productApi.getSearchProducts(currentParams); |
|
|
|
setTotalProducts(res.total || 0); |
|
|
|
if (isLoadMore) { |
|
setProducts(prev => [...prev, ...res.products]); |
|
setCurrentPage(prev => prev + 1); |
|
} else { |
|
setProducts(res.products); |
|
setCurrentPage(1); |
|
} |
|
|
|
const currentTotal = isLoadMore ? products.length + res.products.length : res.products.length; |
|
setHasMore(currentTotal < (res.total || 0)); |
|
|
|
if (!isLoadMore && !initialLoadComplete) { |
|
setInitialLoadComplete(true); |
|
const remainingParams = { |
|
...currentParams, |
|
page: 2, |
|
page_size: 30 |
|
}; |
|
const remainingRes = await productApi.getSearchProducts(remainingParams); |
|
setProducts(prev => { |
|
const newProducts = [...prev, ...remainingRes.products]; |
|
setHasMore(newProducts.length < (res.total || 0)); |
|
return newProducts; |
|
}); |
|
setCurrentPage(2); |
|
} |
|
|
|
} catch (error) { |
|
console.error("Error fetching products:", error); |
|
} finally { |
|
if (isLoadMore) { |
|
setIsLoadingMore(false); |
|
setLoadingPlaceholders(0); // 清除占位符 |
|
} else { |
|
setTimeout(() => { |
|
setLoading(false); |
|
}, 300); |
|
} |
|
} |
|
}; |
|
const handleProductPress = useCallback( |
|
(item: Product) => { |
|
InteractionManager.runAfterInteractions(() => { |
|
navigation.navigate("ProductDetail", { |
|
offer_id: item.offer_id, |
|
searchKeyword: searchParams.keyword, |
|
price: item.min_price, |
|
}); |
|
}); |
|
}, |
|
[navigation] |
|
); |
|
const onRefresh = useCallback(async () => { |
|
setRefreshing(true); |
|
setInitialLoadComplete(false); // 重置初始加载标记 |
|
setCurrentPage(1); // 重置页码 |
|
setSearchParams(prev => ({ ...prev, page_size: 10 })); // 只重置每页数量 |
|
try { |
|
await getProductData(); |
|
} catch (error) { |
|
console.error("Error fetching products:", error); |
|
} finally { |
|
setRefreshing(false); |
|
} |
|
}, [searchParams]); |
|
useEffect(() => { |
|
console.log("userStore.user", userStore.user); |
|
getProductData(); |
|
}, [userStore.user]); |
|
const categories = [ |
|
"Tous", |
|
"Bijoux", |
|
"Maison et Cuisine", |
|
"Vêtements femme", |
|
"Grandes tailles femme", |
|
"Chaussures femme", |
|
"Pyjamas et Sous-vête-ments", |
|
"Accessoires beauté", |
|
"Soins cheveux", |
|
"Hygiène et Soins pour le corps", |
|
"Maquillage", |
|
]; |
|
const defaultSubcategories: SubcategoryItem[] = [ |
|
{ id: 1, title: "Jewelry", icon: "diamond-outline" }, |
|
{ id: 2, title: "Earrings", icon: "ear-outline" }, |
|
{ id: 3, title: "Bracelet", icon: "watch-outline" }, |
|
{ id: 4, title: "Jewelry Sets", icon: "gift-outline" }, |
|
{ id: 5, title: "Earrings", icon: "ear-outline" }, |
|
{ id: 6, title: "Bracelet", icon: "watch-outline" }, |
|
]; |
|
const categoryContent: CategoryContentType = { |
|
Tous: [], |
|
Bijoux: defaultSubcategories, |
|
"Maison et Cuisine": defaultSubcategories, |
|
"Vêtements femme": defaultSubcategories, |
|
"Grandes tailles femme": defaultSubcategories, |
|
"Chaussures femme": defaultSubcategories, |
|
"Pyjamas et Sous-vête-ments": defaultSubcategories, |
|
"Accessoires beauté": defaultSubcategories, |
|
"Soins cheveux": defaultSubcategories, |
|
"Hygiène et Soins pour le corps": defaultSubcategories, |
|
Maquillage: defaultSubcategories, |
|
}; |
|
useEffect(() => { |
|
// Ensure the content is available when category changes |
|
if (!categoryContent[selectedHorizontalCategory]) { |
|
setSelectedHorizontalCategory("Tous"); |
|
} |
|
}, [selectedHorizontalCategory]); |
|
// 导航到搜索页面 - 使用useCallback优化函数引用 |
|
const navigateToSearch = useCallback(() => { |
|
// 使用InteractionManager延迟执行导航操作,确保当前交互和动画已完成 |
|
InteractionManager.runAfterInteractions(() => { |
|
navigation.navigate("Search"); |
|
}); |
|
}, [navigation]); |
|
const navigateToShippingDetails = useCallback(() => { |
|
InteractionManager.runAfterInteractions(() => { |
|
navigation.navigate("ShippingDetailsSection"); |
|
}); |
|
}, [navigation]); |
|
const navigateToInquiry = useCallback(() => { |
|
InteractionManager.runAfterInteractions(() => { |
|
navigation.navigate("InquiryScreen"); |
|
}); |
|
}, [navigation]); |
|
const scrollToCategory = (category: string) => { |
|
const categoryIndex = categories.findIndex((c) => c === category); |
|
if (categoryIndex !== -1 && horizontalScrollRef.current) { |
|
const firstFourKeys = Object.keys(categoryContent).slice( |
|
0, |
|
categoryIndex - 1 |
|
); |
|
let str = ""; |
|
firstFourKeys.forEach((key, index) => { |
|
str += key; |
|
}); |
|
horizontalScrollRef.current.scrollTo({ |
|
x: str.length * fontSize(16) + (categoryIndex - 1 + 17), |
|
animated: true, |
|
}); |
|
} |
|
}; |
|
// 渲染产品列表项 |
|
const renderProductItem = ({ item }: { item: Product }) => ( |
|
<TouchableOpacity |
|
onPress={() => handleProductPress(item)} |
|
activeOpacity={0.9} |
|
style={styles.beautyProductCard1} |
|
> |
|
<View style={styles.beautyCardContainer1}> |
|
{item.product_image_urls && item.product_image_urls.length > 0 ? ( |
|
<LazyImage |
|
uri={item.product_image_urls[0]} |
|
style={styles.productImage} |
|
resizeMode="cover" |
|
/> |
|
) : ( |
|
<View style={[styles.productImage as any, styles.imagePlaceholder]}> |
|
<IconComponent name="image-outline" size={24} color="#999" /> |
|
</View> |
|
)} |
|
|
|
{userStore.user?.user_id && ( |
|
<View style={styles.vipButtonContainer}> |
|
<TouchableOpacity style={styles.vipButton}> |
|
<Text style={styles.vipButtonText}>VIP</Text> |
|
<Text style={styles.vipLabelBold}> |
|
{userStore.user?.vip_level} |
|
</Text> |
|
</TouchableOpacity> |
|
</View> |
|
)} |
|
</View> |
|
<View style={styles.beautyProductCard}> |
|
<Text |
|
style={styles.beautyProductTitle} |
|
numberOfLines={2} |
|
ellipsizeMode="tail" |
|
> |
|
{getSubjectTransLanguage(item)} |
|
</Text> |
|
<View style={styles.beautyProductInfoRow}> |
|
<View style={styles.flexRowCentered}> |
|
{userStore.user?.user_id && ( |
|
<Text style={styles.priceLabel1}> |
|
{item.original_min_price || "0"} |
|
{item.currency || "FCFA"} |
|
</Text> |
|
)} |
|
<View style={styles.priceContainer}> |
|
<Text style={styles.highlightedText}> |
|
{item.min_price || "0"} |
|
</Text> |
|
<Text style={styles.highlightedText1}> |
|
{item.currency || "FCFA"} |
|
</Text> |
|
</View> |
|
</View> |
|
</View> |
|
<Text style={styles.beautySalesInfo}> |
|
{item.sold_out || "0"}+ ventes |
|
</Text> |
|
</View> |
|
</TouchableOpacity> |
|
); |
|
|
|
// 渲染骨架屏网格 |
|
const renderSkeletonGrid = useCallback(() => { |
|
// 创建骨架屏数组 |
|
const skeletonArray = Array(8).fill(null); |
|
|
|
return ( |
|
<View style={styles.skeletonContainer}> |
|
<FlatList |
|
data={skeletonArray} |
|
renderItem={() => <ProductSkeleton />} |
|
keyExtractor={(_, index) => `skeleton-${index}`} |
|
numColumns={2} |
|
columnWrapperStyle={styles.productCardGroup} |
|
scrollEnabled={false} |
|
contentContainerStyle={{ paddingBottom: 15 }} |
|
/> |
|
</View> |
|
); |
|
}, []); |
|
|
|
// 清理expo-image-picker临时文件 |
|
const cleanupImagePickerCache = async () => { |
|
try { |
|
// Skip cache cleanup on web platform |
|
if (Platform.OS === 'web') { |
|
console.log('Cache cleanup skipped on web platform'); |
|
setGalleryUsed(false); |
|
return; |
|
} |
|
|
|
// 相册选择后清理临时缓存 |
|
const cacheDir = `${FileSystem.cacheDirectory}ImagePicker`; |
|
await FileSystem.deleteAsync(cacheDir, { idempotent: true }); |
|
console.log("已清理ImagePicker缓存"); |
|
|
|
// 立即重置状态,无需用户干预 |
|
setGalleryUsed(false); |
|
} catch (error) { |
|
console.log("清理缓存错误", error); |
|
// Even if cleanup fails, reset the state |
|
setGalleryUsed(false); |
|
} |
|
}; |
|
|
|
// 将图片URI转换为FormData |
|
const uriToFormData = async (uri: string) => { |
|
try { |
|
// 创建FormData对象 |
|
const formData = new FormData(); |
|
|
|
// 获取文件名 |
|
const filename = uri.split("/").pop() || "image.jpg"; |
|
|
|
// 判断文件类型(mime type) |
|
const match = /\.(\w+)$/.exec(filename); |
|
const type = match ? `image/${match[1]}` : "image/jpeg"; |
|
|
|
// 处理iOS路径前缀 |
|
const imageUri = Platform.OS === "ios" ? uri.replace("file://", "") : uri; |
|
|
|
// 将图片转换为Blob |
|
const imageFetchResponse = await fetch(imageUri); |
|
const imageBlob = await imageFetchResponse.blob(); |
|
|
|
// 添加图片到FormData |
|
formData.append("image", imageBlob, filename); |
|
|
|
console.log("FormData 详情:"); |
|
console.log("- 图片URI:", uri); |
|
console.log("- 文件名:", filename); |
|
console.log("- 文件类型:", type); |
|
|
|
return formData; |
|
} catch (error) { |
|
console.error("创建FormData错误:", error); |
|
throw error; |
|
} |
|
}; |
|
|
|
// 处理从相册选择 |
|
const handleChooseFromGallery = useCallback(async () => { |
|
console.log("handleChooseFromGallery"); |
|
setShowImagePickerModal(false); |
|
|
|
// 等待模态窗关闭后再执行 |
|
setTimeout(async () => { |
|
try { |
|
// 请求相册权限 |
|
const permissionResult = |
|
await ImagePicker.requestMediaLibraryPermissionsAsync(); |
|
if (permissionResult.status !== "granted") { |
|
console.log("相册权限被拒绝"); |
|
return; |
|
} |
|
|
|
// 打开相册 |
|
const result = await ImagePicker.launchImageLibraryAsync({ |
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|
allowsEditing: true, |
|
aspect: [4, 3], |
|
quality: 1, |
|
}); |
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) { |
|
console.log("相册选择成功:", result.assets[0].uri); |
|
|
|
await cleanupImagePickerCache(); |
|
navigation.navigate("ImageSearchResultScreen", { |
|
image: result.assets[0].uri, |
|
type: 1, |
|
}); |
|
} |
|
} catch (error: any) { |
|
console.error("相册错误:", error); |
|
// 出错时也清理缓存 |
|
await cleanupImagePickerCache(); |
|
} |
|
}, 500); |
|
}, [userStore.user]); |
|
|
|
// 处理相机拍照 - 简化版本,不再需要处理galleryUsed |
|
const handleTakePhoto = useCallback(async () => { |
|
console.log("handleTakePhoto"); |
|
setShowImagePickerModal(false); |
|
|
|
// 等待模态窗关闭后再执行 |
|
setTimeout(async () => { |
|
try { |
|
const permissionResult = |
|
await ImagePicker.requestCameraPermissionsAsync(); |
|
if (permissionResult.status !== "granted") { |
|
console.log("相机权限被拒绝"); |
|
return; |
|
} |
|
|
|
const result = await ImagePicker.launchCameraAsync({ |
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|
allowsEditing: true, |
|
aspect: [4, 3], |
|
quality: 1, |
|
}); |
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) { |
|
console.log("拍照成功:", result.assets[0].uri); |
|
|
|
// 使用后清理缓存 |
|
await cleanupImagePickerCache(); |
|
// 将图片URI转换为FormData |
|
navigation.navigate("ImageSearchResultScreen", { |
|
image: result.assets[0].uri, |
|
type: 1, |
|
}); |
|
} |
|
} catch (error: any) { |
|
console.error("相机错误:", error); |
|
// 出错时也清理缓存 |
|
await cleanupImagePickerCache(); |
|
} |
|
}, 500); |
|
}, [userStore.user]); |
|
|
|
// 重置应用状态函数 |
|
const resetAppState = useCallback(() => { |
|
// 重置标记 |
|
setGalleryUsed(false); |
|
|
|
// 清理缓存 |
|
cleanupImagePickerCache(); |
|
|
|
// 提示用户 |
|
Alert.alert("已重置", "现在您可以使用相机功能了"); |
|
}, []); |
|
|
|
// 修改加载更多函数 |
|
const loadMore = () => { |
|
if (!hasMore || isLoadingMore) { |
|
console.log('不加载更多:', { hasMore, isLoadingMore }); |
|
return; |
|
} |
|
|
|
console.log('开始加载更多, 当前页码:', currentPage); |
|
setLoadingPlaceholders(10); // 设置10个占位符 |
|
getProductData(true); |
|
}; |
|
|
|
// 修改滚动到底部处理函数 |
|
const handleEndReached = () => { |
|
console.log('触发加载更多'); |
|
loadMore(); |
|
}; |
|
|
|
// 修改渲染函数 |
|
const renderItem = ({ item, index }: { item: Product; index: number }) => { |
|
// 如果是占位符 |
|
if (index >= products.length && index < products.length + loadingPlaceholders) { |
|
return <ProductSkeleton />; |
|
} |
|
|
|
return renderProductItem({ item }); |
|
}; |
|
|
|
// 渲染列表头部内容 |
|
const renderHeader = () => ( |
|
<> |
|
{/* 轮播图 */} |
|
<View style={styles.swiperContainer}> |
|
<Carousel |
|
loop |
|
width={screenWidth} |
|
data={data} |
|
height={widthUtils(286, 286).height} |
|
modeConfig={{ |
|
parallaxScrollingScale: 0.9, |
|
parallaxScrollingOffset: 50, |
|
}} |
|
renderItem={({ item }) => ( |
|
<TouchableOpacity |
|
onPress={() => navigation.navigate(item.add)} |
|
key={item.imgUrl} |
|
style={{ |
|
flex: 1, |
|
justifyContent: "center", |
|
alignItems: "center", |
|
backgroundColor: "#f2f2f2", |
|
borderRadius: 0, |
|
overflow: "hidden", |
|
}} |
|
> |
|
<Image |
|
source={item.imgUrl} |
|
style={{ width: "100%", height: "100%" }} |
|
resizeMode="cover" |
|
defaultSource={require("../../assets/img/banner en (3).png")} |
|
/> |
|
</TouchableOpacity> |
|
)} |
|
/> |
|
{/* 自定义指示器 */} |
|
{/* <View style={styles.indicatorContainer}> |
|
{data.map((_, index) => ( |
|
<View |
|
key={index} |
|
style={[ |
|
styles.indicator, |
|
activeIndex === index ? styles.activeIndicator : styles.inactiveIndicator, |
|
]} |
|
/> |
|
))} |
|
</View> */} |
|
{/* 搜索栏 - 定位在轮播图上方 */} |
|
<View style={styles.searchOverlay}> |
|
<TouchableOpacity |
|
style={styles.searchBar} |
|
activeOpacity={0.7} |
|
onPress={navigateToSearch} |
|
> |
|
<IconComponent name="search-outline" size={20} color="#999" /> |
|
<Text style={styles.searchPlaceholder}>{t("homePage.searchPlaceholder")}</Text> |
|
<TouchableOpacity |
|
style={styles.cameraButton} |
|
onPress={() => setShowImagePickerModal(true)} |
|
> |
|
<IconComponent name="camera-outline" size={24} color="#333" /> |
|
</TouchableOpacity> |
|
</TouchableOpacity> |
|
</View> |
|
</View> |
|
{/* 内容区域 */} |
|
<View style={styles.bannerContainer}> |
|
{/* 左侧区域 - 上下两个 */} |
|
<View style={styles.leftContainer}> |
|
<TouchableOpacity |
|
style={styles.leftTopItem} |
|
onPress={navigateToShippingDetails} |
|
> |
|
<Image |
|
source={require("../../assets/img/a_计算运费.png")} |
|
style={styles.bannerIcon} |
|
/> |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={styles.leftBottomItem} |
|
onPress={() => navigation.navigate("TikTokScreen")} |
|
> |
|
<Image |
|
source={require("../../assets/img/a_tiktok.png")} |
|
style={styles.bannerIcon} |
|
/> |
|
</TouchableOpacity> |
|
</View> |
|
{/* 右侧区域 - 一个 */} |
|
<TouchableOpacity |
|
style={styles.rightContainer} |
|
onPress={navigateToInquiry} |
|
> |
|
<Image |
|
source={require("../../assets/img/a_VIP.png")} |
|
style={styles.bigbannerIcon} |
|
/> |
|
</TouchableOpacity> |
|
</View> |
|
<View style={styles.category}> |
|
<View style={styles.categoryScrollContainer}> |
|
<ScrollView |
|
bounces={false} |
|
overScrollMode="never" |
|
ref={horizontalScrollRef} |
|
horizontal |
|
showsHorizontalScrollIndicator={false} |
|
style={styles.categoryScroll} |
|
> |
|
{categories.map((category, index) => ( |
|
<TouchableOpacity |
|
key={index} |
|
style={[ |
|
styles.categoryItem, |
|
selectedHorizontalCategory === category && |
|
styles.categoryItemActive, |
|
]} |
|
onPress={() => setSelectedHorizontalCategory(category)} |
|
> |
|
<Text |
|
style={[ |
|
styles.categoryText, |
|
selectedHorizontalCategory === category && |
|
styles.categoryTextActive, |
|
]} |
|
> |
|
{category} |
|
</Text> |
|
</TouchableOpacity> |
|
))} |
|
</ScrollView> |
|
<LinearGradient |
|
colors={["rgba(255,255,255,0)", "rgba(255,255,255,1)"]} |
|
start={{ x: 0, y: 0 }} |
|
end={{ x: 1, y: 0 }} |
|
style={styles.fadeGradient} |
|
/> |
|
</View> |
|
<View style={styles.categoryArrowContainer}> |
|
<TouchableOpacity onPress={() => setShowCategoryModal(true)}> |
|
<DownArrowIcon size={fontSize(18)} color="#666" rotation={360} /> |
|
</TouchableOpacity> |
|
</View> |
|
</View> |
|
{/* Subcategory Content */} |
|
{selectedHorizontalCategory && |
|
categoryContent[selectedHorizontalCategory] && |
|
categoryContent[selectedHorizontalCategory].length > 0 ? ( |
|
<View style={styles.subcategoryContainer}> |
|
<ScrollView |
|
bounces={false} |
|
overScrollMode="never" |
|
horizontal |
|
showsHorizontalScrollIndicator={false} |
|
style={styles.subcategoryScroll} |
|
contentContainerStyle={styles.subcategoryContent} |
|
> |
|
{categoryContent[selectedHorizontalCategory].map((item) => ( |
|
<TouchableOpacity |
|
key={item.id} |
|
style={styles.subcategoryItem} |
|
onPress={() => { |
|
// Handle subcategory selection |
|
}} |
|
> |
|
<View style={styles.subcategoryImagePlaceholder}> |
|
<IconComponent name={item.icon} size={24} color="#666" /> |
|
</View> |
|
<Text style={styles.subcategoryText}>{item.title}</Text> |
|
</TouchableOpacity> |
|
))} |
|
</ScrollView> |
|
</View> |
|
) : null} |
|
</> |
|
); |
|
|
|
return ( |
|
<SafeAreaView style={styles.safeArea}> |
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
<View style={styles.safeAreaContent}> |
|
<View style={styles.container}> |
|
{loading ? ( |
|
<View> |
|
{renderHeader()} |
|
{renderSkeletonGrid()} |
|
</View> |
|
) : ( |
|
<FlatList |
|
ref={flatListRef} |
|
data={[...products, ...Array(loadingPlaceholders).fill(null)]} |
|
numColumns={2} |
|
showsVerticalScrollIndicator={false} |
|
columnWrapperStyle={styles.productCardGroup} |
|
renderItem={renderItem} |
|
keyExtractor={(item, index) => |
|
item?.offer_id?.toString() || `placeholder-${index}` |
|
} |
|
contentContainerStyle={{ |
|
paddingBottom: 15, |
|
backgroundColor: "transparent", |
|
}} |
|
ListHeaderComponent={renderHeader} |
|
onEndReached={handleEndReached} |
|
onEndReachedThreshold={3} |
|
ListFooterComponent={() => ( |
|
!hasMore && !loadingPlaceholders ? ( |
|
<View style={{ padding: 10, alignItems: 'center' }}> |
|
<Text>没有更多数据了</Text> |
|
</View> |
|
) : null |
|
)} |
|
refreshControl={ |
|
<RefreshControl |
|
refreshing={refreshing} |
|
onRefresh={onRefresh} |
|
colors={["#ff5100"]} |
|
tintColor="#ff5100" |
|
progressBackgroundColor="transparent" |
|
/> |
|
} |
|
/> |
|
)} |
|
|
|
{/* Categories Modal */} |
|
<Modal |
|
visible={showCategoryModal} |
|
animationType="slide" |
|
transparent={true} |
|
onRequestClose={() => setShowCategoryModal(false)} |
|
> |
|
<View style={styles.modalOverlay}> |
|
<View style={styles.modalContent}> |
|
<View style={styles.modalHeader}> |
|
<View style={styles.modalTitleContainer}> |
|
<Ionicons name="flame-outline" size={24} color="#000" /> |
|
<Text style={styles.modalTitle}>推荐</Text> |
|
</View> |
|
<TouchableOpacity |
|
style={styles.closeButton} |
|
onPress={() => setShowCategoryModal(false)} |
|
> |
|
<Text style={styles.closeButtonText}> |
|
<CloseIcon size={18} color="#000" /> |
|
</Text> |
|
</TouchableOpacity> |
|
</View> |
|
<ScrollView |
|
style={styles.modalScrollView} |
|
bounces={false} |
|
overScrollMode="never" |
|
> |
|
{categories.map((category, index) => ( |
|
<TouchableOpacity |
|
key={index} |
|
style={styles.categoryModalItem} |
|
onPress={() => { |
|
setSelectedCategory(category); |
|
setSelectedHorizontalCategory(category); |
|
setShowCategoryModal(false); |
|
scrollToCategory(category); |
|
}} |
|
> |
|
<Text |
|
style={[ |
|
styles.categoryModalText, |
|
selectedCategory === category && |
|
styles.selectedCategoryText, |
|
]} |
|
> |
|
{category} |
|
</Text> |
|
{selectedCategory === category && ( |
|
<CheckmarkIcon size={fontSize(16)} color="#000" /> |
|
)} |
|
</TouchableOpacity> |
|
))} |
|
</ScrollView> |
|
</View> |
|
</View> |
|
</Modal> |
|
|
|
{/* Image Picker Modal */} |
|
<Modal |
|
visible={showImagePickerModal} |
|
animationType="slide" |
|
transparent={true} |
|
onRequestClose={() => setShowImagePickerModal(false)} |
|
> |
|
<TouchableOpacity |
|
style={styles.imagePickerOverlay} |
|
activeOpacity={1} |
|
onPress={() => setShowImagePickerModal(false)} |
|
> |
|
<View style={styles.imagePickerContent}> |
|
{!galleryUsed ? ( |
|
// 正常状态,显示相机选项 |
|
<TouchableOpacity |
|
style={styles.imagePickerOption} |
|
onPress={handleTakePhoto} |
|
> |
|
<IconComponent |
|
name="camera-outline" |
|
size={24} |
|
color="#333" |
|
/> |
|
<Text style={styles.imagePickerText}>拍照</Text> |
|
</TouchableOpacity> |
|
) : ( |
|
// 已使用相册状态,显示重置选项 |
|
<TouchableOpacity |
|
style={styles.imagePickerOption} |
|
onPress={resetAppState} |
|
> |
|
<IconComponent |
|
name="refresh-outline" |
|
size={24} |
|
color="#333" |
|
/> |
|
<Text style={styles.imagePickerText}>重置相机功能</Text> |
|
</TouchableOpacity> |
|
)} |
|
|
|
<View style={styles.imagePickerDivider} /> |
|
|
|
<TouchableOpacity |
|
style={styles.imagePickerOption} |
|
onPress={handleChooseFromGallery} |
|
> |
|
<IconComponent name="images-outline" size={24} color="#333" /> |
|
<Text style={styles.imagePickerText}>从相册选择</Text> |
|
</TouchableOpacity> |
|
|
|
<TouchableOpacity |
|
style={styles.imagePickerCancelButton} |
|
onPress={() => setShowImagePickerModal(false)} |
|
> |
|
<Text style={styles.imagePickerCancelText}>取消</Text> |
|
</TouchableOpacity> |
|
</View> |
|
</TouchableOpacity> |
|
</Modal> |
|
</View> |
|
</View> |
|
</SafeAreaView> |
|
); |
|
}; |
|
const styles = StyleSheet.create<StylesType>({ |
|
safeArea: { |
|
flex: 1, |
|
backgroundColor: "#fff", |
|
}, |
|
safeAreaContent: { |
|
flex: 1, |
|
paddingTop: Platform.OS === "android" ? 0 : 0, |
|
}, |
|
container: { |
|
flex: 1, |
|
backgroundColor: "#fff", |
|
}, |
|
swpImg: { |
|
width: "100%", |
|
height: 180, |
|
}, |
|
searchOverlay: { |
|
position: "absolute", |
|
top: 60, |
|
left: 15, |
|
right: 15, |
|
zIndex: 10, |
|
}, |
|
searchBar: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
backgroundColor: "rgba(255, 255, 255, 0.9)", |
|
borderRadius: 20, |
|
paddingHorizontal: 15, |
|
height: 40, |
|
shadowColor: "#000", |
|
shadowOffset: { |
|
width: 0, |
|
height: 2, |
|
}, |
|
shadowOpacity: 0.1, |
|
shadowRadius: 3, |
|
elevation: 3, |
|
}, |
|
searchPlaceholder: { |
|
flex: 1, |
|
marginLeft: 8, |
|
fontSize: fontSize(16), |
|
}, |
|
cameraButton: { |
|
padding: 5, |
|
}, |
|
bannerContainer: { |
|
flexDirection: "row", |
|
marginTop: 20, |
|
paddingHorizontal: 15, |
|
width: "100%", |
|
}, |
|
leftContainer: { |
|
marginRight: "4%", |
|
}, |
|
leftTopItem: { |
|
height: widthUtils(90, 200).height, |
|
width: widthUtils(90, 200).width, |
|
borderRadius: 8, |
|
padding: 0, |
|
marginBottom: 10, |
|
overflow: "hidden", |
|
}, |
|
leftBottomItem: { |
|
height: widthUtils(90, 200).height, |
|
width: widthUtils(90, 200).width, |
|
borderRadius: 8, |
|
padding: 0, |
|
overflow: "hidden", |
|
}, |
|
rightContainer: { |
|
width: widthUtils(190, 180).width, |
|
height: widthUtils(190, 180).height, |
|
borderRadius: 8, |
|
padding: 0, |
|
overflow: "hidden", |
|
flex: 1, |
|
}, |
|
bannerIcon: { |
|
width: "100%", |
|
height: "100%", |
|
objectFit: "contain", |
|
}, |
|
bigbannerIcon: { |
|
width: "100%", |
|
height: "100%", |
|
objectFit: "contain", |
|
}, |
|
category: { |
|
width: "100%", |
|
paddingVertical: 10, |
|
flexDirection: "row", |
|
alignItems: "center", |
|
marginTop: 10, |
|
backgroundColor: "#fff", |
|
}, |
|
categoryScrollContainer: { |
|
flex: 1, |
|
position: "relative", |
|
overflow: "hidden", |
|
paddingRight: 55, |
|
}, |
|
categoryScroll: { |
|
paddingHorizontal: 15, |
|
}, |
|
categoryItem: { |
|
paddingHorizontal: 12, |
|
paddingVertical: 8, |
|
marginRight: 5, |
|
}, |
|
categoryItemActive: { |
|
borderBottomWidth: 2, |
|
borderBottomColor: "#000", |
|
}, |
|
categoryText: { |
|
fontSize: fontSize(16), |
|
color: "#747474", |
|
fontFamily: "Alexandria", |
|
fontWeight: "400", |
|
}, |
|
categoryTextActive: { |
|
color: "#000", |
|
fontWeight: "500", |
|
}, |
|
swiperContainer: { |
|
width: "100%", |
|
height: widthUtils(286, 430).height, |
|
paddingHorizontal: 0, |
|
}, |
|
swiper: { |
|
width: "100%", |
|
}, |
|
dot: { |
|
backgroundColor: "rgba(255,255,255,0.5)", |
|
width: 8, |
|
height: 8, |
|
borderRadius: 4, |
|
marginLeft: 3, |
|
marginRight: 3, |
|
}, |
|
activeDot: { |
|
backgroundColor: "#fff", |
|
width: 20, |
|
height: 8, |
|
borderRadius: 4, |
|
marginLeft: 3, |
|
marginRight: 3, |
|
}, |
|
slide: { |
|
flex: 1, |
|
justifyContent: "center", |
|
alignItems: "center", |
|
width: "100%", |
|
}, |
|
slideImage: { |
|
width: "100%", |
|
height: "100%", |
|
}, |
|
fadeGradient: { |
|
position: "absolute", |
|
right: 40, |
|
top: 0, |
|
bottom: 0, |
|
width: 40, |
|
zIndex: 1, |
|
backgroundColor: "transparent", |
|
}, |
|
categoryArrowContainer: { |
|
position: "absolute", |
|
right: 0, |
|
paddingRight: 15, |
|
height: "100%", |
|
justifyContent: "center", |
|
alignItems: "center", |
|
zIndex: 2, |
|
backgroundColor: "#fff", |
|
}, |
|
modalOverlay: { |
|
flex: 1, |
|
backgroundColor: "rgba(0, 0, 0, 0.5)", |
|
justifyContent: "flex-end", |
|
}, |
|
modalContent: { |
|
backgroundColor: "#fff", |
|
borderTopLeftRadius: 20, |
|
borderTopRightRadius: 20, |
|
maxHeight: "80%", |
|
}, |
|
modalHeader: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
justifyContent: "space-between", |
|
padding: 16, |
|
borderBottomWidth: 1, |
|
borderBottomColor: "#f0f0f0", |
|
}, |
|
modalTitleContainer: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
}, |
|
modalTitle: { |
|
fontSize: fontSize(16), |
|
fontWeight: "600", |
|
marginLeft: 8, |
|
}, |
|
closeButton: { |
|
padding: 8, |
|
}, |
|
closeButtonText: { |
|
fontSize: fontSize(24), |
|
color: "#000", |
|
fontWeight: "300", |
|
}, |
|
modalScrollView: { |
|
padding: 16, |
|
}, |
|
categoryModalItem: { |
|
flexDirection: "row", |
|
justifyContent: "space-between", |
|
alignItems: "center", |
|
paddingVertical: 16, |
|
}, |
|
categoryModalText: { |
|
fontSize: fontSize(16), |
|
color: "#666", |
|
fontFamily: "Segoe UI", |
|
fontWeight: "700", |
|
}, |
|
selectedCategoryText: { |
|
color: "#000", |
|
fontWeight: "500", |
|
}, |
|
subcategoryContainer: { |
|
height: 100, // Fixed height for the subcategory container |
|
marginTop: 15, |
|
}, |
|
subcategoryScroll: { |
|
flex: 1, |
|
}, |
|
subcategoryContent: { |
|
paddingHorizontal: 15, |
|
}, |
|
subcategoryItem: { |
|
alignItems: "center", |
|
marginRight: 15, |
|
width: 60, |
|
}, |
|
subcategoryImagePlaceholder: { |
|
width: 60, |
|
height: 60, |
|
borderRadius: 30, |
|
backgroundColor: "#f5f5f5", |
|
justifyContent: "center", |
|
alignItems: "center", |
|
marginBottom: 8, |
|
}, |
|
subcategoryText: { |
|
fontSize: fontSize(12), |
|
color: "#333", |
|
textAlign: "center", |
|
fontFamily: "Alexandria", |
|
}, |
|
productContainer: { |
|
flex: 1, |
|
backgroundColor: "#fff", |
|
}, |
|
productCardList: { |
|
padding: 15, |
|
paddingTop: 0, |
|
}, |
|
productCardGroup: { |
|
justifyContent: "space-between", |
|
marginBottom: 15, |
|
paddingHorizontal: 15, |
|
}, |
|
beautyProductCard1: { |
|
width: "48%", |
|
}, |
|
beautyCardContainer1: { |
|
flexDirection: "column", |
|
alignItems: "flex-end", |
|
justifyContent: "center", |
|
width: "100%", |
|
height: 160, |
|
backgroundColor: "transparent", |
|
borderRadius: 10, |
|
overflow: "hidden", |
|
position: "relative", |
|
}, |
|
vipButtonContainer: { |
|
position: "absolute", |
|
top: 0, |
|
right: 0, |
|
zIndex: 2, |
|
}, |
|
vipButton: { |
|
width: widthUtils(30, 60).width, |
|
height: widthUtils(30, 60).height, |
|
justifyContent: "center", |
|
alignItems: "center", |
|
backgroundColor: "#3b3b3b", |
|
borderRadius: 10, |
|
flexDirection: "row", |
|
}, |
|
vipButtonText: { |
|
fontStyle: "italic", |
|
fontWeight: "900", |
|
fontSize: fontSize(18), |
|
color: "#f1c355", |
|
}, |
|
vipLabelBold: { |
|
fontStyle: "italic", |
|
fontWeight: "900", |
|
fontSize: fontSize(18), |
|
color: "#f1c355", |
|
}, |
|
beautyProductCard: { |
|
marginTop: 9, |
|
}, |
|
beautyProductTitle: { |
|
fontSize: fontSize(14), |
|
fontWeight: "600", |
|
color: "black", |
|
lineHeight: 18, |
|
}, |
|
beautyProductInfoRow: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
}, |
|
flexRowCentered: {}, |
|
priceContainer: { |
|
flexDirection: "row", |
|
}, |
|
highlightedText: { |
|
fontWeight: "700", |
|
fontSize: fontSize(24), |
|
color: "#ff5100", |
|
marginLeft: 2, |
|
}, |
|
highlightedText1: { |
|
fontWeight: "700", |
|
fontSize: fontSize(14), |
|
color: "#ff5100", |
|
}, |
|
priceContainer1: {}, |
|
priceLabel1: { |
|
fontSize: fontSize(12), |
|
fontWeight: "600", |
|
color: "#9a9a9a", |
|
textDecorationLine: "line-through", |
|
}, |
|
beautySalesInfo: { |
|
marginTop: 6.75, |
|
fontSize: fontSize(14), |
|
fontWeight: "600", |
|
color: "#7c7c7c", |
|
}, |
|
indicatorContainer: { |
|
flexDirection: "row", |
|
justifyContent: "center", |
|
alignItems: "center", |
|
position: "absolute", |
|
bottom: 10, |
|
left: 0, |
|
right: 0, |
|
}, |
|
indicator: { |
|
marginHorizontal: 4, |
|
borderRadius: 3, |
|
}, |
|
activeIndicator: { |
|
width: 14, |
|
height: 6, |
|
backgroundColor: "#fff", |
|
}, |
|
inactiveIndicator: { |
|
width: 6, |
|
height: 6, |
|
backgroundColor: "rgba(255, 255, 255, 0.5)", |
|
}, |
|
// 骨架屏样式 |
|
skeletonContainer: { |
|
paddingHorizontal: 0, |
|
paddingTop: 0, |
|
}, |
|
skeletonImage: { |
|
width: "100%", |
|
paddingBottom: "100%", |
|
borderRadius: 10, |
|
backgroundColor: "#e1e1e1", |
|
overflow: "hidden", |
|
position: "relative", |
|
}, |
|
skeletonTitle: { |
|
height: 16, |
|
borderRadius: 4, |
|
marginTop: 8, |
|
marginBottom: 4, |
|
width: "100%", |
|
backgroundColor: "#e1e1e1", |
|
overflow: "hidden", |
|
position: "relative", |
|
}, |
|
skeletonPrice: { |
|
height: 24, |
|
width: 80, |
|
borderRadius: 4, |
|
marginTop: 8, |
|
backgroundColor: "#e1e1e1", |
|
overflow: "hidden", |
|
position: "relative", |
|
}, |
|
skeletonSales: { |
|
height: 14, |
|
width: "40%", |
|
borderRadius: 4, |
|
marginTop: 8, |
|
backgroundColor: "#e1e1e1", |
|
overflow: "hidden", |
|
position: "relative", |
|
}, |
|
shimmer: { |
|
width: "30%", |
|
height: "100%", |
|
backgroundColor: "rgba(255, 255, 255, 0.3)", |
|
position: "absolute", |
|
top: 0, |
|
left: 0, |
|
}, |
|
imagePlaceholder: { |
|
backgroundColor: "#EAEAEA", |
|
justifyContent: "center", |
|
alignItems: "center", |
|
borderRadius: 8, |
|
}, |
|
productImage: { |
|
width: "100%", |
|
height: "100%", |
|
borderRadius: 10, |
|
}, |
|
// Image Picker Modal Styles |
|
imagePickerOverlay: { |
|
flex: 1, |
|
backgroundColor: "rgba(0, 0, 0, 0.5)", |
|
justifyContent: "flex-end", |
|
}, |
|
imagePickerContent: { |
|
backgroundColor: "#fff", |
|
borderTopLeftRadius: 20, |
|
borderTopRightRadius: 20, |
|
paddingTop: 20, |
|
}, |
|
imagePickerOption: { |
|
flexDirection: "row", |
|
alignItems: "center", |
|
paddingVertical: 16, |
|
paddingHorizontal: 20, |
|
}, |
|
imagePickerText: { |
|
fontSize: fontSize(16), |
|
marginLeft: 12, |
|
color: "#333", |
|
}, |
|
imagePickerDivider: { |
|
height: 1, |
|
backgroundColor: "#f0f0f0", |
|
marginHorizontal: 20, |
|
}, |
|
imagePickerCancelButton: { |
|
alignItems: "center", |
|
paddingVertical: 16, |
|
marginTop: 8, |
|
borderTopWidth: 1, |
|
borderTopColor: "#f0f0f0", |
|
}, |
|
imagePickerCancelText: { |
|
fontSize: fontSize(16), |
|
color: "#999", |
|
}, |
|
}); |