index.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. import { Image } from 'expo-image';
  2. import { useLocalSearchParams, useRouter } from 'expo-router';
  3. import React, { useCallback, useEffect, useRef, useState } from 'react';
  4. import {
  5. ActivityIndicator,
  6. Animated,
  7. Dimensions,
  8. ImageBackground,
  9. ScrollView,
  10. StatusBar,
  11. StyleSheet,
  12. Text,
  13. TouchableOpacity,
  14. View,
  15. } from 'react-native';
  16. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  17. import { Images } from '@/constants/images';
  18. import { useAuth } from '@/contexts/AuthContext';
  19. import {
  20. getBoxDetail,
  21. getNextBox,
  22. getPoolDetail,
  23. getPreBox,
  24. lockBox,
  25. poolIn,
  26. poolOut,
  27. previewOrder,
  28. unlockBox,
  29. } from '@/services/award';
  30. import { CheckoutModal } from '../award-detail/components/CheckoutModal';
  31. import { RecordModal } from '../award-detail/components/RecordModal';
  32. import { RuleModal } from '../award-detail/components/RuleModal';
  33. import { BoxChooseModal } from './components/BoxChooseModal';
  34. import { NumChooseModal } from './components/NumChooseModal';
  35. import { ProductListYfs } from './components/ProductListYfs';
  36. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  37. interface PoolData {
  38. id: string;
  39. name: string;
  40. cover: string;
  41. price: number;
  42. specialPrice?: number;
  43. specialPriceFive?: number;
  44. restrictionQuantity?: number;
  45. luckGoodsList: ProductItem[];
  46. luckGoodsLevelProbabilityList?: any[];
  47. }
  48. interface ProductItem {
  49. id: string;
  50. name: string;
  51. cover: string;
  52. level: string;
  53. probability: number;
  54. price?: number;
  55. quantity?: number;
  56. }
  57. interface BoxData {
  58. number: string;
  59. quantity: number;
  60. leftQuantity: number;
  61. leftQuantityA: number;
  62. leftQuantityB: number;
  63. leftQuantityC: number;
  64. leftQuantityD: number;
  65. lastNumber: number;
  66. lock?: { locker: string; leftTime: number };
  67. usedStat?: Record<string, { spuId: string; quantity: number }>;
  68. prizeList?: any[];
  69. }
  70. export default function AwardDetailYfsScreen() {
  71. const { poolId } = useLocalSearchParams<{ poolId: string }>();
  72. const router = useRouter();
  73. const insets = useSafeAreaInsets();
  74. const { user } = useAuth();
  75. const [loading, setLoading] = useState(true);
  76. const [data, setData] = useState<PoolData | null>(null);
  77. const [products, setProducts] = useState<ProductItem[]>([]);
  78. const [box, setBox] = useState<BoxData | null>(null);
  79. const [boxNum, setBoxNum] = useState<string>('');
  80. const [currentIndex, setCurrentIndex] = useState(0);
  81. const [leftTime, setLeftTime] = useState(0);
  82. const [probability, setProbability] = useState<any[]>([]);
  83. const [scrollTop, setScrollTop] = useState(0);
  84. const checkoutRef = useRef<any>(null);
  85. const recordRef = useRef<any>(null);
  86. const ruleRef = useRef<any>(null);
  87. const numChooseRef = useRef<any>(null);
  88. const boxChooseRef = useRef<any>(null);
  89. const floatAnim = useRef(new Animated.Value(0)).current;
  90. const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  91. useEffect(() => {
  92. Animated.loop(
  93. Animated.sequence([
  94. Animated.timing(floatAnim, { toValue: 10, duration: 1500, useNativeDriver: true }),
  95. Animated.timing(floatAnim, { toValue: -10, duration: 1500, useNativeDriver: true }),
  96. ])
  97. ).start();
  98. }, []);
  99. const loadData = useCallback(async () => {
  100. if (!poolId) return;
  101. setLoading(true);
  102. try {
  103. const detail = await getPoolDetail(poolId);
  104. if (detail) {
  105. setData(detail);
  106. setProducts(detail.luckGoodsList || []);
  107. }
  108. } catch (error) {
  109. console.error('加载数据失败:', error);
  110. }
  111. setLoading(false);
  112. }, [poolId]);
  113. const loadBox = useCallback(
  114. async (num?: string) => {
  115. if (!poolId) return;
  116. try {
  117. const res = await getBoxDetail(poolId, num);
  118. if (res) handleBoxResult(res);
  119. } catch (error) {
  120. console.error('加载盒子失败:', error);
  121. }
  122. },
  123. [poolId]
  124. );
  125. const handleBoxResult = (res: any) => {
  126. const map: Record<string, any> = {};
  127. if (res.usedStat) {
  128. res.usedStat.forEach((item: any) => {
  129. map[item.spuId] = item;
  130. });
  131. }
  132. res.usedStat = map;
  133. setBox(res);
  134. setBoxNum(res.number);
  135. lockTimeStart(res);
  136. if (res.leftQuantity <= 0) {
  137. setProbability([
  138. { level: 'A', probability: 0 },
  139. { level: 'B', probability: 0 },
  140. { level: 'C', probability: 0 },
  141. { level: 'D', probability: 0 },
  142. ]);
  143. } else {
  144. setProbability([
  145. { level: 'A', probability: ((res.leftQuantityA / res.leftQuantity) * 100).toFixed(2) },
  146. { level: 'B', probability: ((res.leftQuantityB / res.leftQuantity) * 100).toFixed(2) },
  147. { level: 'C', probability: ((res.leftQuantityC / res.leftQuantity) * 100).toFixed(2) },
  148. { level: 'D', probability: ((res.leftQuantityD / res.leftQuantity) * 100).toFixed(2) },
  149. ]);
  150. }
  151. };
  152. const lockTimeStart = (boxData: BoxData) => {
  153. lockTimeEnd();
  154. if (boxData?.lock) {
  155. setLeftTime(boxData.lock.leftTime);
  156. timerRef.current = setInterval(() => {
  157. setLeftTime((prev) => {
  158. if (prev <= 1) {
  159. lockTimeEnd();
  160. loadBox();
  161. return 0;
  162. }
  163. return prev - 1;
  164. });
  165. }, 1000);
  166. }
  167. };
  168. const lockTimeEnd = () => {
  169. if (timerRef.current) {
  170. clearInterval(timerRef.current);
  171. timerRef.current = null;
  172. }
  173. setLeftTime(0);
  174. };
  175. useEffect(() => {
  176. loadData();
  177. loadBox();
  178. if (poolId) poolIn(poolId);
  179. return () => {
  180. if (poolId) poolOut(poolId);
  181. lockTimeEnd();
  182. };
  183. }, [poolId]);
  184. const handlePay = async (num: number) => {
  185. if (!poolId || !data || !box) return;
  186. try {
  187. const preview = await previewOrder(poolId, num, box.number);
  188. if (preview) checkoutRef.current?.show(num, preview, box.number);
  189. } catch (error) {
  190. console.error('预览订单失败:', error);
  191. }
  192. };
  193. const handleSuccess = () => {
  194. setTimeout(() => {
  195. loadData();
  196. loadBox(boxNum);
  197. }, 500);
  198. };
  199. // 处理多盒购买(号码选择后的回调)
  200. const handleNumPay = async (params: { preview: any; seatNumbers: number[]; boxNumber: string }) => {
  201. checkoutRef.current?.show(params.seatNumbers.length, params.preview, params.boxNumber, params.seatNumbers);
  202. };
  203. // 打开号码选择弹窗
  204. const handleShowNumChoose = () => {
  205. if (!box) return;
  206. numChooseRef.current?.show({
  207. number: box.number,
  208. quantity: box.quantity || box.leftQuantity,
  209. lastNumber: box.lastNumber,
  210. });
  211. };
  212. // 打开换盒弹窗
  213. const handleShowBoxChoose = () => {
  214. boxChooseRef.current?.show();
  215. };
  216. // 选择盒子后的回调
  217. const handleChooseBox = (boxNumber: string) => {
  218. loadBox(boxNumber);
  219. };
  220. const handlePreBox = async () => {
  221. if (!poolId || !boxNum || parseInt(boxNum) <= 1) return;
  222. try {
  223. const res = await getPreBox(poolId, boxNum);
  224. if (res) handleBoxResult(res);
  225. } catch (error) {
  226. console.error('获取上一个盒子失败:', error);
  227. }
  228. };
  229. const handleNextBox = async () => {
  230. if (!poolId || !box || parseInt(boxNum) >= box.lastNumber) return;
  231. try {
  232. const res = await getNextBox(poolId, boxNum);
  233. if (res) handleBoxResult(res);
  234. } catch (error) {
  235. console.error('获取下一个盒子失败:', error);
  236. }
  237. };
  238. const handleLock = async () => {
  239. if (!poolId || !boxNum) return;
  240. try {
  241. await lockBox(poolId, boxNum);
  242. loadBox(boxNum);
  243. } catch (error) {
  244. console.error('锁定失败:', error);
  245. }
  246. };
  247. const handleUnlock = async () => {
  248. if (!poolId || !boxNum) return;
  249. try {
  250. await unlockBox(poolId, boxNum);
  251. loadBox(boxNum);
  252. } catch (error) {
  253. console.error('解锁失败:', error);
  254. }
  255. };
  256. const handlePrev = () => {
  257. if (currentIndex > 0) setCurrentIndex(currentIndex - 1);
  258. };
  259. const handleNext = () => {
  260. if (currentIndex < products.length - 1) setCurrentIndex(currentIndex + 1);
  261. };
  262. const getLevelName = (level: string) => {
  263. const map: Record<string, string> = { A: '超神款', B: '欧皇款', C: '隐藏款', D: '普通款' };
  264. return map[level] || level;
  265. };
  266. const getLeftNum = (item: ProductItem) => {
  267. if (!box?.usedStat || !box.usedStat[item.id]?.quantity) {
  268. return item.quantity || 1;
  269. }
  270. return (item.quantity || 1) - box.usedStat[item.id].quantity;
  271. };
  272. const getProbability = (item: ProductItem) => {
  273. if (!box || box.leftQuantity <= 0) return '0';
  274. const leftNum = getLeftNum(item);
  275. return ((leftNum / box.leftQuantity) * 100).toFixed(2);
  276. };
  277. const leftNum = box?.lock ? (leftTime / box.lock.leftTime) * 100 : 0;
  278. const headerBg = scrollTop > 0 ? '#333' : 'transparent';
  279. if (loading) {
  280. return (
  281. <View style={styles.loadingContainer}>
  282. <ActivityIndicator size="large" color="#fff" />
  283. </View>
  284. );
  285. }
  286. if (!data) {
  287. return (
  288. <View style={styles.loadingContainer}>
  289. <Text style={styles.errorText}>奖池不存在</Text>
  290. <TouchableOpacity style={styles.backBtn2} onPress={() => router.back()}>
  291. <Text style={styles.backBtn2Text}>返回</Text>
  292. </TouchableOpacity>
  293. </View>
  294. );
  295. }
  296. const currentProduct = products[currentIndex];
  297. return (
  298. <View style={styles.container}>
  299. <StatusBar barStyle="light-content" />
  300. <ImageBackground source={{ uri: Images.common.indexBg }} style={styles.background} resizeMode="cover">
  301. {/* 顶部导航 */}
  302. <View style={[styles.header, { paddingTop: insets.top, backgroundColor: headerBg }]}>
  303. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  304. <Text style={styles.backText}>{'<'}</Text>
  305. </TouchableOpacity>
  306. <Text style={styles.headerTitle} numberOfLines={1}>
  307. {data.name}
  308. </Text>
  309. <View style={styles.placeholder} />
  310. </View>
  311. <ScrollView
  312. style={styles.scrollView}
  313. showsVerticalScrollIndicator={false}
  314. onScroll={(e) => setScrollTop(e.nativeEvent.contentOffset.y)}
  315. scrollEventThrottle={16}
  316. >
  317. {/* 主商品展示区域 */}
  318. <ImageBackground source={{ uri: Images.box.detail.mainGoodsSection }} style={styles.mainGoodsSection} resizeMode="cover">
  319. <View style={{ height: 72 + insets.top }} />
  320. {/* 商品轮播区域 */}
  321. <View style={styles.mainSwiper}>
  322. {currentProduct && (
  323. <>
  324. <Animated.View style={[styles.productImageBox, { transform: [{ translateY: floatAnim }] }]}>
  325. <Image source={{ uri: currentProduct.cover }} style={styles.productImage} contentFit="contain" />
  326. </Animated.View>
  327. {/* 等级信息 */}
  328. <ImageBackground source={{ uri: Images.box.detail.detailsBut }} style={styles.detailsBut} resizeMode="contain">
  329. <View style={styles.detailsText}>
  330. <Text style={styles.levelText}>{getLevelName(currentProduct.level)}</Text>
  331. <Text style={styles.probabilityText}>({getProbability(currentProduct)}%)</Text>
  332. </View>
  333. </ImageBackground>
  334. {/* 商品名称 */}
  335. <ImageBackground source={{ uri: Images.box.detail.nameBg }} style={styles.goodsNameBg} resizeMode="contain">
  336. <Text style={styles.goodsNameText} numberOfLines={6}>
  337. {currentProduct.name}
  338. </Text>
  339. </ImageBackground>
  340. </>
  341. )}
  342. {/* 左右切换按钮 */}
  343. {currentIndex > 0 && (
  344. <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
  345. <Image source={{ uri: Images.box.detail.left }} style={styles.arrowImg} contentFit="contain" />
  346. </TouchableOpacity>
  347. )}
  348. {currentIndex < products.length - 1 && (
  349. <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
  350. <Image source={{ uri: Images.box.detail.right }} style={styles.arrowImg} contentFit="contain" />
  351. </TouchableOpacity>
  352. )}
  353. </View>
  354. {/* 左侧装饰 */}
  355. <Image source={{ uri: Images.box.detail.positionBgleftBg }} style={styles.positionBgleftBg} contentFit="contain" />
  356. {/* 右侧装饰 */}
  357. <Image source={{ uri: Images.box.detail.positionBgRightBg }} style={styles.positionBgRightBg} contentFit="contain" />
  358. </ImageBackground>
  359. {/* 底部装饰文字 */}
  360. <Image source={{ uri: Images.box.detail.mainGoodsSectionBtext }} style={styles.mainGoodsSectionBtext} contentFit="cover" />
  361. {/* 侧边按钮 - 规则 */}
  362. <TouchableOpacity style={[styles.positionBut, styles.positionRule]} onPress={() => ruleRef.current?.show()}>
  363. <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
  364. <Text style={styles.positionButText}>规则</Text>
  365. </ImageBackground>
  366. </TouchableOpacity>
  367. {/* 侧边按钮 - 记录 */}
  368. <TouchableOpacity style={[styles.positionBut, styles.positionRecord]} onPress={() => recordRef.current?.show()}>
  369. <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
  370. <Text style={styles.positionButText}>记录</Text>
  371. </ImageBackground>
  372. </TouchableOpacity>
  373. {/* 侧边按钮 - 锁定/解锁 */}
  374. {box && !box.lock && (
  375. <TouchableOpacity style={[styles.positionBut, styles.positionLock]} onPress={handleLock}>
  376. <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
  377. <Text style={styles.positionButText}>锁定</Text>
  378. </ImageBackground>
  379. </TouchableOpacity>
  380. )}
  381. {box?.lock && user && box.lock.locker === (user.userId || user.id) && (
  382. <TouchableOpacity style={[styles.positionBut, styles.positionLock]} onPress={handleUnlock}>
  383. <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
  384. <Text style={styles.positionButText}>解锁</Text>
  385. </ImageBackground>
  386. </TouchableOpacity>
  387. )}
  388. {/* 侧边按钮 - 仓库 */}
  389. <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store' as any)}>
  390. <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
  391. <Text style={styles.positionButTextR}>仓库</Text>
  392. </ImageBackground>
  393. </TouchableOpacity>
  394. {/* 侧边按钮 - 刷新 */}
  395. <TouchableOpacity style={[styles.positionBut, styles.positionRefresh]} onPress={() => loadBox(boxNum)}>
  396. <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
  397. <Text style={styles.positionButTextR}>刷新</Text>
  398. </ImageBackground>
  399. </TouchableOpacity>
  400. {/* 锁定倒计时 */}
  401. {box?.lock && (
  402. <View style={styles.lockTimeBox}>
  403. <Text style={styles.lockTimeLabel}>剩余时间:</Text>
  404. <View style={styles.lockTimeBarBox}>
  405. <View style={styles.lockTimeBar}>
  406. <View style={[styles.processBar, { width: `${leftNum}%` }]} />
  407. </View>
  408. <Text style={[styles.lockTimeText, { left: `${leftNum}%` }]}>{leftTime}</Text>
  409. </View>
  410. </View>
  411. )}
  412. {/* 标题 */}
  413. <View style={styles.productTitleBox}>
  414. <Image source={{ uri: Images.box.detail.productTitle }} style={styles.productTitleImg} contentFit="contain" />
  415. </View>
  416. {/* 一番赏盒子信息区域 */}
  417. <ImageBackground source={{ uri: Images.box.detail.firstBoxBg }} style={styles.firstLastBox}>
  418. {/* 当前盒子剩余数量 */}
  419. {box && (
  420. <View style={styles.boxSizeRow}>
  421. <Text style={styles.boxSizeText}>当前盒子剩余:</Text>
  422. <Text style={styles.boxSizeText}>
  423. <Text style={styles.boxSizeNum}>{box.leftQuantity}</Text>
  424. /{box.quantity || '-'}发
  425. </Text>
  426. </View>
  427. )}
  428. {/* 抢先赏/最终赏/全局赏展示 */}
  429. {box?.prizeList && box.prizeList.length > 0 && (
  430. <View style={styles.prizeListRow}>
  431. {box.prizeList.map((item: any, index: number) => (
  432. <ImageBackground key={index} source={{ uri: Images.box.detail.firstItemBg }} style={styles.prizeItem}>
  433. <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="contain" />
  434. <View style={[styles.prizeLevelTag, {
  435. backgroundColor: item.level === 'FIRST' ? 'rgba(91, 189, 208, 0.8)' :
  436. item.level === 'LAST' ? 'rgba(246, 44, 113, 0.8)' : 'rgba(44, 246, 74, 0.8)'
  437. }]}>
  438. <Text style={styles.prizeLevelText}>
  439. {item.level === 'FIRST' ? '抢先赏' : item.level === 'LAST' ? '最终赏' : '全局赏'}
  440. </Text>
  441. </View>
  442. </ImageBackground>
  443. ))}
  444. </View>
  445. )}
  446. {/* 换盒控制区 */}
  447. <ImageBackground source={{ uri: Images.box.detail.funBoxBg }} style={styles.funBox}>
  448. <TouchableOpacity style={styles.preBoxBtn} onPress={handlePreBox} disabled={!boxNum || parseInt(boxNum) <= 1}>
  449. <Text style={[styles.funBoxText, (!boxNum || parseInt(boxNum) <= 1) && styles.disabled]}>上一盒</Text>
  450. </TouchableOpacity>
  451. <TouchableOpacity style={styles.changeBoxBtn} onPress={handleShowBoxChoose}>
  452. <Text style={styles.changeBoxText}>换盒({boxNum || '-'}/{box?.lastNumber || '-'})</Text>
  453. </TouchableOpacity>
  454. <TouchableOpacity style={styles.nextBoxBtn} onPress={handleNextBox} disabled={!box || parseInt(boxNum) >= box.lastNumber}>
  455. <Text style={[styles.funBoxText, (!box || parseInt(boxNum) >= box.lastNumber) && styles.disabled]}>下一盒</Text>
  456. </TouchableOpacity>
  457. </ImageBackground>
  458. </ImageBackground>
  459. {/* 商品列表 */}
  460. <ProductListYfs products={products} levelList={probability} poolId={poolId!} price={data.price} box={box} />
  461. <View style={{ height: 150 }} />
  462. </ScrollView>
  463. {/* 底部购买栏 */}
  464. <ImageBackground source={{ uri: Images.box.detail.boxDetailBott }} style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]} resizeMode="cover">
  465. <View style={styles.bottomBtns}>
  466. <TouchableOpacity style={styles.btnItem} onPress={() => handlePay(1)} activeOpacity={0.8}>
  467. <ImageBackground source={{ uri: Images.common.butBgV }} style={styles.btnBg} resizeMode="contain">
  468. <Text style={styles.btnText}>购买一盒</Text>
  469. <Text style={styles.btnPrice}>(¥{data.specialPrice || data.price})</Text>
  470. </ImageBackground>
  471. </TouchableOpacity>
  472. <TouchableOpacity style={styles.btnItem} onPress={() => handlePay(5)} activeOpacity={0.8}>
  473. <ImageBackground source={{ uri: Images.common.butBgL }} style={styles.btnBg} resizeMode="contain">
  474. <Text style={styles.btnText}>购买五盒</Text>
  475. <Text style={styles.btnPrice}>(¥{data.specialPriceFive || data.price * 5})</Text>
  476. </ImageBackground>
  477. </TouchableOpacity>
  478. <TouchableOpacity style={styles.btnItem} onPress={handleShowNumChoose} activeOpacity={0.8}>
  479. <ImageBackground source={{ uri: Images.common.butBgH }} style={styles.btnBg} resizeMode="contain">
  480. <Text style={styles.btnText}>购买多盒</Text>
  481. </ImageBackground>
  482. </TouchableOpacity>
  483. </View>
  484. </ImageBackground>
  485. </ImageBackground>
  486. <CheckoutModal ref={checkoutRef} data={data} poolId={poolId!} boxNumber={boxNum} onSuccess={handleSuccess} />
  487. <RecordModal ref={recordRef} poolId={poolId!} />
  488. <RuleModal ref={ruleRef} />
  489. <NumChooseModal ref={numChooseRef} poolId={poolId!} onPay={handleNumPay} />
  490. <BoxChooseModal ref={boxChooseRef} poolId={poolId!} onChoose={handleChooseBox} />
  491. </View>
  492. );
  493. }
  494. const styles = StyleSheet.create({
  495. container: { flex: 1, backgroundColor: '#1a1a2e' },
  496. background: { flex: 1 },
  497. loadingContainer: { flex: 1, backgroundColor: '#1a1a2e', justifyContent: 'center', alignItems: 'center' },
  498. errorText: { color: '#999', fontSize: 16 },
  499. backBtn2: { marginTop: 20, backgroundColor: '#ff6600', paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 },
  500. backBtn2Text: { color: '#fff', fontSize: 14 },
  501. header: {
  502. flexDirection: 'row',
  503. alignItems: 'center',
  504. justifyContent: 'space-between',
  505. paddingHorizontal: 10,
  506. paddingBottom: 10,
  507. position: 'absolute',
  508. top: 0,
  509. left: 0,
  510. right: 0,
  511. zIndex: 100,
  512. },
  513. backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
  514. backText: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
  515. headerTitle: { color: '#fff', fontSize: 15, fontWeight: 'bold', flex: 1, textAlign: 'center', width: 250 },
  516. placeholder: { width: 40 },
  517. scrollView: { flex: 1 },
  518. mainGoodsSection: { width: SCREEN_WIDTH, height: 504, position: 'relative' },
  519. mainSwiper: { position: 'relative', width: '100%', height: 375, alignItems: 'center', justifyContent: 'center', marginTop: -50 },
  520. productImageBox: { width: 200, height: 300, justifyContent: 'center', alignItems: 'center' },
  521. productImage: { width: 200, height: 300 },
  522. detailsBut: { width: 120, height: 45, justifyContent: 'center', alignItems: 'center', marginTop: -30 },
  523. detailsText: { flexDirection: 'row', alignItems: 'center' },
  524. levelText: { fontSize: 14, color: '#FBC400', fontWeight: 'bold' },
  525. probabilityText: { fontSize: 10, color: '#FBC400', marginLeft: 3 },
  526. goodsNameBg: { position: 'absolute', left: 47, top: 53, width: 43, height: 100, paddingTop: 8, justifyContent: 'flex-start', alignItems: 'center' },
  527. goodsNameText: { fontSize: 12, fontWeight: 'bold', color: '#000', width: 20, textAlign: 'center' },
  528. prevBtn: { position: 'absolute', left: 35, top: '40%' },
  529. nextBtn: { position: 'absolute', right: 35, top: '40%' },
  530. arrowImg: { width: 33, height: 38 },
  531. positionBgleftBg: { position: 'absolute', left: 0, top: 225, width: 32, height: 188 },
  532. positionBgRightBg: { position: 'absolute', right: 0, top: 225, width: 32, height: 188 },
  533. mainGoodsSectionBtext: { width: SCREEN_WIDTH, height: 74, marginTop: -10 },
  534. positionBut: { position: 'absolute', zIndex: 10, width: 35, height: 34 },
  535. positionButBg: { width: 35, height: 34, justifyContent: 'center', alignItems: 'center' },
  536. positionButText: { fontSize: 12, fontWeight: 'bold', color: '#fff', transform: [{ rotate: '14deg' }], textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
  537. positionButTextR: { fontSize: 12, fontWeight: 'bold', color: '#fff', transform: [{ rotate: '-16deg' }], textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
  538. positionRule: { top: 256, left: 0 },
  539. positionRecord: { top: 300, left: 0 },
  540. positionLock: { top: 345, left: 0 },
  541. positionStore: { top: 256, right: 0 },
  542. positionRefresh: { top: 300, right: 0 },
  543. lockTimeBox: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#71ccff', padding: 10, marginHorizontal: 10, borderRadius: 8, marginTop: -60 },
  544. lockTimeLabel: { color: '#000', fontSize: 12 },
  545. lockTimeBarBox: { flex: 1, height: 30, position: 'relative', justifyContent: 'center' },
  546. lockTimeBar: { height: 8, backgroundColor: 'rgba(255,255,255,0.6)', borderRadius: 4, overflow: 'hidden' },
  547. processBar: { height: '100%', backgroundColor: '#209ae5', borderRadius: 4 },
  548. lockTimeText: { position: 'absolute', top: -5, fontSize: 10, backgroundColor: '#000', color: '#fff', paddingHorizontal: 4, borderRadius: 2, marginLeft: -13 },
  549. productTitleBox: { alignItems: 'center', marginTop: -20, marginBottom: 10 },
  550. productTitleImg: { width: 121, height: 29 },
  551. // 一番赏盒子信息区域
  552. firstLastBox: {
  553. marginHorizontal: 10,
  554. height: 193,
  555. marginBottom: 10,
  556. paddingTop: 20,
  557. },
  558. boxSizeRow: {
  559. flexDirection: 'row',
  560. alignItems: 'center',
  561. paddingLeft: 10,
  562. paddingTop: 12,
  563. },
  564. boxSizeText: {
  565. fontSize: 12,
  566. color: '#fff',
  567. textShadowColor: '#000',
  568. textShadowOffset: { width: 1, height: 1 },
  569. textShadowRadius: 2,
  570. },
  571. boxSizeNum: {
  572. fontSize: 16,
  573. fontWeight: 'bold',
  574. },
  575. prizeListRow: {
  576. flexDirection: 'row',
  577. justifyContent: 'center',
  578. alignItems: 'center',
  579. paddingTop: 20,
  580. minHeight: 66,
  581. },
  582. prizeItem: {
  583. width: 63,
  584. height: 65,
  585. marginHorizontal: 5,
  586. position: 'relative',
  587. },
  588. prizeImage: {
  589. width: 60,
  590. height: 50,
  591. },
  592. prizeLevelTag: {
  593. position: 'absolute',
  594. left: 0,
  595. bottom: -8,
  596. width: '100%',
  597. height: 22,
  598. justifyContent: 'center',
  599. alignItems: 'center',
  600. },
  601. prizeLevelText: {
  602. fontSize: 10,
  603. color: '#fff',
  604. fontWeight: 'bold',
  605. },
  606. funBox: {
  607. width: 270,
  608. height: 38,
  609. flexDirection: 'row',
  610. alignItems: 'center',
  611. justifyContent: 'center',
  612. alignSelf: 'center',
  613. marginTop: 15,
  614. },
  615. preBoxBtn: {
  616. width: 90,
  617. height: 50,
  618. justifyContent: 'center',
  619. alignItems: 'center',
  620. },
  621. nextBoxBtn: {
  622. width: 90,
  623. height: 50,
  624. justifyContent: 'center',
  625. alignItems: 'center',
  626. },
  627. changeBoxBtn: {
  628. width: 110,
  629. height: 60,
  630. justifyContent: 'center',
  631. alignItems: 'center',
  632. },
  633. funBoxText: {
  634. fontSize: 12,
  635. color: '#fff',
  636. fontWeight: '500',
  637. },
  638. changeBoxText: {
  639. fontSize: 12,
  640. color: '#000',
  641. fontWeight: '500',
  642. },
  643. disabled: { opacity: 0.3 },
  644. bottomBar: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 69, paddingHorizontal: 5 },
  645. bottomBtns: { flexDirection: 'row', height: 64, alignItems: 'center', justifyContent: 'space-around' },
  646. btnItem: { flex: 1, marginHorizontal: 6 },
  647. btnBg: { width: '100%', height: 54, justifyContent: 'center', alignItems: 'center' },
  648. btnText: { fontSize: 14, fontWeight: 'bold', color: '#fff' },
  649. btnPrice: { fontSize: 9, color: '#fff' },
  650. });