Browse Source

切换货币首页加载

main
Mac 2 weeks ago
parent
commit
0ad46eef3a
  1. 38
      app.json
  2. 1
      app/locales/en/translation.json
  3. 4
      app/locales/fr/translation.json
  4. 1
      app/screens/BalanceScreen/PhoneNumberInputModal.tsx
  5. 6
      app/screens/CartScreen.tsx
  6. 557
      app/screens/HomeScreen.tsx
  7. 210
      app/screens/ProductCard.tsx
  8. 82
      app/screens/loginList/index.tsx
  9. 1
      app/screens/productStatus/OrderDatails.tsx
  10. 7
      app/screens/setting/CountrySetting.tsx

38
app.json

@ -7,8 +7,8 @@
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"userInterfaceStyle": "light", "userInterfaceStyle": "light",
"newArchEnabled": true, "newArchEnabled": true,
"scheme": "myapp", "scheme": "com.brainnel.app",
"owner":"brainnel", "owner": "brainnel",
"deepLinking": true, "deepLinking": true,
"splash": { "splash": {
"image": "./assets/splash-icon.png", "image": "./assets/splash-icon.png",
@ -22,10 +22,19 @@
"CFBundleURLTypes": [ "CFBundleURLTypes": [
{ {
"CFBundleURLSchemes": [ "CFBundleURLSchemes": [
"myapp" "com.brainnel.app",
"myapp",
"fb164237502913710"
] ]
} }
] ]
},
"config": {
"facebook": {
"scheme": "fb164237502913710",
"appId": "164237502913710",
"displayName": "newBrainnelApp"
}
} }
}, },
"android": { "android": {
@ -39,10 +48,20 @@
}, },
"googleServicesFile": "./google-services.json", "googleServicesFile": "./google-services.json",
"package": "com.brainnel.app", "package": "com.brainnel.app",
"config": {
"facebook": {
"scheme": "fb164237502913710",
"appId": "164237502913710",
"displayName": "newBrainnelApp"
}
},
"intentFilters": [ "intentFilters": [
{ {
"action": "VIEW", "action": "VIEW",
"data": [ "data": [
{
"scheme": "com.brainnel.app"
},
{ {
"scheme": "myapp" "scheme": "myapp"
} }
@ -68,7 +87,16 @@
} }
} }
], ],
"expo-localization" "expo-localization",
[
"react-native-fbsdk-next",
{
"appID": "995035562798467",
"displayName": "newBrainnelApp",
"scheme": "fb995035562798467",
"clientToken": "2005c337398b1c286e2a189e4e4417ac"
}
]
], ],
"extra": { "extra": {
"eas": { "eas": {
@ -76,4 +104,4 @@
} }
} }
} }
} }

1
app/locales/en/translation.json

@ -280,6 +280,7 @@
"order.select_payment": "Select Payment Method", "order.select_payment": "Select Payment Method",
"order.select_currency": "Select Currency", "order.select_currency": "Select Currency",
"order.confirm_payment": "Confirm Payment", "order.confirm_payment": "Confirm Payment",
"order.add_cart_success": "Add to cart successfully",
"member.introduction": "Member Benefits", "member.introduction": "Member Benefits",
"member.client_service": "Client Service", "member.client_service": "Client Service",
"member.vip.spend_more_text": "Spend more to upgrade your VIP level", "member.vip.spend_more_text": "Spend more to upgrade your VIP level",

4
app/locales/fr/translation.json

@ -166,7 +166,7 @@
"order.error.payment_update": "Échec de la mise à jour du mode de paiement", "order.error.payment_update": "Échec de la mise à jour du mode de paiement",
"order.details": "Détails de la commande", "order.details": "Détails de la commande",
"order.information": "Informations de commande", "order.information": "Informations de commande",
"order.error.add_cart": "Échec de l'ajout au panier",
"order.id": "ID de commande", "order.id": "ID de commande",
"order.create_time": "Date de commande", "order.create_time": "Date de commande",
"order.shipping_type": "Méthode d'expédition", "order.shipping_type": "Méthode d'expédition",
@ -211,6 +211,8 @@
"order.select_payment": "Moyens de paiement", "order.select_payment": "Moyens de paiement",
"order.select_currency": "Sélectionner la devise", "order.select_currency": "Sélectionner la devise",
"order.confirm_payment": "Confirmer le paiement", "order.confirm_payment": "Confirmer le paiement",
"order.add_cart_success": "Ajout au panier réussi",
"order.error.add_cart": "Échec de l'ajout au panier",
"member.introduction": "Avantages pour les membres", "member.introduction": "Avantages pour les membres",
"member.client_service": "Service Client", "member.client_service": "Service Client",
"member.vip.spend_more_text": "Dépensez plus pour améliorer votre niveau VIP", "member.vip.spend_more_text": "Dépensez plus pour améliorer votre niveau VIP",

