| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- import { Image } from 'expo-image';
- import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
- import {
- ActivityIndicator,
- ImageBackground,
- Modal,
- ScrollView,
- StyleSheet,
- Text,
- TouchableOpacity,
- View,
- } from 'react-native';
- import { Images } from '@/constants/images';
- import { getBoxList } from '@/services/award';
- // 等级筛选标签
- const TABS = [
- { title: '全部', value: '' },
- { title: '超神款', value: 'A' },
- { title: '欧皇款', value: 'B' },
- { title: '隐藏款', value: 'C' },
- { title: '普通款', value: 'D' },
- ];
- interface BoxItem {
- number: string;
- quantity: number;
- leftQuantity: number;
- quantityA: number;
- quantityB: number;
- quantityC: number;
- quantityD: number;
- leftQuantityA: number;
- leftQuantityB: number;
- leftQuantityC: number;
- leftQuantityD: number;
- }
- interface BoxChooseModalProps {
- poolId: string;
- onChoose: (boxNumber: string) => void;
- }
- export interface BoxChooseModalRef {
- show: () => void;
- close: () => void;
- }
- export const BoxChooseModal = forwardRef<BoxChooseModalRef, BoxChooseModalProps>(
- ({ poolId, onChoose }, ref) => {
- const [visible, setVisible] = useState(false);
- const [loading, setLoading] = useState(false);
- const [currentTab, setCurrentTab] = useState(TABS[0]);
- const [boxList, setBoxList] = useState<BoxItem[]>([]);
- const [pageNum, setPageNum] = useState(1);
- const [hasMore, setHasMore] = useState(true);
- const loadData = useCallback(async (page: number, level?: string) => {
- if (page === 1) setLoading(true);
- try {
- const levelValue = level === '' ? undefined : level === 'A' ? 1 : level === 'B' ? 2 : level === 'C' ? 3 : level === 'D' ? 4 : undefined;
- const res = await getBoxList(poolId, levelValue, page, 20);
- if (res && res.records) {
- const newList = res.records.filter((item: BoxItem) => item.leftQuantity > 0);
- if (page === 1) {
- setBoxList(newList);
- } else {
- setBoxList(prev => [...prev, ...newList]);
- }
- setHasMore(res.records.length >= 20);
- setPageNum(page);
- }
- } catch (error) {
- console.error('加载盒子列表失败:', error);
- } finally {
- setLoading(false);
- }
- }, [poolId]);
- useImperativeHandle(ref, () => ({
- show: () => {
- setVisible(true);
- setCurrentTab(TABS[0]);
- setPageNum(1);
- loadData(1, '');
- },
- close: () => {
- setVisible(false);
- setBoxList([]);
- },
- }));
- const close = () => {
- setVisible(false);
- };
- const clickTab = (tab: typeof TABS[0]) => {
- setCurrentTab(tab);
- setPageNum(1);
- loadData(1, tab.value);
- };
- const loadMore = () => {
- if (!loading && hasMore) {
- loadData(pageNum + 1, currentTab.value);
- }
- };
- const choose = (item: BoxItem) => {
- if (item.leftQuantity <= 0) return;
- onChoose(item.number);
- close();
- };
- const handleScroll = (event: any) => {
- const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
- const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 50;
- if (isCloseToBottom) {
- loadMore();
- }
- };
- return (
- <Modal visible={visible} transparent animationType="slide" onRequestClose={close}>
- <View style={styles.overlay}>
- <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={close} />
- <ImageBackground source={{ uri: Images.box.detail.recordBg }} style={styles.container} resizeMode="cover">
- {/* 标题 */}
- <View style={styles.titleSection}>
- <Text style={styles.title}>换盒</Text>
- <TouchableOpacity style={styles.closeBtn} onPress={close}>
- <Text style={styles.closeText}>×</Text>
- </TouchableOpacity>
- </View>
- {/* 标签页 */}
- <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.tabsScroll}>
- <View style={styles.tabs}>
- {TABS.map((tab) => (
- <TouchableOpacity
- key={tab.value}
- style={[styles.tabItem, currentTab.value === tab.value && styles.tabItemActive]}
- onPress={() => clickTab(tab)}
- >
- <Text style={[styles.tabText, currentTab.value === tab.value && styles.tabTextActive]}>
- {tab.title}
- </Text>
- </TouchableOpacity>
- ))}
- </View>
- </ScrollView>
- {/* 盒子列表 */}
- <ScrollView
- style={styles.listScroll}
- showsVerticalScrollIndicator={false}
- onScroll={handleScroll}
- scrollEventThrottle={16}
- >
- <View style={styles.listContainer}>
- {loading && pageNum === 1 ? (
- <View style={styles.loadingBox}>
- <ActivityIndicator color="#FFC900" size="large" />
- </View>
- ) : boxList.length === 0 ? (
- <View style={styles.emptyBox}>
- <Text style={styles.emptyText}>暂无可用盒子</Text>
- </View>
- ) : (
- boxList.map((item, index) => (
- <TouchableOpacity
- key={item.number}
- style={styles.boxItem}
- onPress={() => choose(item)}
- activeOpacity={0.8}
- >
- <View style={styles.itemIndex}>
- <Text style={styles.itemIndexText}>{index + 1}</Text>
- </View>
- <View style={styles.itemContent}>
- <View style={styles.leftSection}>
- <Image
- source={{ uri: Images.box.detail.boxIcon }}
- style={styles.boxIcon}
- contentFit="contain"
- />
- <Text style={styles.leftText}>剩{item.leftQuantity}发</Text>
- </View>
- <View style={styles.divider} />
- <View style={styles.levelList}>
- <View style={styles.levelBox}>
- <Image source={{ uri: Images.box.detail.levelTextA }} style={styles.levelIcon} contentFit="contain" />
- <View style={styles.numBox}>
- <Text style={styles.currentNum}>{item.leftQuantityA}</Text>
- <Text style={styles.totalNum}>/{item.quantityA}</Text>
- </View>
- </View>
- <View style={styles.levelBox}>
- <Image source={{ uri: Images.box.detail.levelTextB }} style={styles.levelIcon} contentFit="contain" />
- <View style={styles.numBox}>
- <Text style={styles.currentNum}>{item.leftQuantityB}</Text>
- <Text style={styles.totalNum}>/{item.quantityB}</Text>
- </View>
- </View>
- <View style={styles.levelBox}>
- <Image source={{ uri: Images.box.detail.levelTextC }} style={styles.levelIcon} contentFit="contain" />
- <View style={styles.numBox}>
- <Text style={styles.currentNum}>{item.leftQuantityC}</Text>
- <Text style={styles.totalNum}>/{item.quantityC}</Text>
- </View>
- </View>
- <View style={styles.levelBox}>
- <Image source={{ uri: Images.box.detail.levelTextD }} style={styles.levelIcon} contentFit="contain" />
- <View style={styles.numBox}>
- <Text style={styles.currentNum}>{item.leftQuantityD}</Text>
- <Text style={styles.totalNum}>/{item.quantityD}</Text>
- </View>
- </View>
- </View>
- </View>
- </TouchableOpacity>
- ))
- )}
- {loading && pageNum > 1 && (
- <View style={styles.loadMoreBox}>
- <ActivityIndicator color="#FFC900" size="small" />
- </View>
- )}
- </View>
- </ScrollView>
- </ImageBackground>
- </View>
- </Modal>
- );
- }
- );
- const styles = StyleSheet.create({
- overlay: {
- flex: 1,
- backgroundColor: 'rgba(0,0,0,0.5)',
- justifyContent: 'flex-end',
- },
- mask: { flex: 1 },
- container: {
- borderTopLeftRadius: 15,
- borderTopRightRadius: 15,
- paddingTop: 15,
- paddingBottom: 34,
- maxHeight: '80%',
- },
- titleSection: {
- alignItems: 'center',
- paddingVertical: 15,
- position: 'relative',
- },
- title: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- textShadowColor: '#000',
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 2,
- },
- closeBtn: {
- position: 'absolute',
- right: 15,
- top: 10,
- width: 24,
- height: 24,
- backgroundColor: '#ebebeb',
- borderRadius: 12,
- justifyContent: 'center',
- alignItems: 'center',
- },
- closeText: { fontSize: 18, color: '#a2a2a2', marginTop: -2 },
- tabsScroll: {
- maxHeight: 40,
- marginHorizontal: 10,
- marginBottom: 10,
- },
- tabs: {
- flexDirection: 'row',
- },
- tabItem: {
- paddingHorizontal: 12,
- paddingVertical: 6,
- backgroundColor: 'rgba(255,255,255,0.2)',
- borderRadius: 15,
- marginRight: 8,
- },
- tabItemActive: {
- backgroundColor: '#FFC900',
- },
- tabText: {
- fontSize: 12,
- color: '#fff',
- },
- tabTextActive: {
- color: '#000',
- fontWeight: 'bold',
- },
- listScroll: {
- height: 400,
- marginHorizontal: 10,
- },
- listContainer: {
- backgroundColor: '#f3f3f3',
- borderWidth: 2,
- borderColor: '#000',
- padding: 15,
- borderRadius: 4,
- },
- loadingBox: {
- height: 200,
- justifyContent: 'center',
- alignItems: 'center',
- },
- emptyBox: {
- height: 200,
- justifyContent: 'center',
- alignItems: 'center',
- },
- emptyText: {
- fontSize: 14,
- color: '#999',
- },
- boxItem: {
- backgroundColor: '#fff',
- borderWidth: 3,
- borderColor: '#000',
- borderRadius: 4,
- marginBottom: 10,
- position: 'relative',
- shadowColor: '#FFC900',
- shadowOffset: { width: 0, height: 3 },
- shadowOpacity: 1,
- shadowRadius: 0,
- elevation: 3,
- },
- itemIndex: {
- position: 'absolute',
- left: 0,
- top: 0,
- width: 22,
- height: 22,
- borderWidth: 1.5,
- borderColor: '#000',
- backgroundColor: '#fff',
- justifyContent: 'center',
- alignItems: 'center',
- zIndex: 1,
- },
- itemIndexText: {
- fontSize: 14,
- fontWeight: 'bold',
- color: '#000',
- },
- itemContent: {
- flexDirection: 'row',
- alignItems: 'center',
- padding: 12,
- },
- leftSection: {
- width: 74,
- alignItems: 'center',
- },
- boxIcon: {
- width: 24,
- height: 24,
- },
- leftText: {
- fontSize: 12,
- color: '#000',
- marginTop: 4,
- },
- divider: {
- width: 1,
- height: 40,
- backgroundColor: '#dcdad3',
- opacity: 0.5,
- marginRight: 10,
- },
- levelList: {
- flex: 1,
- flexDirection: 'row',
- flexWrap: 'wrap',
- },
- levelBox: {
- width: '50%',
- flexDirection: 'row',
- alignItems: 'center',
- marginBottom: 4,
- },
- levelIcon: {
- width: 45,
- height: 16,
- marginRight: 4,
- },
- numBox: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- currentNum: {
- fontSize: 13,
- fontWeight: '500',
- color: '#000',
- },
- totalNum: {
- fontSize: 11,
- color: '#666',
- },
- loadMoreBox: {
- paddingVertical: 15,
- alignItems: 'center',
- },
- });
|