WinRecordModal.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import { Images } from '@/constants/images';
  2. import Service from '@/services/weal';
  3. import { ImageBackground } from 'expo-image';
  4. import React, { forwardRef, useImperativeHandle, useState } from 'react';
  5. import { FlatList, Image, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  6. export interface WinRecordModalRef {
  7. show: () => void;
  8. close: () => void;
  9. }
  10. export const WinRecordModal = forwardRef<WinRecordModalRef>((_, ref) => {
  11. const [visible, setVisible] = useState(false);
  12. const [data, setData] = useState<any[]>([]);
  13. const [loading, setLoading] = useState(false);
  14. const [isRefreshing, setIsRefreshing] = useState(false);
  15. const [current, setCurrent] = useState(1);
  16. const size = 20;
  17. const [hasMore, setHasMore] = useState(true);
  18. useImperativeHandle(ref, () => ({
  19. show: () => {
  20. setVisible(true);
  21. refresh();
  22. },
  23. close: () => setVisible(false),
  24. }));
  25. const getData = async (pageNum: number, isRefresh: boolean = false) => {
  26. if (loading) return;
  27. setLoading(true);
  28. try {
  29. const res = await Service.prizeResult({ current: pageNum, size });
  30. if (res && res.records) {
  31. if (isRefresh) {
  32. setData(res.records);
  33. } else {
  34. setData(prev => [...prev, ...res.records]);
  35. }
  36. if (res.records.length < size) {
  37. setHasMore(false);
  38. } else {
  39. setHasMore(true);
  40. }
  41. }
  42. } catch (error) {
  43. console.error("Failed to fetch records", error);
  44. } finally {
  45. setLoading(false);
  46. setIsRefreshing(false);
  47. }
  48. };
  49. const refresh = () => {
  50. setCurrent(1);
  51. setIsRefreshing(true);
  52. setHasMore(true);
  53. getData(1, true);
  54. };
  55. const loadMore = () => {
  56. if (!loading && hasMore) {
  57. const next = current + 1;
  58. setCurrent(next);
  59. getData(next);
  60. }
  61. };
  62. if (!visible) return null;
  63. return (
  64. <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
  65. <View style={styles.overlay}>
  66. <View style={styles.contentContainer}>
  67. <ImageBackground source={{ uri: Images.mine.dialogContentBg }} style={styles.contentBg} resizeMode="stretch">
  68. <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
  69. <Image source={{ uri: Images.common.closeBut }} style={styles.closeIcon} />
  70. </TouchableOpacity>
  71. <View style={styles.titleBox}>
  72. <Text style={styles.title}>中奖记录</Text>
  73. </View>
  74. <View style={styles.listBox}>
  75. {data.length > 0 ? (
  76. <FlatList
  77. data={data}
  78. keyExtractor={(item, index) => index.toString()}
  79. renderItem={({ item }) => (
  80. <View style={styles.item}>
  81. <Image source={{ uri: item.cover }} style={styles.itemImg} />
  82. <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
  83. <View style={styles.userInfo}>
  84. <ImageBackground source={{ uri: Images.common.indexBg }} style={styles.avatarBg} resizeMode="cover">
  85. <Image source={{ uri: item.avatar || Images.common.defaultAvatar }} style={styles.avatar} />
  86. </ImageBackground>
  87. <Text style={styles.nickname} numberOfLines={1}>{item.nickname}</Text>
  88. </View>
  89. </View>
  90. )}
  91. onEndReached={loadMore}
  92. onEndReachedThreshold={0.1}
  93. refreshing={isRefreshing}
  94. onRefresh={refresh}
  95. />
  96. ) : (
  97. <View style={styles.emptyContainer}>
  98. <Text style={styles.emptyText}>暂无记录</Text>
  99. </View>
  100. )}
  101. </View>
  102. </ImageBackground>
  103. </View>
  104. </View>
  105. </Modal>
  106. );
  107. });
  108. const styles = StyleSheet.create({
  109. overlay: {
  110. flex: 1,
  111. backgroundColor: 'rgba(0,0,0,0.6)',
  112. justifyContent: 'center',
  113. alignItems: 'center',
  114. },
  115. contentContainer: {
  116. width: '100%',
  117. paddingHorizontal: 10,
  118. alignItems: 'center',
  119. },
  120. contentBg: {
  121. width: '100%', // Adjust based on your design, usually full width - padding
  122. height: 367, // 734rpx
  123. paddingTop: 69, // 138rpx
  124. position: 'relative',
  125. },
  126. closeBtn: {
  127. position: 'absolute',
  128. right: 15,
  129. top: 25,
  130. zIndex: 1,
  131. },
  132. closeIcon: {
  133. width: 30,
  134. height: 30,
  135. },
  136. titleBox: {
  137. position: 'absolute',
  138. top: 5,
  139. left: 0,
  140. right: 0,
  141. alignItems: 'center',
  142. },
  143. title: {
  144. fontSize: 16,
  145. fontWeight: 'bold',
  146. color: '#fff',
  147. marginTop: 28,
  148. textShadowColor: '#000',
  149. textShadowOffset: { width: 1, height: 1 },
  150. textShadowRadius: 1,
  151. },
  152. listBox: {
  153. flex: 1,
  154. paddingHorizontal: 20,
  155. paddingBottom: 20,
  156. marginTop: 20, // push down below header area
  157. },
  158. item: {
  159. flexDirection: 'row',
  160. alignItems: 'center',
  161. paddingVertical: 10,
  162. borderBottomWidth: 1,
  163. borderBottomColor: '#999',
  164. },
  165. itemImg: {
  166. width: 50, // 100rpx
  167. height: 50,
  168. borderRadius: 25,
  169. borderWidth: 2,
  170. borderColor: '#090909',
  171. marginRight: 10,
  172. },
  173. itemName: {
  174. fontSize: 12,
  175. color: '#000',
  176. fontWeight: 'bold',
  177. flex: 1,
  178. marginRight: 11,
  179. },
  180. userInfo: {
  181. width: 82, // 164rpx
  182. alignItems: 'flex-end',
  183. },
  184. avatarBg: {
  185. width: 32, // 64rpx
  186. height: 32,
  187. marginBottom: 5,
  188. justifyContent: 'center',
  189. alignItems: 'center',
  190. },
  191. avatar: {
  192. width: 32,
  193. height: 32,
  194. borderRadius: 16,
  195. },
  196. nickname: {
  197. fontSize: 10, // font2
  198. color: '#666',
  199. },
  200. emptyContainer: {
  201. flex: 1,
  202. justifyContent: 'center',
  203. alignItems: 'center',
  204. },
  205. emptyText: {
  206. color: '#999',
  207. }
  208. });