DetailsPopup.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import { Image } from 'expo-image';
  2. import React, { forwardRef, useImperativeHandle, useMemo, useState } from 'react';
  3. import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  4. interface PrizeItem {
  5. id: string;
  6. name: string;
  7. cover: string;
  8. level: string;
  9. price?: number;
  10. probability?: number;
  11. }
  12. interface GoodsItem {
  13. id: string;
  14. name: string;
  15. cover: string;
  16. level: string;
  17. price?: number;
  18. probability?: number;
  19. }
  20. interface DetailsPopupProps {}
  21. export interface DetailsPopupRef {
  22. open: (item: GoodsItem, prizes: PrizeItem[]) => void;
  23. close: () => void;
  24. }
  25. export const DetailsPopup = forwardRef<DetailsPopupRef, DetailsPopupProps>((_, ref) => {
  26. const [visible, setVisible] = useState(false);
  27. const [info, setInfo] = useState<GoodsItem | null>(null);
  28. const [prizes, setPrizes] = useState<PrizeItem[]>([]);
  29. // 按等级分类奖品
  30. const { cellAList, cellBList, cellCList, cellDList, cellAprobability, cellBprobability, cellCprobability, cellDprobability } = useMemo(() => {
  31. const result = {
  32. cellAList: [] as PrizeItem[],
  33. cellBList: [] as PrizeItem[],
  34. cellCList: [] as PrizeItem[],
  35. cellDList: [] as PrizeItem[],
  36. cellAprobability: 0,
  37. cellBprobability: 0,
  38. cellCprobability: 0,
  39. cellDprobability: 0,
  40. };
  41. prizes.forEach((item) => {
  42. switch (item.level) {
  43. case 'A':
  44. result.cellAList.push(item);
  45. result.cellAprobability += item.probability || 0;
  46. break;
  47. case 'B':
  48. result.cellBList.push(item);
  49. result.cellBprobability += item.probability || 0;
  50. break;
  51. case 'C':
  52. result.cellCList.push(item);
  53. result.cellCprobability += item.probability || 0;
  54. break;
  55. case 'D':
  56. result.cellDList.push(item);
  57. result.cellDprobability += item.probability || 0;
  58. break;
  59. }
  60. });
  61. return result;
  62. }, [prizes]);
  63. useImperativeHandle(ref, () => ({
  64. open: (item: GoodsItem, prizeList: PrizeItem[]) => {
  65. setInfo(item);
  66. setPrizes(prizeList);
  67. setVisible(true);
  68. },
  69. close: () => setVisible(false),
  70. }));
  71. const isGuaranteed = info?.level === 'NESTED_BOX_GUARANTEED';
  72. return (
  73. <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
  74. <View style={styles.overlay}>
  75. <View style={styles.container}>
  76. {/* 顶部标题区 */}
  77. <View style={styles.header}>
  78. <Text style={styles.title} numberOfLines={1}>{info?.name}</Text>
  79. <View style={[styles.levelBadge, isGuaranteed ? styles.levelD : styles.levelAll]}>
  80. <Text style={styles.levelText}>{isGuaranteed ? 'D赏' : '全局赏'}</Text>
  81. </View>
  82. </View>
  83. {/* 主图展示区 */}
  84. <View style={styles.mainContent}>
  85. <View style={styles.productImage}>
  86. <Image source={{ uri: info?.cover }} style={styles.image} contentFit="cover" />
  87. </View>
  88. <View style={styles.priceInfo}>
  89. <View style={styles.priceItem}>
  90. <Text style={styles.label}>指导价:</Text>
  91. <Text style={styles.value}>¥ {info?.price || 0}</Text>
  92. </View>
  93. <View style={styles.priceItem}>
  94. <Text style={styles.label}>概率:</Text>
  95. <Text style={styles.value}>{((info?.probability || 0) * 100).toFixed(2)}%</Text>
  96. </View>
  97. <Text style={styles.tips}>1~3抽完赠机送</Text>
  98. </View>
  99. </View>
  100. {/* 奖品列表 - 仅非保底款显示 */}
  101. {!isGuaranteed && (
  102. <ScrollView style={styles.prizeScroll} showsVerticalScrollIndicator={false}>
  103. {/* A赏 */}
  104. {cellAList.length > 0 && (
  105. <View style={styles.prizeSection}>
  106. <View style={styles.sectionHeader}>
  107. <View style={[styles.levelTag, styles.levelTagA]}>
  108. <Text style={styles.levelTagText}>A赏</Text>
  109. </View>
  110. <Text style={styles.probability}>概率{(cellAprobability * 100).toFixed(2)}%</Text>
  111. </View>
  112. <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.prizeList}>
  113. {cellAList.map((item, index) => (
  114. <View key={item.id || index} style={styles.prizeItem}>
  115. <View style={styles.prizeImageBox}>
  116. <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="cover" />
  117. <View style={styles.itemPrice}>
  118. <Text style={styles.itemPriceText}>¥{item.price || 0}</Text>
  119. </View>
  120. </View>
  121. <Text style={styles.prizeName} numberOfLines={1}>{item.name}</Text>
  122. </View>
  123. ))}
  124. </ScrollView>
  125. </View>
  126. )}
  127. {/* B赏 */}
  128. {cellBList.length > 0 && (
  129. <View style={styles.prizeSection}>
  130. <View style={styles.sectionHeader}>
  131. <View style={[styles.levelTag, styles.levelTagB]}>
  132. <Text style={styles.levelTagText}>B赏</Text>
  133. </View>
  134. <Text style={styles.probability}>概率{(cellBprobability * 100).toFixed(2)}%</Text>
  135. </View>
  136. <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.prizeList}>
  137. {cellBList.map((item, index) => (
  138. <View key={item.id || index} style={styles.prizeItem}>
  139. <View style={styles.prizeImageBox}>
  140. <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="cover" />
  141. <View style={styles.itemPrice}>
  142. <Text style={styles.itemPriceText}>¥{item.price || 0}</Text>
  143. </View>
  144. </View>
  145. <Text style={styles.prizeName} numberOfLines={1}>{item.name}</Text>
  146. </View>
  147. ))}
  148. </ScrollView>
  149. </View>
  150. )}
  151. {/* C赏 */}
  152. {cellCList.length > 0 && (
  153. <View style={styles.prizeSection}>
  154. <View style={styles.sectionHeader}>
  155. <View style={[styles.levelTag, styles.levelTagB]}>
  156. <Text style={styles.levelTagText}>C赏</Text>
  157. </View>
  158. <Text style={styles.probability}>概率{(cellCprobability * 100).toFixed(2)}%</Text>
  159. </View>
  160. <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.prizeList}>
  161. {cellCList.map((item, index) => (
  162. <View key={item.id || index} style={styles.prizeItem}>
  163. <View style={styles.prizeImageBox}>
  164. <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="cover" />
  165. <View style={styles.itemPrice}>
  166. <Text style={styles.itemPriceText}>¥{item.price || 0}</Text>
  167. </View>
  168. </View>
  169. <Text style={styles.prizeName} numberOfLines={1}>{item.name}</Text>
  170. </View>
  171. ))}
  172. </ScrollView>
  173. </View>
  174. )}
  175. {/* D赏 */}
  176. {cellDList.length > 0 && (
  177. <View style={styles.prizeSection}>
  178. <View style={styles.sectionHeader}>
  179. <View style={[styles.levelTag, styles.levelTagB]}>
  180. <Text style={styles.levelTagText}>D赏</Text>
  181. </View>
  182. <Text style={styles.probability}>概率{(cellDprobability * 100).toFixed(2)}%</Text>
  183. </View>
  184. <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.prizeList}>
  185. {cellDList.map((item, index) => (
  186. <View key={item.id || index} style={styles.prizeItem}>
  187. <View style={styles.prizeImageBox}>
  188. <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="cover" />
  189. <View style={styles.itemPrice}>
  190. <Text style={styles.itemPriceText}>¥{item.price || 0}</Text>
  191. </View>
  192. </View>
  193. <Text style={styles.prizeName} numberOfLines={1}>{item.name}</Text>
  194. </View>
  195. ))}
  196. </ScrollView>
  197. </View>
  198. )}
  199. </ScrollView>
  200. )}
  201. {/* 关闭按钮 */}
  202. <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
  203. <Text style={styles.closeBtnText}>×</Text>
  204. </TouchableOpacity>
  205. </View>
  206. </View>
  207. </Modal>
  208. );
  209. });
  210. const styles = StyleSheet.create({
  211. overlay: {
  212. flex: 1,
  213. backgroundColor: 'rgba(0,0,0,0.5)',
  214. justifyContent: 'center',
  215. alignItems: 'center',
  216. padding: 20,
  217. },
  218. container: {
  219. width: '100%',
  220. maxWidth: 310,
  221. backgroundColor: '#ffb300',
  222. borderRadius: 10,
  223. overflow: 'hidden',
  224. maxHeight: '80%',
  225. },
  226. header: {
  227. flexDirection: 'row',
  228. alignItems: 'center',
  229. justifyContent: 'space-between',
  230. padding: 15,
  231. },
  232. title: {
  233. fontSize: 18,
  234. fontWeight: 'bold',
  235. color: '#000',
  236. flex: 1,
  237. marginRight: 10,
  238. },
  239. levelBadge: {
  240. paddingHorizontal: 12,
  241. paddingVertical: 4,
  242. borderRadius: 2,
  243. },
  244. levelD: {
  245. backgroundColor: '#6340FF',
  246. borderWidth: 1,
  247. borderColor: '#A2BBFF',
  248. },
  249. levelAll: {
  250. backgroundColor: '#A3E100',
  251. borderWidth: 1,
  252. borderColor: '#EAFFB1',
  253. },
  254. levelText: {
  255. fontSize: 12,
  256. fontWeight: 'bold',
  257. color: '#fff',
  258. },
  259. mainContent: {
  260. backgroundColor: '#fff',
  261. padding: 15,
  262. flexDirection: 'row',
  263. },
  264. productImage: {
  265. width: 100,
  266. height: 100,
  267. borderRadius: 6,
  268. borderWidth: 2,
  269. borderColor: '#E0E0E0',
  270. overflow: 'hidden',
  271. },
  272. image: {
  273. width: '100%',
  274. height: '100%',
  275. },
  276. priceInfo: {
  277. flex: 1,
  278. paddingLeft: 15,
  279. justifyContent: 'center',
  280. },
  281. priceItem: {
  282. flexDirection: 'row',
  283. alignItems: 'center',
  284. marginBottom: 8,
  285. },
  286. label: {
  287. fontSize: 14,
  288. color: '#333',
  289. },
  290. value: {
  291. fontSize: 14,
  292. color: '#333',
  293. fontWeight: 'bold',
  294. },
  295. tips: {
  296. fontSize: 13,
  297. color: '#FF9500',
  298. marginTop: 4,
  299. },
  300. prizeScroll: {
  301. maxHeight: 325,
  302. backgroundColor: '#fff',
  303. },
  304. prizeSection: {
  305. marginTop: 15,
  306. },
  307. sectionHeader: {
  308. flexDirection: 'row',
  309. alignItems: 'center',
  310. paddingHorizontal: 15,
  311. marginBottom: 10,
  312. },
  313. levelTag: {
  314. paddingHorizontal: 8,
  315. paddingVertical: 3,
  316. borderRadius: 3,
  317. borderWidth: 1.5,
  318. borderColor: '#000',
  319. marginRight: 10,
  320. },
  321. levelTagA: {
  322. backgroundColor: '#FFD700',
  323. },
  324. levelTagB: {
  325. backgroundColor: '#FFA500',
  326. },
  327. levelTagText: {
  328. fontSize: 14,
  329. fontWeight: 'bold',
  330. color: '#000',
  331. },
  332. probability: {
  333. fontSize: 14,
  334. color: '#000',
  335. },
  336. prizeList: {
  337. paddingHorizontal: 15,
  338. paddingBottom: 15,
  339. },
  340. prizeItem: {
  341. marginRight: 10,
  342. alignItems: 'center',
  343. },
  344. prizeImageBox: {
  345. width: 80,
  346. height: 80,
  347. borderRadius: 6,
  348. borderWidth: 2,
  349. borderColor: '#000',
  350. overflow: 'hidden',
  351. position: 'relative',
  352. },
  353. prizeImage: {
  354. width: '100%',
  355. height: '100%',
  356. },
  357. itemPrice: {
  358. position: 'absolute',
  359. bottom: 0,
  360. left: 0,
  361. right: 0,
  362. backgroundColor: 'rgba(0,0,0,0.5)',
  363. paddingVertical: 2,
  364. },
  365. itemPriceText: {
  366. fontSize: 10,
  367. color: '#fff',
  368. textAlign: 'center',
  369. },
  370. prizeName: {
  371. marginTop: 5,
  372. fontSize: 12,
  373. color: '#000',
  374. textAlign: 'center',
  375. width: 80,
  376. },
  377. closeBtn: {
  378. position: 'absolute',
  379. right: 10,
  380. top: 10,
  381. width: 24,
  382. height: 24,
  383. justifyContent: 'center',
  384. alignItems: 'center',
  385. },
  386. closeBtnText: {
  387. fontSize: 20,
  388. color: '#333',
  389. fontWeight: 'bold',
  390. },
  391. });