| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- import { LEVEL_MAP } from '@/constants/config';
- import { Images } from '@/constants/images';
- import { countRecordsAfterLastLevel, getBuyRecord } from '@/services/award';
- import { Image, ImageBackground } from 'expo-image';
- import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
- import { ActivityIndicator, Dimensions, FlatList, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
- const { width, height } = Dimensions.get('window');
- const TABS = [
- { title: '全部', value: '' },
- { title: '超神款', value: 'A' },
- { title: '欧皇款', value: 'B' },
- { title: '隐藏款', value: 'C' },
- { title: '普通款', value: 'D' }
- ];
- interface RecordModalProps {
- poolId: string;
- }
- export interface RecordModalRef {
- show: () => void;
- close: () => void;
- }
- export const RecordModal = forwardRef<RecordModalRef, RecordModalProps>(({ poolId }, ref) => {
- const [visible, setVisible] = useState(false);
- const [data, setData] = useState<any[]>([]);
- const [activeTab, setActiveTab] = useState(TABS[0]);
- const [loading, setLoading] = useState(false);
- const [refreshing, setRefreshing] = useState(false);
- const [lastId, setLastId] = useState<string | undefined>(undefined);
- const [taggingA, setTaggingA] = useState(0);
- const [taggingB, setTaggingB] = useState(0);
- useImperativeHandle(ref, () => ({
- show: () => {
- setVisible(true);
- refresh();
- },
- close: () => setVisible(false)
- }));
- const refresh = () => {
- setLastId(undefined);
- setData([]);
- loadData(true);
- loadStats();
- };
- const loadStats = async () => {
- try {
- const resA = await countRecordsAfterLastLevel({ levelEnumList: ['A'], poolId });
- setTaggingA(resA.data || 0); // Check API structure: {data: number}
- const resB = await countRecordsAfterLastLevel({ levelEnumList: ['B'], poolId });
- setTaggingB(resB.data || 0);
- } catch (e) {
- console.error('Failed to load stats', e);
- }
- };
- const loadData = async (isRefresh = false) => {
- if (loading) return;
- setLoading(true);
- try {
- const currentLastId = isRefresh ? undefined : lastId;
- const res = await getBuyRecord(poolId, currentLastId, activeTab.value as any);
- if (res && res.length > 0) {
- setData(prev => isRefresh ? res : [...prev, ...res]);
- setLastId(res[res.length - 1].id);
- }
- } catch (e) {
- console.error('Failed to load records', e);
- }
- setLoading(false);
- setRefreshing(false);
- };
- const handleTabChange = (tab: any) => {
- setActiveTab(tab);
- // Reset and reload
- setLastId(undefined);
- setData([]);
- // We can't immediately call loadData due to state update async nature,
- // but let's assume useEffect or immediate call with arg works.
- // Actually best to useEffect on activeTab change?
- // Or specific effect for tab
- // Let's do explicit reload in effect or here
- // setData is sync-ish in RN usually batching, let's use effect.
- };
- useEffect(() => {
- if (visible) {
- setLastId(undefined);
- setData([]);
- loadData(true);
- }
- }, [activeTab]);
- const renderItem = ({ item }: { item: any }) => (
- <View style={styles.item}>
- <Image source={{ uri: item.avatar || Images.common.defaultAvatar }} style={styles.avatar} />
- <Text style={styles.nickname} numberOfLines={1}>{item.nickname}</Text>
-
- {/* Level Icon - Assuming we have images for level text similar to legacy */}
- {/* Legacy: :src="LEVEL_MAP[item.level].titleText" */}
- {/* We don't have titleText in our config yet, need to map or use text */}
- {/* For now use Text with color */}
- <View style={[styles.levelTag, { borderColor: LEVEL_MAP[item.level]?.color || '#000' }]}>
- <Text style={[styles.levelText, { color: LEVEL_MAP[item.level]?.color || '#000' }]}>
- {LEVEL_MAP[item.level]?.title}
- </Text>
- </View>
- <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
- <Text style={styles.countText}>×{item.lastCount}</Text>
- <Image source={{ uri: item.cover }} style={styles.itemImage} />
- </View>
- );
- return (
- <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
- <View style={styles.overlay}>
- <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={() => setVisible(false)} />
- <ImageBackground
- source={{ uri: Images.box.detail.recordBg }}
- style={styles.container}
- resizeMode="stretch"
- >
- {/* Header */}
- <View style={styles.header}>
- <Text style={styles.title}>购买记录</Text>
- <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
- {/* Close Icon or Text */}
- <Text style={{ fontSize: 20 }}>×</Text>
- </TouchableOpacity>
- </View>
- {/* Stats Banner */}
- <ImageBackground
- source={{ uri: Images.box.detail.taggingBg }}
- style={styles.statsBanner}
- resizeMode="stretch"
- >
- <View style={styles.statItem}>
- <Text style={styles.statNum}>{taggingA}</Text>
- <Text style={styles.statLabel}>发未出</Text>
- <Text style={[styles.statLevel, { color: LEVEL_MAP.A.color }]}>超神款</Text>
- </View>
- <View style={styles.statItem}>
- <Text style={styles.statNum}>{taggingB}</Text>
- <Text style={styles.statLabel}>发未出</Text>
- <Text style={[styles.statLevel, { color: LEVEL_MAP.B.color }]}>欧皇款</Text>
- </View>
- </ImageBackground>
- {/* Tabs */}
- <View style={styles.tabs}>
- {TABS.map(tab => (
- <TouchableOpacity
- key={tab.value}
- style={[styles.tab, activeTab.value === tab.value && styles.activeTab]}
- onPress={() => handleTabChange(tab)}
- >
- <Text style={[styles.tabText, activeTab.value === tab.value && styles.activeTabText]}>
- {tab.title}
- </Text>
- </TouchableOpacity>
- ))}
- </View>
- {/* List */}
- <FlatList
- data={data}
- renderItem={renderItem}
- keyExtractor={(item, index) => item.id || String(index)}
- style={styles.list}
- contentContainerStyle={{ paddingBottom: 20 }}
- onEndReached={() => loadData(false)}
- onEndReachedThreshold={0.5}
- ListEmptyComponent={
- !loading ? <Text style={styles.emptyText}>暂无记录</Text> : null
- }
- ListFooterComponent={loading ? <ActivityIndicator color="#000" /> : null}
- />
- </ImageBackground>
- </View>
- </Modal>
- );
- });
- const styles = StyleSheet.create({
- overlay: {
- flex: 1,
- backgroundColor: 'rgba(0,0,0,0.5)',
- justifyContent: 'flex-end',
- },
- mask: {
- flex: 1,
- },
- container: {
- height: height * 0.7,
- paddingTop: 0,
- backgroundColor: '#fff',
- borderTopLeftRadius: 15,
- borderTopRightRadius: 15,
- overflow: 'hidden',
- },
- header: {
- height: 50,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 10,
- },
- title: {
- fontSize: 18,
- fontWeight: 'bold',
- },
- closeBtn: {
- position: 'absolute',
- right: 20,
- top: 15,
- width: 30,
- height: 30,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: '#eee',
- borderRadius: 15,
- },
- statsBanner: {
- width: '90%',
- alignSelf: 'center',
- height: 50,
- flexDirection: 'row',
- justifyContent: 'space-around',
- alignItems: 'center',
- marginBottom: 10,
- },
- statItem: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- statNum: { fontSize: 16, fontWeight: 'bold', color: '#fff', textShadowColor:'#000', textShadowRadius:1 },
- statLabel: { fontSize: 12, color: '#ddd', marginHorizontal: 5 },
- statLevel: { fontSize: 12, fontWeight: 'bold' },
-
- tabs: {
- flexDirection: 'row',
- justifyContent: 'space-around',
- paddingHorizontal: 10,
- marginBottom: 10,
- backgroundColor: 'rgba(255,255,255,0.1)',
- paddingVertical: 5,
- },
- tab: {
- paddingVertical: 5,
- paddingHorizontal: 10,
- borderRadius: 5,
- backgroundColor: '#eee',
- },
- activeTab: {
- backgroundColor: '#FEC433',
- },
- tabText: { fontSize: 12, color: '#666' },
- activeTabText: { color: '#000', fontWeight: 'bold' },
- list: {
- flex: 1,
- paddingHorizontal: 15,
- },
- item: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: '#FFF7E3',
- marginBottom: 10,
- padding: 5,
- borderRadius: 8,
- height: 60,
- },
- avatar: { width: 40, height: 40, borderRadius: 20 },
- nickname: { flex: 1, marginLeft: 10, fontSize: 12, color: '#333' },
- levelTag: {
- borderWidth: 1,
- paddingHorizontal: 4,
- borderRadius: 4,
- marginHorizontal: 5
- },
- levelText: { fontSize: 10, fontWeight: 'bold' },
- itemName: { width: 80, fontSize: 12, color: '#FEC433', marginHorizontal: 5 },
- countText: { fontSize: 12, color: '#000', fontWeight: 'bold', marginRight: 5 },
- itemImage: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#fff' },
- emptyText: { textAlign: 'center', marginTop: 20, color: '#999' }
- });
|