| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- import { useLocalSearchParams, useRouter } from 'expo-router';
- import React, { useCallback, useEffect, useRef, useState } from 'react';
- import {
- ActivityIndicator,
- Alert,
- Dimensions,
- ImageBackground,
- ScrollView,
- StatusBar,
- StyleSheet,
- Text,
- TouchableOpacity,
- View
- } from 'react-native';
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
- import { RecordModal, RecordModalRef } from '@/app/award-detail/components/RecordModal';
- import { BoxSelectionModal, BoxSelectionModalRef } from '@/components/award-detail-yfs/BoxSelectionModal';
- import FirstLast from '@/components/award-detail-yfs/FirstLast';
- import { NumSelectionModal, NumSelectionModalRef } from '@/components/award-detail-yfs/NumSelectionModal';
- import ProductListYfs from '@/components/award-detail-yfs/ProductListYfs';
- import ProductSwiper from '@/components/award-detail-yfs/ProductSwiper';
- import PurchaseBar from '@/components/award-detail-yfs/PurchaseBar';
- import { RuleModal, RuleModalRef } from '@/components/award-detail-yfs/RuleModal';
- import { Images } from '@/constants/images';
- import { getBoxDetail, getNextBox, getPoolDetail, getPreBox, lockBox, poolIn, poolOut, unlockBox } from '@/services/award';
- import { CheckoutModal, CheckoutModalRef } from '../award-detail/components/CheckoutModal';
- const { width } = Dimensions.get('window');
- const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#1a1a2e' },
- background: { flex: 1 },
- loading: { flex: 1, backgroundColor: '#1a1a2e', justifyContent: 'center', alignItems: 'center' },
- nav: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: 15,
- height: 90,
- zIndex: 100,
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- },
- backBtn: { width: 40 },
- backText: { color: '#fff', fontSize: 24 },
- navTitle: { color: '#fff', fontSize: 16, fontWeight: 'bold', maxWidth: '60%' },
- placeholder: { width: 40 },
- scrollView: { flex: 1 },
-
- detailWrapper: {
- position: 'relative',
- minHeight: 800,
- paddingBottom: 100,
- },
- mainGoodsSection: {
- width: '100%',
- height: 504,
- position: 'relative',
- zIndex: 1,
- },
- mainGoodsSectionBtext: {
- width: '100%',
- height: 74,
- marginTop: -10,
- zIndex: 2,
- },
- positionBgLeftBg: {
- position: 'absolute',
- left: 0,
- top: 225,
- width: 32,
- height: 188,
- zIndex: 2,
- },
- positionBgRightBg: {
- position: 'absolute',
- right: 0,
- top: 225,
- width: 32,
- height: 188,
- zIndex: 2,
- },
- positionBut: {
- position: 'absolute',
- zIndex: 10,
- width: 35,
- height: 34,
- },
- btnBg: {
- width: '100%',
- height: '100%',
- justifyContent: 'center',
- alignItems: 'center',
- },
- slantedL: {
- color: '#fff',
- fontSize: 12,
- fontWeight: 'bold',
- transform: [{ rotate: '14deg' }],
- marginTop: -2,
- textShadowColor: '#000',
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- },
- slantedR: {
- color: '#fff',
- fontSize: 12,
- fontWeight: 'bold',
- transform: [{ rotate: '-16deg' }],
- marginTop: -2,
- textShadowColor: '#000',
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- },
- positionRule: {
- top: 256,
- left: 0,
- },
- positionStore: {
- top: 256,
- right: 0,
- },
- positionRecord: {
- top: 300,
- left: 0,
- },
- positionRefresh: {
- top: 345,
- right: 0,
- },
- bottomBar: {
- position: 'absolute',
- bottom: 0,
- left: 0,
- right: 0,
- zIndex: 200,
- }
- });
- export default function AwardDetailYfsScreen() {
- const { poolId } = useLocalSearchParams<{ poolId: string }>();
- const router = useRouter();
- const insets = useSafeAreaInsets();
- const [loading, setLoading] = useState(true);
- const [data, setData] = useState<any>(null);
- const [scrollTop, setScrollTop] = useState(0);
- const [currentBox, setCurrentBox] = useState<any>(null);
- const boxRef = useRef<BoxSelectionModalRef>(null);
- const numRef = useRef<NumSelectionModalRef>(null);
- const checkoutRef = useRef<CheckoutModalRef>(null);
- const ruleRef = useRef<RuleModalRef>(null);
- const recordRef = useRef<RecordModalRef>(null);
- const getSafePoolId = () => Array.isArray(poolId) ? poolId[0] : poolId;
- const loadBox = async (boxNum?: string) => {
- try {
- const id = getSafePoolId();
- console.log(`[DEBUG-ICHIBAN] loadBox called for pool: ${id}, boxNum: ${boxNum}`);
- const res = await getBoxDetail(id, boxNum);
- console.log(`[DEBUG-ICHIBAN] getBoxDetail res:`, res ? `ID: ${res.number}, UsedStatKeys: ${Object.keys(res.usedStat || {})}` : 'null');
-
- if (res) setCurrentBox(res);
- } catch (error) {
- console.error('[DEBUG-ICHIBAN] Failed to load box', error);
- }
- };
- const loadData = useCallback(async () => {
- if (!poolId) return;
- setLoading(true);
- try {
- const safePoolId = getSafePoolId(); // calling getSafePoolId inside loadData too to be sure
- console.log(`[DEBUG-ICHIBAN] loadData called for pool: ${safePoolId}`);
-
- const res = await getPoolDetail(safePoolId);
- setData(res);
- console.log(`[DEBUG-ICHIBAN] Pool detail loaded, calling loadBox...`);
- await loadBox(); // Initial box
- } catch (error) {
- console.error('[DEBUG-ICHIBAN] Failed to load detail', error);
- } finally {
- setLoading(false);
- // Assuming setRefreshing is defined elsewhere or will be added by the user
- // setRefreshing(false);
- }
- }, [poolId]);
- useEffect(() => {
- if (poolId) {
- poolIn(poolId);
- return () => {
- poolOut(poolId);
- };
- }
- }, [poolId]);
- const handleBoxSelect = (box: any) => {
- // Must load full detail to get usedStat
- loadBox(box.number);
- };
- const handlePrevBox = async () => {
- if (!currentBox) return;
- try {
- const res = await getPreBox(getSafePoolId(), currentBox.number);
- if (res) setCurrentBox(res);
- else Alert.alert('提示', '已经是第一盒了');
- } catch (e) { Alert.alert('提示', '切换失败'); }
- };
- const handleNextBox = async () => {
- if (!currentBox) return;
- try {
- const res = await getNextBox(getSafePoolId(), currentBox.number);
- if (res) setCurrentBox(res);
- else Alert.alert('提示', '已经是最后一盒了');
- } catch (e) { Alert.alert('提示', '切换失败'); }
- };
- const handlePay = ({ previewRes, chooseNum, boxNum }: any) => {
- checkoutRef.current?.show(chooseNum.length, previewRes, boxNum, chooseNum);
- };
- useEffect(() => {
- loadData();
- }, [loadData]);
- const headerBg = scrollTop > 50 ? '#333' : 'transparent';
- if (loading) {
- return (
- <View style={styles.loading}>
- <ActivityIndicator color="#fff" />
- </View>
- );
- }
- return (
- <View style={styles.container}>
- <StatusBar barStyle="light-content" />
- <ImageBackground source={{ uri: Images.common.indexBg }} style={styles.background} resizeMode="cover">
-
- {/* Navigation Bar */}
- <View style={[styles.nav, { paddingTop: insets.top, backgroundColor: headerBg }]}>
- <TouchableOpacity onPress={() => router.back()} style={styles.backBtn}>
- <Text style={styles.backText}>←</Text>
- </TouchableOpacity>
- <Text style={styles.navTitle} numberOfLines={1}>{data?.name || '详情'}</Text>
- <View style={styles.placeholder} />
- </View>
- <ScrollView
- style={styles.scrollView}
- onScroll={(e) => setScrollTop(e.nativeEvent.contentOffset.y)}
- scrollEventThrottle={16}
- showsVerticalScrollIndicator={false}
- >
- <View style={styles.detailWrapper}>
- {/* Main Goods Section */}
- <ImageBackground
- source={{ uri: Images.box.detail.mainGoodsSection }}
- style={styles.mainGoodsSection}
- resizeMode="stretch"
- >
- <View style={{ height: 180 }} />
-
- <ProductSwiper
- box={currentBox} // Pass current box
- products={data?.luckGoodsList?.map((item: any) => ({
- cover: item.spu.cover,
- name: item.spu.name,
- level: item.level,
- quantity: item.quantity,
- id: item.id, // Ensure IDs are passed
- spu: item.spu
- })) || []}
- />
-
- <ImageBackground
- source={{ uri: Images.box.detail.positionBgleftBg }}
- style={styles.positionBgLeftBg}
- resizeMode="contain"
- />
- <ImageBackground
- source={{ uri: Images.box.detail.positionBgRightBg }}
- style={styles.positionBgRightBg}
- resizeMode="contain"
- />
- </ImageBackground>
- {/* Bottom Text Graphic */}
- <ImageBackground
- source={{ uri: Images.box.detail.mainGoodsSectionBtext }}
- style={styles.mainGoodsSectionBtext}
- resizeMode="contain"
- />
- {/* FirstLast (Box Info & Nav) */}
- <FirstLast
- data={data}
- box={currentBox}
- onPrevBox={handlePrevBox}
- onNextBox={handleNextBox}
- onChangeBox={() => boxRef.current?.show()}
- onProductClick={(spu) => console.log(spu)}
- />
- {/* Product List (A/B/C/D) */}
- <ProductListYfs
- products={data?.luckGoodsList || []}
- poolId={getSafePoolId()}
- box={currentBox}
- />
- {/* Floating Buttons */}
- <TouchableOpacity style={[styles.positionBut, styles.positionRule]} onPress={() => ruleRef.current?.show()}>
- <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.btnBg} resizeMode="contain">
- <Text style={styles.slantedL}>规则</Text>
- </ImageBackground>
- </TouchableOpacity>
- <TouchableOpacity style={[styles.positionBut, styles.positionRecord]} onPress={() => recordRef.current?.show()}>
- <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.btnBg} resizeMode="contain">
- <Text style={styles.slantedL}>记录</Text>
- </ImageBackground>
- </TouchableOpacity>
- <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store')}>
- <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
- <Text style={styles.slantedR}>仓库</Text>
- </ImageBackground>
- </TouchableOpacity>
-
- {/* Lock Button */}
- <TouchableOpacity style={[styles.positionBut, { top: 300, right: 0 }]} onPress={() => {
- if (!currentBox) return;
- const isLocked = currentBox.lockTime && new Date(currentBox.lockTime).getTime() > Date.now();
-
- Alert.alert('提示', isLocked ? '是否解锁盒子?' : '是否锁定盒子(锁定后他人无法购买)?', [
- { text: '取消', style: 'cancel' },
- { text: '确定', onPress: async () => {
- try {
- const poolId = getSafePoolId();
- let success = false;
- if (isLocked) {
- success = await unlockBox(poolId, currentBox.number);
- } else {
- success = await lockBox(poolId, currentBox.number);
- }
-
- if (success) {
- Alert.alert('成功', isLocked ? '解锁成功' : '锁定成功');
- loadBox(currentBox.number); // Refresh
- }
- // System handles error msg via interceptor, no need for manual alert
- } catch (e) {
- // console.error(e); // Silent error
- }
- }}
- ]);
- }}>
- <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
- <Text style={styles.slantedR}>{currentBox?.lockTime && new Date(currentBox.lockTime).getTime() > Date.now() ? '解锁' : '锁盒'}</Text>
- </ImageBackground>
- </TouchableOpacity>
- <TouchableOpacity style={[styles.positionBut, styles.positionRefresh]} onPress={() => loadBox(currentBox?.number)}>
- <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
- <Text style={styles.slantedR}>刷新</Text>
- </ImageBackground>
- </TouchableOpacity>
- </View>
- <View style={{ height: 100 }} />
- </ScrollView>
- <View style={styles.bottomBar}>
- <PurchaseBar
- price={data?.price || 0}
- specialPrice={data?.specialPrice}
- specialPriceFive={data?.specialPriceFive}
- onBuy={(count) => {
- if (!currentBox) {
- boxRef.current?.show();
- } else {
- numRef.current?.show(currentBox);
- }
- }}
- onBuyMany={() => {
- if (!currentBox) {
- boxRef.current?.show();
- } else {
- numRef.current?.show(currentBox);
- }
- }}
- />
- </View>
- {/* Modals */}
- <BoxSelectionModal
- ref={boxRef}
- poolId={getSafePoolId()}
- onSelect={handleBoxSelect}
- />
- <NumSelectionModal
- ref={numRef}
- poolId={getSafePoolId()}
- onPay={handlePay}
- />
- <CheckoutModal
- ref={checkoutRef}
- poolId={getSafePoolId()}
- data={data}
- onSuccess={(res) => {
- console.log('Success', res);
- loadData(); // Refresh
- }}
- />
- <RuleModal ref={ruleRef} />
- <RecordModal ref={recordRef} poolId={getSafePoolId()} />
- </ImageBackground>
- </View>
- );
- }
|