ProductSwiper.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { Image } from 'expo-image';
  2. import React, { useState } from 'react';
  3. import {
  4. Dimensions,
  5. FlatList,
  6. ImageBackground,
  7. StyleSheet,
  8. Text,
  9. TouchableOpacity,
  10. View,
  11. } from 'react-native';
  12. import { Images } from '@/constants/images';
  13. const { width } = Dimensions.get('window');
  14. interface Product {
  15. cover: string;
  16. name: string;
  17. level: string;
  18. quantity?: number;
  19. probability?: string;
  20. }
  21. interface ProductSwiperProps {
  22. products: Product[];
  23. onShowSwipe?: (index: number) => void;
  24. }
  25. export default function ProductSwiper({ products, onShowSwipe }: ProductSwiperProps) {
  26. const [current, setCurrent] = useState(0);
  27. const getProbability = (item: Product) => {
  28. return item.probability || "";
  29. };
  30. if (!products || products.length === 0) return null;
  31. const renderItem = ({ item, index }: { item: Product; index: number }) => (
  32. <TouchableOpacity
  33. activeOpacity={0.9}
  34. onPress={() => onShowSwipe?.(index)}
  35. style={styles.itemContainer}
  36. >
  37. <View style={styles.imageBox}>
  38. <Image source={{ uri: item.cover }} style={styles.image} contentFit="contain" />
  39. </View>
  40. {/* Details Button/Tag */}
  41. <ImageBackground
  42. source={{ uri: Images.box.detail.detailsBut }}
  43. style={styles.detailsTag}
  44. resizeMode="contain"
  45. >
  46. <View style={styles.detailsContent}>
  47. <Text style={styles.levelText}>
  48. {item.level === 'A' ? '超神款' :
  49. item.level === 'B' ? '欧皇款' :
  50. item.level === 'C' ? '隐藏款' : '普通款'}
  51. </Text>
  52. <Text style={styles.probText}>{getProbability(item)}</Text>
  53. </View>
  54. </ImageBackground>
  55. {/* Name Tag */}
  56. <ImageBackground
  57. source={{ uri: Images.box.detail.nameBg }}
  58. style={styles.nameTag}
  59. resizeMode="stretch"
  60. >
  61. <Text style={styles.nameText} numberOfLines={1}>{item.name}</Text>
  62. </ImageBackground>
  63. </TouchableOpacity>
  64. );
  65. return (
  66. <View style={styles.container}>
  67. <FlatList
  68. data={products}
  69. renderItem={renderItem}
  70. horizontal
  71. pagingEnabled
  72. showsHorizontalScrollIndicator={false}
  73. keyExtractor={(_, index) => index.toString()}
  74. onMomentumScrollEnd={(e) => {
  75. const contentOffset = e.nativeEvent.contentOffset;
  76. const viewSize = e.nativeEvent.layoutMeasurement;
  77. const pageNum = Math.floor(contentOffset.x / viewSize.width);
  78. setCurrent(pageNum);
  79. }}
  80. />
  81. {/* Left/Right Arrows if needed */}
  82. {current > 0 && (
  83. <View style={[styles.arrow, styles.leftArrow]}>
  84. <Image source={{ uri: Images.box.detail.left }} style={styles.arrowImg} contentFit="contain" />
  85. </View>
  86. )}
  87. {current < products.length - 1 && (
  88. <View style={[styles.arrow, styles.rightArrow]}>
  89. <Image source={{ uri: Images.box.detail.right }} style={styles.arrowImg} contentFit="contain" />
  90. </View>
  91. )}
  92. </View>
  93. );
  94. }
  95. const styles = StyleSheet.create({
  96. container: {
  97. width: '100%',
  98. height: 380, // Adjusted height
  99. alignItems: 'center',
  100. marginTop: 20,
  101. },
  102. itemContainer: {
  103. width: width, // Full width for paging
  104. height: '100%',
  105. justifyContent: 'center',
  106. alignItems: 'center',
  107. },
  108. imageBox: {
  109. width: '60%',
  110. height: '60%',
  111. justifyContent: 'center',
  112. alignItems: 'center',
  113. },
  114. image: {
  115. width: '100%',
  116. height: '100%',
  117. },
  118. detailsTag: {
  119. position: 'absolute',
  120. right: 40,
  121. bottom: 80,
  122. width: 100,
  123. height: 60,
  124. justifyContent: 'center',
  125. alignItems: 'center',
  126. },
  127. detailsContent: {
  128. alignItems: 'center',
  129. justifyContent: 'center',
  130. marginTop: 5,
  131. },
  132. levelText: {
  133. color: '#fff',
  134. fontSize: 12,
  135. fontWeight: 'bold',
  136. textShadowColor: '#000',
  137. textShadowOffset: { width: 1, height: 1 },
  138. textShadowRadius: 1,
  139. },
  140. probText: {
  141. color: '#fff',
  142. fontSize: 10,
  143. fontWeight: 'bold',
  144. textShadowColor: '#000',
  145. textShadowOffset: { width: 1, height: 1 },
  146. textShadowRadius: 1,
  147. },
  148. nameTag: {
  149. position: 'absolute',
  150. left: 40,
  151. top: 40,
  152. width: 40,
  153. height: 120,
  154. justifyContent: 'center',
  155. alignItems: 'center',
  156. paddingVertical: 10,
  157. },
  158. nameText: {
  159. color: '#000',
  160. fontSize: 12,
  161. fontWeight: 'bold',
  162. width: 12,
  163. textAlign: 'center',
  164. },
  165. arrow: {
  166. position: 'absolute',
  167. top: '50%',
  168. marginTop: -20,
  169. width: 40,
  170. height: 40,
  171. zIndex: 10,
  172. },
  173. leftArrow: {
  174. left: 10,
  175. },
  176. rightArrow: {
  177. right: 10,
  178. },
  179. arrowImg: {
  180. width: '100%',
  181. height: '100%',
  182. }
  183. });