ProductList.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import { Image } from 'expo-image';
  2. import { useRouter } from 'expo-router';
  3. import React from 'react';
  4. import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  5. import { Images } from '@/constants/images';
  6. interface ProductItem {
  7. id: string;
  8. name: string;
  9. cover: string;
  10. level: string;
  11. probability: number;
  12. price?: number;
  13. quantity?: number;
  14. spu?: {
  15. id: string;
  16. cover: string;
  17. name: string;
  18. };
  19. }
  20. interface ProductListProps {
  21. products: ProductItem[];
  22. levelList?: any[];
  23. poolId: string;
  24. price: number;
  25. }
  26. // 等级配置 - 对应小程序的 LEVEL_MAP
  27. const LEVEL_CONFIG: Record<string, { title: string; color: string; bgColor: string }> = {
  28. A: { title: '超神款', color: '#fff', bgColor: '#FF4444' },
  29. B: { title: '欧皇款', color: '#fff', bgColor: '#FF9900' },
  30. C: { title: '隐藏款', color: '#fff', bgColor: '#9966FF' },
  31. D: { title: '普通款', color: '#fff', bgColor: '#00CCFF' },
  32. };
  33. const ignoreRatio0 = (val: number) => {
  34. // 将小数转换为百分比,保留两位小数
  35. const percent = val * 100;
  36. // 如果是整数则不显示小数点
  37. if (percent === Math.floor(percent)) {
  38. return String(Math.floor(percent));
  39. }
  40. // 保留两位小数,去除末尾的0
  41. return percent.toFixed(2).replace(/\.?0+$/, '');
  42. };
  43. export const ProductList: React.FC<ProductListProps> = ({ products, levelList, poolId }) => {
  44. const router = useRouter();
  45. // 按等级分组
  46. const groupedProducts = products.reduce(
  47. (acc, item) => {
  48. const level = item.level || 'D';
  49. if (!acc[level]) acc[level] = [];
  50. acc[level].push(item);
  51. return acc;
  52. },
  53. {} as Record<string, ProductItem[]>
  54. );
  55. // 计算各等级概率
  56. const getLevelProbability = (level: string) => {
  57. const item = levelList?.find((e) => e.level === level);
  58. return item ? `${item.probability}%` : '0%';
  59. };
  60. // 点击产品跳转到详情页
  61. const handleProductPress = (item: ProductItem) => {
  62. // 找到该产品在原始列表中的索引
  63. const index = products.findIndex((p) => p.id === item.id);
  64. router.push({
  65. pathname: '/award-detail/swipe' as any,
  66. params: { poolId, index: index >= 0 ? index : 0 },
  67. });
  68. };
  69. const renderLevelSection = (level: string, items: ProductItem[]) => {
  70. const config = LEVEL_CONFIG[level] || LEVEL_CONFIG['D'];
  71. return (
  72. <View key={level} style={styles.levelBox}>
  73. {/* 等级标题行 */}
  74. <View style={styles.levelTitleRow}>
  75. <Text style={[styles.levelTitle, { color: config.bgColor }]}>{config.title}</Text>
  76. <View style={styles.levelProportion}>
  77. <Text style={styles.probabilityLabel}>概率:</Text>
  78. <Text style={styles.probabilityValue}>{getLevelProbability(level)}</Text>
  79. </View>
  80. </View>
  81. {/* 商品横向滚动列表 */}
  82. <ScrollView
  83. horizontal
  84. showsHorizontalScrollIndicator={false}
  85. contentContainerStyle={styles.scrollContent}
  86. >
  87. {items.map((item, index) => {
  88. const cover = item.spu?.cover || item.cover;
  89. return (
  90. <TouchableOpacity
  91. key={item.id || index}
  92. style={styles.productItem}
  93. onPress={() => handleProductPress(item)}
  94. activeOpacity={0.8}
  95. >
  96. {/* 商品图片 */}
  97. <View style={styles.productImageBox}>
  98. <Image
  99. source={{ uri: cover }}
  100. style={styles.productImage}
  101. contentFit="contain"
  102. />
  103. </View>
  104. {/* 等级标签 - 显示概率和等级名称 */}
  105. <View style={[styles.levelTag, { backgroundColor: config.bgColor }]}>
  106. <Text style={styles.levelTagLabel}>概率</Text>
  107. <Text style={styles.levelTagText}>{ignoreRatio0(item.probability)}%</Text>
  108. </View>
  109. </TouchableOpacity>
  110. );
  111. })}
  112. </ScrollView>
  113. </View>
  114. );
  115. };
  116. const levelOrder = ['A', 'B', 'C', 'D'];
  117. return (
  118. <View style={styles.container}>
  119. {/* 标题 */}
  120. <View style={styles.titleBox}>
  121. <Image source={{ uri: Images.box.detail.productTitle }} style={styles.titleImg} contentFit="contain" />
  122. </View>
  123. {levelOrder.map((level) => {
  124. const items = groupedProducts[level];
  125. if (!items || items.length === 0) return null;
  126. return renderLevelSection(level, items);
  127. })}
  128. </View>
  129. );
  130. };
  131. const styles = StyleSheet.create({
  132. container: {
  133. paddingHorizontal: 10,
  134. marginTop: -40,
  135. },
  136. titleBox: {
  137. alignItems: 'center',
  138. marginBottom: 15,
  139. },
  140. titleImg: {
  141. width: 121,
  142. height: 29,
  143. },
  144. levelBox: {
  145. marginBottom: 20,
  146. },
  147. levelTitleRow: {
  148. flexDirection: 'row',
  149. alignItems: 'center',
  150. justifyContent: 'center',
  151. paddingHorizontal: 14,
  152. paddingBottom: 10,
  153. position: 'relative',
  154. },
  155. levelTitle: {
  156. fontSize: 18,
  157. fontWeight: 'bold',
  158. textAlign: 'center',
  159. textShadowColor: '#000',
  160. textShadowOffset: { width: 1, height: 1 },
  161. textShadowRadius: 1,
  162. },
  163. levelProportion: {
  164. position: 'absolute',
  165. right: 0,
  166. bottom: 10,
  167. flexDirection: 'row',
  168. alignItems: 'center',
  169. },
  170. probabilityLabel: {
  171. fontSize: 12,
  172. color: '#ffc901',
  173. },
  174. probabilityValue: {
  175. fontSize: 12,
  176. color: '#fff',
  177. },
  178. scrollContent: {
  179. paddingHorizontal: 5,
  180. },
  181. productItem: {
  182. width: 90,
  183. marginRight: 10,
  184. alignItems: 'center',
  185. },
  186. productImageBox: {
  187. width: 90,
  188. height: 90,
  189. backgroundColor: '#fff',
  190. borderRadius: 4,
  191. overflow: 'hidden',
  192. },
  193. productImage: {
  194. width: 90,
  195. height: 90,
  196. },
  197. levelTag: {
  198. width: 80,
  199. height: 26,
  200. borderRadius: 2,
  201. flexDirection: 'row',
  202. justifyContent: 'center',
  203. alignItems: 'center',
  204. marginTop: -8,
  205. },
  206. levelTagLabel: {
  207. fontSize: 10,
  208. color: '#fff',
  209. marginRight: 2,
  210. },
  211. levelTagText: {
  212. fontSize: 12,
  213. color: '#fff',
  214. fontWeight: 'bold',
  215. },
  216. });