1
app/screens/BalanceScreen/PhoneNumberInputModal.tsx

@ -199,7 +199,6 @@ const PhoneNumberInputModal = ({
<Text style={styles.countryCodeText}> <Text style={styles.countryCodeText}>
{displayCountryCode || '+243'} {displayCountryCode || '+243'}
</Text> </Text>
<Text style={styles.countryCodeArrow}></Text>
</TouchableOpacity> </TouchableOpacity>
<TextInput <TextInput
style={styles.phoneInput} style={styles.phoneInput}

6
app/screens/CartScreen.tsx

@ -663,6 +663,8 @@ export const CartScreen = () => {
<View style={styles.safeAreaContent}> <View style={styles.safeAreaContent}>
<View style={styles.container}> <View style={styles.container}>
<ScrollView <ScrollView
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
style={styles.scrollContainer} style={styles.scrollContainer}
contentContainerStyle={styles.scrollContentContainer} contentContainerStyle={styles.scrollContentContainer}
> >
@ -817,7 +819,7 @@ export const CartScreen = () => {
style={styles.VipImg} style={styles.VipImg}
/> />
<Text style={styles.discountPercentageTextStyle}> <Text style={styles.discountPercentageTextStyle}>
{((1 - vip_discount) * 100).toFixed(0)}% -{((1 - vip_discount) * 100).toFixed(0)}%
</Text> </Text>
</View> </View>
</View> </View>
@ -1327,8 +1329,8 @@ const styles = StyleSheet.create({
orderQuantityContainer: { orderQuantityContainer: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
marginLeft: 15,
alignSelf: "flex-end", alignSelf: "flex-end",
marginRight: 10,
}, },
borderBoxDivider1: { borderBoxDivider1: {
width: widthUtils(9, 9).width, width: widthUtils(9, 9).width,

557
app/screens/HomeScreen.tsx

@ -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%",
}, },

210
app/screens/ProductCard.tsx

@ -55,6 +55,18 @@ const ProductCard: React.FC<ProductCardProps> = ({
const [images, setImages] = useState<string[]>([]); const [images, setImages] = useState<string[]>([]);
const [currentImageIndex, setCurrentImageIndex] = useState(0); const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [imageViewerVisible, setImageViewerVisible] = useState(false); const [imageViewerVisible, setImageViewerVisible] = useState(false);
// 添加数量输入弹窗相关状态
const [quantityInputVisible, setQuantityInputVisible] = useState(false);
const [editingItem, setEditingItem] = useState<{
type: 'hasImg' | 'noImg';
index: number;
currentQuantity: number;
maxQuantity: number;
attributeValue?: string;
} | null>(null);
const [quantityInput, setQuantityInput] = useState("");
const { const {
user: { user_id, vip_level, currency }, user: { user_id, vip_level, currency },
} = useUserStore(); } = useUserStore();
@ -100,6 +112,56 @@ const ProductCard: React.FC<ProductCardProps> = ({
setAlertModalVisible(true); setAlertModalVisible(true);
}; };
// 处理数量输入弹窗确认
const handleQuantityInputConfirm = () => {
if (!editingItem) return;
const newQuantity = parseInt(quantityInput);
if (isNaN(newQuantity) || newQuantity < 0) {
showCustomAlert("输入错误", "请输入有效的数量");
return;
}
if (newQuantity > editingItem.maxQuantity) {
showCustomAlert("库存不足", `最大可选数量为 ${editingItem.maxQuantity}`);
return;
}
// 根据类型调用相应的处理函数
if (editingItem.type === 'hasImg') {
handleSizeSelect(
editingItem.attributeValue || "",
newQuantity.toString(),
editingItem.index,
editingItem.maxQuantity
);
} else {
handleNoImgSizeSelect(
editingItem.attributeValue || "",
newQuantity.toString(),
editingItem.index,
editingItem.maxQuantity
);
}
setQuantityInputVisible(false);
setEditingItem(null);
setQuantityInput("");
};
// 处理点击数量显示
const handleQuantityPress = (
type: 'hasImg' | 'noImg',
index: number,
currentQuantity: number,
maxQuantity: number,
attributeValue?: string
) => {
setEditingItem({ type, index, currentQuantity, maxQuantity, attributeValue });
setQuantityInput(currentQuantity.toString());
setQuantityInputVisible(true);
};
// 加入购物车 // 加入购物车
const addCartHandel = () => { const addCartHandel = () => {
if (!user_id) { if (!user_id) {
@ -421,19 +483,22 @@ const ProductCard: React.FC<ProductCardProps> = ({
> >
<Text>-</Text> <Text>-</Text>
</TouchableOpacity> </TouchableOpacity>
<TextInput <TouchableOpacity
style={styles.sizePriceBoxStepForwardInput} style={styles.sizePriceBoxStepForwardInput}
value={list?.size?.toString() ?? "0"} onPress={() =>
keyboardType="numeric" handleQuantityPress(
onChangeText={(text) => 'hasImg',
handleSizeSelect(
list?.attributes?.[0]?.value,
text,
index1, index1,
list?.amount_on_sale ?? 0 list?.size ?? 0,
list?.amount_on_sale ?? 0,
list?.attributes?.[0]?.value
) )
} }
/> >
<Text style={styles.quantityDisplayText}>
{list?.size?.toString() ?? "0"}
</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.sizePriceBoxStepForwardButton} style={styles.sizePriceBoxStepForwardButton}
onPress={() => onPress={() =>
@ -541,19 +606,22 @@ const ProductCard: React.FC<ProductCardProps> = ({
> >
<Text>-</Text> <Text>-</Text>
</TouchableOpacity> </TouchableOpacity>
<TextInput <TouchableOpacity
style={styles.sizePriceBoxStepForwardInput} style={styles.sizePriceBoxStepForwardInput}
value={list?.size?.toString() ?? "0"} onPress={() =>
keyboardType="numeric" handleQuantityPress(
onChangeText={(text) => 'hasImg',
handleSizeSelect(
list?.attributes?.[0]?.value,
text,
index1, index1,
list?.amount_on_sale ?? 0 list?.size ?? 0,
list?.amount_on_sale ?? 0,
list?.attributes?.[0]?.value
) )
} }
/> >
<Text style={styles.quantityDisplayText}>
{list?.size?.toString() ?? "0"}
</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.sizePriceBoxStepForwardButton} style={styles.sizePriceBoxStepForwardButton}
onPress={() => onPress={() =>
@ -625,19 +693,22 @@ const ProductCard: React.FC<ProductCardProps> = ({
> >
<Text>-</Text> <Text>-</Text>
</TouchableOpacity> </TouchableOpacity>
<TextInput <TouchableOpacity
style={styles.sizePriceBoxStepForwardInput} style={styles.sizePriceBoxStepForwardInput}
value={list?.size?.toString() ?? "0"} onPress={() =>
keyboardType="numeric" handleQuantityPress(
onChangeText={(text) => 'noImg',
handleNoImgSizeSelect(
list?.attributes?.[0]?.value,
text,
index1, index1,
list?.amount_on_sale ?? 0 list?.size ?? 0,
list?.amount_on_sale ?? 0,
list?.attributes?.[0]?.value
) )
} }
/> >
<Text style={styles.quantityDisplayText}>
{list?.size?.toString() ?? "0"}
</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.sizePriceBoxStepForwardButton} style={styles.sizePriceBoxStepForwardButton}
onPress={() => onPress={() =>
@ -726,19 +797,22 @@ const ProductCard: React.FC<ProductCardProps> = ({
> >
<Text>-</Text> <Text>-</Text>
</TouchableOpacity> </TouchableOpacity>
<TextInput <TouchableOpacity
style={styles.sizePriceBoxStepForwardInput} style={styles.sizePriceBoxStepForwardInput}
keyboardType="numeric" onPress={() =>
value={list?.size?.toString() ?? "0"} handleQuantityPress(
onChangeText={(text) => 'noImg',
handleNoImgSizeSelect(
list.attributes?.[0]?.value,
text,
index1, index1,
list?.amount_on_sale ?? 0 list?.size ?? 0,
list?.amount_on_sale ?? 0,
list?.attributes?.[0]?.value
) )
} }
/> >
<Text style={styles.quantityDisplayText}>
{list?.size?.toString() ?? "0"}
</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.sizePriceBoxStepForwardButton} style={styles.sizePriceBoxStepForwardButton}
onPress={() => onPress={() =>
@ -846,6 +920,48 @@ const ProductCard: React.FC<ProductCardProps> = ({
</View> </View>
</View> </View>
</Modal> </Modal>
{/* 数量输入弹窗 */}
<Modal
visible={quantityInputVisible}
transparent
animationType="fade"
onRequestClose={() => setQuantityInputVisible(false)}
>
<View style={styles.overlay}>
<View style={styles.popup}>
<Text style={styles.promptText}></Text>
<TextInput
style={styles.quantityInput}
value={quantityInput}
onChangeText={setQuantityInput}
keyboardType="number-pad"
autoFocus
/>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.cancelButton1, { width: "45%" }]}
onPress={() => {
setQuantityInputVisible(false);
setEditingItem(null);
setQuantityInput("");
}}
>
<Text style={styles.cancelText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.confirmButton,
{ width: "45%", backgroundColor: "#ff5100" },
]}
onPress={handleQuantityInputConfirm}
>
<Text style={styles.confirmText}></Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View> </View>
); );
}; };
@ -1104,10 +1220,11 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
}, },
sizePriceBoxStepForwardInput: { sizePriceBoxStepForwardInput: {
width: widthUtils(40, 30).width, width: widthUtils(60, 40).width,
height: widthUtils(40, 30).height, height: widthUtils(40, 30).height,
backgroundColor: "#fff", backgroundColor: "#fff",
textAlign: "center", justifyContent: "center",
alignItems: "center",
fontSize: fontSize(14), fontSize: fontSize(14),
borderWidth: 1, borderWidth: 1,
borderColor: "#f3f4f8", borderColor: "#f3f4f8",
@ -1325,6 +1442,23 @@ const styles = StyleSheet.create({
marginBottom: 20, marginBottom: 20,
paddingHorizontal: 20, paddingHorizontal: 20,
}, },
quantityDisplayText: {
fontSize: fontSize(16),
fontWeight: "700",
color: "#000",
fontFamily: "Segoe UI",
},
quantityInput: {
width: "100%",
height: 40,
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 5,
paddingHorizontal: 10,
fontSize: fontSize(16),
textAlign: "center",
fontFamily: "Segoe UI",
},
}); });
export default ProductCard; export default ProductCard;

82
app/screens/loginList/index.tsx

@ -10,6 +10,7 @@ import {
Image, Image,
Modal, Modal,
SafeAreaView, SafeAreaView,
Alert
} from "react-native"; } from "react-native";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
@ -27,6 +28,8 @@ import useUserStore from "../../store/user";
// statusCodes, // statusCodes,
// } from "@react-native-google-signin/google-signin"; // } from "@react-native-google-signin/google-signin";
// import { LoginManager, AccessToken, Settings } from "react-native-fbsdk-next";
const isDevelopment = __DEV__; // 开发模式检测 const isDevelopment = __DEV__; // 开发模式检测
// 移出条件块,始终尝试配置 Google 登录 // 移出条件块,始终尝试配置 Google 登录
@ -106,12 +109,10 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
// 处理谷歌登录 // 处理谷歌登录
const handleGoogleLogin = async () => { const handleGoogleLogin = async () => {
// try { // try {
// if (!GoogleSignin || typeof GoogleSignin.signIn !== "function") { // if (!GoogleSignin || typeof GoogleSignin.signIn !== "function") {
// console.log("Google Sign-in模块未正确初始化或配置失败"); // console.log("Google Sign-in模块未正确初始化或配置失败");
// return; // return;
// } // }
// await GoogleSignin.hasPlayServices(); // await GoogleSignin.hasPlayServices();
// const userInfo = await GoogleSignin.signIn(); // const userInfo = await GoogleSignin.signIn();
// console.log("Google 登录成功:", userInfo); // console.log("Google 登录成功:", userInfo);
@ -124,19 +125,16 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
// console.log("Google 登录失败:", err); // console.log("Google 登录失败:", err);
// navigation.navigate("Login"); // navigation.navigate("Login");
// } // }
// // 这里可以处理登录成功后的逻辑 // // 这里可以处理登录成功后的逻辑
// // 比如导航到主页面或保存用户信息 // // 比如导航到主页面或保存用户信息
// // navigation.navigate("MainTabs", { screen: "Home" }); // // navigation.navigate("MainTabs", { screen: "Home" });
// } catch (error: any) { // } catch (error: any) {
// console.log("Google 登录错误:", error); // console.log("Google 登录错误:", error);
// // 开发模式下的错误处理 // // 开发模式下的错误处理
// if (isDevelopment) { // if (isDevelopment) {
// console.log("开发模式:忽略Google登录错误,但已尝试真实登录"); // 修改日志,表明已尝试真实登录 // console.log("开发模式:忽略Google登录错误,但已尝试真实登录"); // 修改日志,表明已尝试真实登录
// return; // return;
// } // }
// if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) { // if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) {
// console.log("用户取消登录"); // console.log("用户取消登录");
// } else if (statusCodes && error.code === statusCodes.IN_PROGRESS) { // } else if (statusCodes && error.code === statusCodes.IN_PROGRESS) {
@ -152,10 +150,80 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
// } // }
// } // }
}; };
const [userInfo, setUserInfo] = useState<any>(null);
// useEffect(() => {
// // 确保在 App 启动时初始化 SDK。这通常在您的 App.js 的顶层完成。
// // 如果您在 app.json 中配置了 Facebook App ID,这里可以省略 Settings.setAppID 和 Settings.setDisplayName
// Settings.initializeSDK();
// // 在应用程序启动时检查是否已经登录(可选)
// AccessToken.getCurrentAccessToken().then(data => {
// if (data) {
// console.log("已登录 Facebook,Token:", data.accessToken);
// // 可以尝试获取用户信息
// // fetchFacebookProfile(data.accessToken);
// }
// });
// }, []);
// 辅助函数:获取 Facebook 用户资料 (可选,需要 'public_profile' 权限)
// const fetchFacebookProfile = async (token:string) => {
// try {
// const response = await fetch(`https://graph.facebook.com/me?fields=id,name,email&access_token=${token}`);
// const profile = await response.json();
// setUserInfo(profile);
// console.log('Facebook User Info:', profile);
// } catch (error) {
// console.error('获取 Facebook 用户资料错误:', error);
// Alert.alert("获取资料失败", "无法从 Facebook 获取用户详细资料,请检查网络或权限设置。");
// }
// };
// 处理Facebook登录 // 处理Facebook登录
const handleFacebookLogin = () => { const handleFacebookLogin = async () => {
// 处理Facebook登录 // try {
// // 可选: 先退出登录,确保每次都是全新登录 (主要用于测试)
// // await LoginManager.logOut();
// const result = await LoginManager.logInWithPermissions([
// "public_profile",
// "email",
// ]);
// if (result.isCancelled) {
// Alert.alert("登录取消", "用户取消了 Facebook 登录。");
// return;
// }
// const data = await AccessToken.getCurrentAccessToken();
// // 确保 accessToken 存在且为字符串
// if (!data || !data.accessToken) {
// Alert.alert("登录失败", "无法获取有效的 Facebook AccessToken。");
// return;
// }
// const tokenString = data.accessToken.toString();
// console.log("Facebook Access Token:", tokenString);
// // 直接获取 Facebook 用户信息并打印
// await fetchFacebookProfile(tokenString);
// // 移除之前的 Alert, 因为 fetchFacebookProfile 内部会处理打印和可能的错误提示
// // 如果 fetchFacebookProfile 成功,信息已在控制台,如果失败,它会 Alert
// // 可以选择在这里加一个通用成功提示,表明流程已执行
// Alert.alert("操作完成", "已尝试 Facebook 登录并获取用户信息,请查看控制台输出。");
// } catch (error: any) {
// console.error("Facebook 登录或获取资料时发生错误:", error);
// let errorMessage = "发生未知错误";
// if (error && typeof error.message === 'string') {
// errorMessage = error.message;
// }
// Alert.alert("登录错误", `Facebook 操作失败:${errorMessage}`);
// }
}; };
// 处理Apple登录 // 处理Apple登录

1
app/screens/productStatus/OrderDatails.tsx

@ -821,7 +821,6 @@ export const OrderDetails = () => {
t("order.status.waiting_payment"), t("order.status.waiting_payment"),
t("order.status.waiting_shipment"), t("order.status.waiting_shipment"),
t("order.status.in_transit"), t("order.status.in_transit"),
t("order.status.waiting_receipt"),
t("order.status.completed"), t("order.status.completed"),
]} ]}
/> />

7
app/screens/setting/CountrySetting.tsx

@ -98,6 +98,11 @@ export const CountrySetting = () => {
visibilityTime: 1000, visibilityTime: 1000,
}); });
// Emit event for HomeScreen to refresh if currency or language changed
if (changeType === "currency" || changeType === "language") {
eventBus.emit("settingsChanged");
}
// 只有在用户已登录的情况下才调用服务器接口 // 只有在用户已登录的情况下才调用服务器接口
if (user?.user_id) { if (user?.user_id) {
try { try {
@ -116,6 +121,8 @@ export const CountrySetting = () => {
// 用户未登录,只保存到本地 // 用户未登录,只保存到本地
if (changeType === "language" && language) { if (changeType === "language" && language) {
await changeLanguage(language); await changeLanguage(language);
// 语言变更时也需要通知HomeScreen重新加载
eventBus.emit("refreshSetting");
} }
console.log('用户未登录,设置已保存到本地'); console.log('用户未登录,设置已保存到本地');
} }

Loading…
Cancel
Save