|
|
@ -43,6 +43,7 @@ import * as ImagePicker from "expo-image-picker"; |
|
|
|
import * as FileSystem from "expo-file-system"; |
|
|
|
import * as FileSystem from "expo-file-system"; |
|
|
|
import { useGlobalStore } from "../store/useGlobalStore"; |
|
|
|
import { useGlobalStore } from "../store/useGlobalStore"; |
|
|
|
import { getCurrentLanguage } from '../i18n'; |
|
|
|
import { getCurrentLanguage } from '../i18n'; |
|
|
|
|
|
|
|
import { eventBus } from '../utils/eventBus'; |
|
|
|
// 为图标定义类型
|
|
|
|
// 为图标定义类型
|
|
|
|
type IconProps = { |
|
|
|
type IconProps = { |
|
|
|
name: string; |
|
|
|
name: string; |
|
|
@ -260,7 +261,6 @@ type StylesType = { |
|
|
|
productCardList: ViewStyle; |
|
|
|
productCardList: ViewStyle; |
|
|
|
productCardGroup: ViewStyle; |
|
|
|
productCardGroup: ViewStyle; |
|
|
|
beautyProductCard1: ViewStyle; |
|
|
|
beautyProductCard1: ViewStyle; |
|
|
|
productCardGroup1: ViewStyle; |
|
|
|
|
|
|
|
beautyCardContainer1: ViewStyle; |
|
|
|
beautyCardContainer1: ViewStyle; |
|
|
|
vipButtonContainer: ViewStyle; |
|
|
|
vipButtonContainer: ViewStyle; |
|
|
|
vipButton: ViewStyle; |
|
|
|
vipButton: ViewStyle; |
|
|
@ -296,20 +296,14 @@ type StylesType = { |
|
|
|
imagePickerCancelButton: ViewStyle; |
|
|
|
imagePickerCancelButton: ViewStyle; |
|
|
|
imagePickerCancelText: TextStyle; |
|
|
|
imagePickerCancelText: TextStyle; |
|
|
|
}; |
|
|
|
}; |
|
|
|
export const HomeScreen = () => { |
|
|
|
// 轮播图组件 - 独立提取,避免重复渲染
|
|
|
|
const [activeIndex, setActiveIndex] = useState(0); |
|
|
|
const CarouselBanner = React.memo(({ onCameraPress }: { onCameraPress: () => void }) => { |
|
|
|
const screenWidth = Dimensions.get("window").width; |
|
|
|
const screenWidth = Dimensions.get("window").width; |
|
|
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
|
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
|
|
const { t } = useTranslation(); |
|
|
|
const { t } = useTranslation(); |
|
|
|
const [showCategoryModal, setShowCategoryModal] = useState(false); |
|
|
|
const [currentIndex, setCurrentIndex] = useState(0); |
|
|
|
const [showImagePickerModal, setShowImagePickerModal] = useState(false); |
|
|
|
|
|
|
|
const [selectedCategory, setSelectedCategory] = useState("Bijoux"); |
|
|
|
const data = useMemo(() => [ |
|
|
|
const [selectedHorizontalCategory, setSelectedHorizontalCategory] = useState("Tous"); |
|
|
|
|
|
|
|
const userStore = useUserStore(); |
|
|
|
|
|
|
|
const { country, currency } = useGlobalStore(); |
|
|
|
|
|
|
|
const flatListRef = useRef<FlatList>(null); |
|
|
|
|
|
|
|
const horizontalScrollRef = useRef<ScrollView>(null); |
|
|
|
|
|
|
|
const data = [ |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
imgUrl: require("../../assets/img/banner en (5)_compressed.png"), |
|
|
|
imgUrl: require("../../assets/img/banner en (5)_compressed.png"), |
|
|
|
add: "TikTokScreen", |
|
|
|
add: "TikTokScreen", |
|
|
@ -322,7 +316,106 @@ export const HomeScreen = () => { |
|
|
|
imgUrl: require("../../assets/img/banner en (4)_compressed.png"), |
|
|
|
imgUrl: require("../../assets/img/banner en (4)_compressed.png"), |
|
|
|
add: "CompanyScreen", |
|
|
|
add: "CompanyScreen", |
|
|
|
}, |
|
|
|
}, |
|
|
|
]; |
|
|
|
], []); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const navigateToSearch = useCallback(() => { |
|
|
|
|
|
|
|
InteractionManager.runAfterInteractions(() => { |
|
|
|
|
|
|
|
navigation.navigate("Search"); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}, [navigation]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleBannerPress = useCallback((screenName: string) => { |
|
|
|
|
|
|
|
navigation.navigate(screenName); |
|
|
|
|
|
|
|
}, [navigation]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const onSnapToItem = useCallback((index: number) => { |
|
|
|
|
|
|
|
setCurrentIndex(index); |
|
|
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
<View style={styles.swiperContainer}> |
|
|
|
|
|
|
|
<Carousel |
|
|
|
|
|
|
|
loop |
|
|
|
|
|
|
|
width={screenWidth} |
|
|
|
|
|
|
|
data={data} |
|
|
|
|
|
|
|
height={widthUtils(286, 286).height} |
|
|
|
|
|
|
|
modeConfig={{ |
|
|
|
|
|
|
|
parallaxScrollingScale: 0.9, |
|
|
|
|
|
|
|
parallaxScrollingOffset: 50, |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
onSnapToItem={onSnapToItem} |
|
|
|
|
|
|
|
renderItem={({ item }) => ( |
|
|
|
|
|
|
|
<TouchableOpacity |
|
|
|
|
|
|
|
onPress={() => handleBannerPress(item.add)} |
|
|
|
|
|
|
|
key={item.imgUrl} |
|
|
|
|
|
|
|
activeOpacity={1} |
|
|
|
|
|
|
|
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, |
|
|
|
|
|
|
|
index === currentIndex ? 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={onCameraPress} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<IconComponent name="camera-outline" size={24} color="#333" /> |
|
|
|
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, (prevProps, nextProps) => { |
|
|
|
|
|
|
|
// 自定义比较函数,只有当onCameraPress真正改变时才重新渲染
|
|
|
|
|
|
|
|
return prevProps.onCameraPress === nextProps.onCameraPress; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
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 userStore = useUserStore(); |
|
|
|
|
|
|
|
const { country, currency } = useGlobalStore(); |
|
|
|
|
|
|
|
const flatListRef = useRef<FlatList>(null); |
|
|
|
|
|
|
|
const horizontalScrollRef = useRef<ScrollView>(null); |
|
|
|
const [galleryUsed, setGalleryUsed] = useState(false); |
|
|
|
const [galleryUsed, setGalleryUsed] = useState(false); |
|
|
|
const [hotTerms, setHotTerms] = useState<string[]>([]); |
|
|
|
const [hotTerms, setHotTerms] = useState<string[]>([]); |
|
|
|
const [isLoadingHotTerms, setIsLoadingHotTerms] = useState(false); |
|
|
|
const [isLoadingHotTerms, setIsLoadingHotTerms] = useState(false); |
|
|
@ -381,6 +474,94 @@ export const HomeScreen = () => { |
|
|
|
initApp(); |
|
|
|
initApp(); |
|
|
|
}, []); |
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听设置变更事件,重新加载产品数据
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
const handleRefreshSetting = async () => { |
|
|
|
|
|
|
|
console.log("接收到refreshSetting事件,重新加载产品数据"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// 重新获取热门关键词
|
|
|
|
|
|
|
|
const response = await productApi.getHotTerms(); |
|
|
|
|
|
|
|
const terms = response.terms || []; |
|
|
|
|
|
|
|
setHotTerms(terms); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新的随机关键词重新加载数据
|
|
|
|
|
|
|
|
if (terms.length > 0) { |
|
|
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * terms.length); |
|
|
|
|
|
|
|
const randomKeyword = terms[randomIndex]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新参数
|
|
|
|
|
|
|
|
setParams(prev => ({ |
|
|
|
|
|
|
|
...prev, |
|
|
|
|
|
|
|
keyword: randomKeyword, |
|
|
|
|
|
|
|
language: getCurrentLanguage(), // 更新语言参数
|
|
|
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 重置状态并重新加载
|
|
|
|
|
|
|
|
setLoading(true); |
|
|
|
|
|
|
|
setProducts([]); |
|
|
|
|
|
|
|
setCurrentPage(1); |
|
|
|
|
|
|
|
setHasMore(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 简化的产品加载逻辑
|
|
|
|
|
|
|
|
const initialParams = { |
|
|
|
|
|
|
|
keyword: randomKeyword, |
|
|
|
|
|
|
|
sort_order: "desc", |
|
|
|
|
|
|
|
sort_by: "default", |
|
|
|
|
|
|
|
language: getCurrentLanguage(), |
|
|
|
|
|
|
|
page: 1, |
|
|
|
|
|
|
|
page_size: 10, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const firstPageRes = await productApi.getSearchProducts(initialParams); |
|
|
|
|
|
|
|
setProducts(firstPageRes.products); |
|
|
|
|
|
|
|
setTotalItems(firstPageRes.total || 0); |
|
|
|
|
|
|
|
setCurrentPage(1); |
|
|
|
|
|
|
|
setHasMore(firstPageRes.products.length < (firstPageRes.total || 0)); |
|
|
|
|
|
|
|
setLoading(false); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// 如果没有热门关键词,使用默认关键词
|
|
|
|
|
|
|
|
setParams(prev => ({ |
|
|
|
|
|
|
|
...prev, |
|
|
|
|
|
|
|
language: getCurrentLanguage(), // 更新语言参数
|
|
|
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setLoading(true); |
|
|
|
|
|
|
|
setProducts([]); |
|
|
|
|
|
|
|
setCurrentPage(1); |
|
|
|
|
|
|
|
setHasMore(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const initialParams = { |
|
|
|
|
|
|
|
keyword: "pen", |
|
|
|
|
|
|
|
sort_order: "desc", |
|
|
|
|
|
|
|
sort_by: "default", |
|
|
|
|
|
|
|
language: getCurrentLanguage(), |
|
|
|
|
|
|
|
page: 1, |
|
|
|
|
|
|
|
page_size: 10, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const firstPageRes = await productApi.getSearchProducts(initialParams); |
|
|
|
|
|
|
|
setProducts(firstPageRes.products); |
|
|
|
|
|
|
|
setTotalItems(firstPageRes.total || 0); |
|
|
|
|
|
|
|
setCurrentPage(1); |
|
|
|
|
|
|
|
setHasMore(firstPageRes.products.length < (firstPageRes.total || 0)); |
|
|
|
|
|
|
|
setLoading(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("重新加载产品数据失败:", error); |
|
|
|
|
|
|
|
setLoading(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加事件监听器
|
|
|
|
|
|
|
|
eventBus.on("refreshSetting", handleRefreshSetting); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 清理函数,移除事件监听器
|
|
|
|
|
|
|
|
return () => { |
|
|
|
|
|
|
|
eventBus.off("refreshSetting", handleRefreshSetting); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
}, []); // 空依赖数组,因为我们只需要在组件挂载时添加监听器
|
|
|
|
|
|
|
|
|
|
|
|
// 获取随机关键词
|
|
|
|
// 获取随机关键词
|
|
|
|
const getRandomKeyword = useCallback(() => { |
|
|
|
const getRandomKeyword = useCallback(() => { |
|
|
|
if (hotTerms.length === 0) return "pen"; |
|
|
|
if (hotTerms.length === 0) return "pen"; |
|
|
@ -746,9 +927,8 @@ export const HomeScreen = () => { |
|
|
|
cleanupImagePickerCache(); |
|
|
|
cleanupImagePickerCache(); |
|
|
|
Alert.alert("已重置", "现在您可以使用相机功能了"); |
|
|
|
Alert.alert("已重置", "现在您可以使用相机功能了"); |
|
|
|
}, []); |
|
|
|
}, []); |
|
|
|
// 优化轮播图切换回调
|
|
|
|
const handleCameraPress = useCallback(() => { |
|
|
|
const handleCarouselSnap = useCallback((index: number) => { |
|
|
|
setShowImagePickerModal(true); |
|
|
|
setActiveIndex(index); |
|
|
|
|
|
|
|
}, []); |
|
|
|
}, []); |
|
|
|
const renderItem = ({ item, index }: { item: Product; index: number }) => { |
|
|
|
const renderItem = ({ item, index }: { item: Product; index: number }) => { |
|
|
|
if (index >= products.length && index < products.length + loadingPlaceholders) { |
|
|
|
if (index >= products.length && index < products.length + loadingPlaceholders) { |
|
|
@ -756,70 +936,8 @@ export const HomeScreen = () => { |
|
|
|
} |
|
|
|
} |
|
|
|
return renderProductItem({ item }); |
|
|
|
return renderProductItem({ item }); |
|
|
|
}; |
|
|
|
}; |
|
|
|
const renderHeader = useCallback(() => ( |
|
|
|
const renderHeader = () => ( |
|
|
|
<> |
|
|
|
<> |
|
|
|
<View style={styles.swiperContainer}> |
|
|
|
|
|
|
|
<Carousel |
|
|
|
|
|
|
|
key="carousel-header" |
|
|
|
|
|
|
|
width={screenWidth} |
|
|
|
|
|
|
|
data={data} |
|
|
|
|
|
|
|
height={widthUtils(286, 286).height} |
|
|
|
|
|
|
|
modeConfig={{ |
|
|
|
|
|
|
|
parallaxScrollingScale: 0, |
|
|
|
|
|
|
|
parallaxScrollingOffset: 0, |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
onSnapToItem={handleCarouselSnap} |
|
|
|
|
|
|
|
renderItem={({ item }) => ( |
|
|
|
|
|
|
|
<TouchableOpacity |
|
|
|
|
|
|
|
onPress={() => navigation.navigate(item.add)} |
|
|
|
|
|
|
|
activeOpacity={1} |
|
|
|
|
|
|
|
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, |
|
|
|
|
|
|
|
index === activeIndex ? 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.bannerContainer}> |
|
|
|
<View style={styles.leftContainer}> |
|
|
|
<View style={styles.leftContainer}> |
|
|
|
<TouchableOpacity |
|
|
|
<TouchableOpacity |
|
|
@ -924,20 +1042,14 @@ export const HomeScreen = () => { |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
) : null} |
|
|
|
) : null} |
|
|
|
</> |
|
|
|
</> |
|
|
|
), [activeIndex, selectedHorizontalCategory]); |
|
|
|
); |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<SafeAreaView style={styles.safeArea}> |
|
|
|
<SafeAreaView style={styles.safeArea}> |
|
|
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
|
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
|
|
<View style={styles.safeAreaContent}> |
|
|
|
<View style={styles.safeAreaContent}> |
|
|
|
<View style={styles.container}> |
|
|
|
<View style={styles.container}> |
|
|
|
{loading ? ( |
|
|
|
{loading ? ( |
|
|
|
<View> |
|
|
|
|
|
|
|
{renderHeader()} |
|
|
|
|
|
|
|
{renderSkeletonGrid()} |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<ScrollView |
|
|
|
<ScrollView |
|
|
|
showsVerticalScrollIndicator={false} |
|
|
|
|
|
|
|
refreshControl={ |
|
|
|
refreshControl={ |
|
|
|
<RefreshControl |
|
|
|
<RefreshControl |
|
|
|
refreshing={refreshing} |
|
|
|
refreshing={refreshing} |
|
|
@ -947,236 +1059,51 @@ export const HomeScreen = () => { |
|
|
|
progressBackgroundColor="transparent" |
|
|
|
progressBackgroundColor="transparent" |
|
|
|
/> |
|
|
|
/> |
|
|
|
} |
|
|
|
} |
|
|
|
onScroll={({ nativeEvent }) => { |
|
|
|
|
|
|
|
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent; |
|
|
|
|
|
|
|
const paddingToBottom = 20; |
|
|
|
|
|
|
|
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom) { |
|
|
|
|
|
|
|
handleLoadMore(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
scrollEventThrottle={400} |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
{/* 轮播图区域 */} |
|
|
|
<CarouselBanner onCameraPress={handleCameraPress} /> |
|
|
|
<View style={styles.swiperContainer}> |
|
|
|
{renderHeader()} |
|
|
|
<Carousel |
|
|
|
{renderSkeletonGrid()} |
|
|
|
key="carousel-header" |
|
|
|
</ScrollView> |
|
|
|
width={screenWidth} |
|
|
|
) : ( |
|
|
|
data={data} |
|
|
|
<FlatList |
|
|
|
height={widthUtils(286, 286).height} |
|
|
|
ref={flatListRef} |
|
|
|
modeConfig={{ |
|
|
|
data={[...products, ...Array(loadingPlaceholders).fill(null)]} |
|
|
|
parallaxScrollingScale: 0, |
|
|
|
numColumns={2} |
|
|
|
parallaxScrollingOffset: 0, |
|
|
|
showsVerticalScrollIndicator={false} |
|
|
|
}} |
|
|
|
columnWrapperStyle={styles.productCardGroup} |
|
|
|
onSnapToItem={handleCarouselSnap} |
|
|
|
renderItem={renderItem} |
|
|
|
renderItem={({ item }) => ( |
|
|
|
keyExtractor={(item, index) => |
|
|
|
<TouchableOpacity |
|
|
|
(item?.offer_id ? `${item.offer_id}-${currentPage}-${index}` : `placeholder-${currentPage}-${index}`) |
|
|
|
onPress={() => navigation.navigate(item.add)} |
|
|
|
} |
|
|
|
activeOpacity={1} |
|
|
|
contentContainerStyle={{ |
|
|
|
key={item.imgUrl} |
|
|
|
paddingBottom: 15, |
|
|
|
style={{ |
|
|
|
backgroundColor: "transparent", |
|
|
|
flex: 1, |
|
|
|
}} |
|
|
|
justifyContent: "center", |
|
|
|
ListHeaderComponent={() => ( |
|
|
|
alignItems: "center", |
|
|
|
<> |
|
|
|
backgroundColor: "#f2f2f2", |
|
|
|
<CarouselBanner onCameraPress={handleCameraPress} /> |
|
|
|
borderRadius: 0, |
|
|
|
{renderHeader()} |
|
|
|
overflow: "hidden", |
|
|
|
</> |
|
|
|
}} |
|
|
|
)} |
|
|
|
> |
|
|
|
onEndReached={handleLoadMore} |
|
|
|
<Image |
|
|
|
onEndReachedThreshold={3} |
|
|
|
source={item.imgUrl} |
|
|
|
ListFooterComponent={() => ( |
|
|
|
style={{ width: "100%", height: "100%" }} |
|
|
|
!hasMore && !loadingPlaceholders ? ( |
|
|
|
resizeMode="cover" |
|
|
|
|
|
|
|
defaultSource={require("../../assets/img/banner en (3).png")} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<View style={styles.indicatorContainer}> |
|
|
|
|
|
|
|
{data.map((_, index) => ( |
|
|
|
|
|
|
|
<View |
|
|
|
|
|
|
|
key={index} |
|
|
|
|
|
|
|
style={[ |
|
|
|
|
|
|
|
styles.indicator, |
|
|
|
|
|
|
|
index === activeIndex ? 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> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Banner区域 */} |
|
|
|
|
|
|
|
<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, |
|
|
|
|
|
|
|
]} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{t(`homePage.${category.toLowerCase()}`)} |
|
|
|
|
|
|
|
</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> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 子分类区域 */} |
|
|
|
|
|
|
|
{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} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 产品网格 */} |
|
|
|
|
|
|
|
<View style={styles.productContainer}> |
|
|
|
|
|
|
|
<View style={styles.productCardList}> |
|
|
|
|
|
|
|
{(() => { |
|
|
|
|
|
|
|
const allItems = [...products, ...Array(loadingPlaceholders).fill(null)]; |
|
|
|
|
|
|
|
const rows = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < allItems.length; i += 2) { |
|
|
|
|
|
|
|
const leftItem = allItems[i]; |
|
|
|
|
|
|
|
const rightItem = allItems[i + 1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rows.push( |
|
|
|
|
|
|
|
<View key={`row-${i}`} style={styles.productCardGroup1}> |
|
|
|
|
|
|
|
{/* 左侧商品 */} |
|
|
|
|
|
|
|
{leftItem ? ( |
|
|
|
|
|
|
|
i >= products.length ? ( |
|
|
|
|
|
|
|
<ProductSkeleton /> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
renderProductItem({ item: leftItem }) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
) : null} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 右侧商品 */} |
|
|
|
|
|
|
|
{rightItem ? ( |
|
|
|
|
|
|
|
i + 1 >= products.length ? ( |
|
|
|
|
|
|
|
<ProductSkeleton /> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
renderProductItem({ item: rightItem }) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<View style={{ width: "48%" }} /> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return rows; |
|
|
|
|
|
|
|
})()} |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 底部提示 */} |
|
|
|
|
|
|
|
{!hasMore && !loadingPlaceholders && ( |
|
|
|
|
|
|
|
<View style={{ padding: 10, alignItems: 'center' }}> |
|
|
|
<View style={{ padding: 10, alignItems: 'center' }}> |
|
|
|
<Text>没有更多数据了</Text> |
|
|
|
<Text>没有更多数据了</Text> |
|
|
|
</View> |
|
|
|
</View> |
|
|
|
)} |
|
|
|
) : null |
|
|
|
</View> |
|
|
|
)} |
|
|
|
</ScrollView> |
|
|
|
refreshControl={ |
|
|
|
|
|
|
|
<RefreshControl |
|
|
|
|
|
|
|
refreshing={refreshing} |
|
|
|
|
|
|
|
onRefresh={handleRefresh} |
|
|
|
|
|
|
|
colors={["#ff5100"]} |
|
|
|
|
|
|
|
tintColor="#ff5100" |
|
|
|
|
|
|
|
progressBackgroundColor="transparent" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/> |
|
|
|
)} |
|
|
|
)} |
|
|
|
<Modal |
|
|
|
<Modal |
|
|
|
visible={showCategoryModal} |
|
|
|
visible={showCategoryModal} |
|
|
@ -1566,16 +1493,10 @@ const styles = StyleSheet.create<StylesType>({ |
|
|
|
paddingTop: 0, |
|
|
|
paddingTop: 0, |
|
|
|
}, |
|
|
|
}, |
|
|
|
productCardGroup: { |
|
|
|
productCardGroup: { |
|
|
|
flexDirection: "row", |
|
|
|
|
|
|
|
justifyContent: "space-between", |
|
|
|
justifyContent: "space-between", |
|
|
|
marginBottom: 15, |
|
|
|
marginBottom: 15, |
|
|
|
paddingHorizontal: 15, |
|
|
|
paddingHorizontal: 15, |
|
|
|
}, |
|
|
|
}, |
|
|
|
productCardGroup1:{ |
|
|
|
|
|
|
|
flexDirection: "row", |
|
|
|
|
|
|
|
justifyContent: "space-between", |
|
|
|
|
|
|
|
marginBottom: 15, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
beautyProductCard1: { |
|
|
|
beautyProductCard1: { |
|
|
|
width: "48%", |
|
|
|
width: "48%", |
|
|
|
}, |
|
|
|
}, |
|
|
|