WishMaterialModal.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import ServiceAward from '@/services/award';
  2. import { Ionicons } from '@expo/vector-icons';
  3. import React, { forwardRef, useImperativeHandle, useState } from 'react';
  4. import { ActivityIndicator, FlatList, Image, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  5. export interface WishMaterialModalRef {
  6. show: (callback: (selected: any[]) => void) => void;
  7. close: () => void;
  8. }
  9. export const WishMaterialModal = forwardRef<WishMaterialModalRef, {}>((props, ref) => {
  10. const [visible, setVisible] = useState(false);
  11. const [list, setList] = useState<any[]>([]);
  12. const [selectedIds, setSelectedIds] = useState<string[]>([]);
  13. const [loading, setLoading] = useState(false);
  14. const [onConfirm, setOnConfirm] = useState<((selected: any[]) => void) | null>(null);
  15. const [page, setPage] = useState(1);
  16. const [hasMore, setHasMore] = useState(true);
  17. useImperativeHandle(ref, () => ({
  18. show: (callback) => {
  19. setVisible(true);
  20. setOnConfirm(() => callback);
  21. setPage(1);
  22. setList([]);
  23. setSelectedIds([]);
  24. loadData(1);
  25. },
  26. close: () => setVisible(false),
  27. }));
  28. const loadData = async (curPage: number) => {
  29. setLoading(true);
  30. try {
  31. // Fetch inventory/store items
  32. const data = await ServiceAward.getStore(curPage, 20);
  33. const records = (Array.isArray(data) ? data : data?.records) || [];
  34. if (records.length > 0) {
  35. setList(prev => curPage === 1 ? records : [...prev, ...records]);
  36. setHasMore(records.length === 20);
  37. } else {
  38. setList(prev => curPage === 1 ? [] : prev);
  39. setHasMore(false);
  40. }
  41. } catch (error) {
  42. console.error(error);
  43. } finally {
  44. setLoading(false);
  45. }
  46. };
  47. const handleLoadMore = () => {
  48. if (!loading && hasMore) {
  49. const nextPage = page + 1;
  50. setPage(nextPage);
  51. loadData(nextPage);
  52. }
  53. };
  54. const toggleSelect = (id: string) => {
  55. // Multi-select allowed? Usually yes for materials.
  56. setSelectedIds(prev =>
  57. prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
  58. );
  59. };
  60. const handleConfirm = () => {
  61. const selectedGoods = list.filter(item => selectedIds.includes(item.id));
  62. if (onConfirm) {
  63. onConfirm(selectedGoods);
  64. }
  65. setVisible(false);
  66. };
  67. const renderItem = ({ item }: { item: any }) => {
  68. const isSelected = selectedIds.includes(item.id);
  69. return (
  70. <TouchableOpacity style={styles.item} onPress={() => toggleSelect(item.id)}>
  71. <Image source={{ uri: item.spu.cover }} style={styles.itemImg} />
  72. <View style={styles.itemInfo}>
  73. <Text style={styles.itemName} numberOfLines={1}>{item.spu.name}</Text>
  74. <Text style={styles.itemMagic}>价值: {item.magicAmount} 果实</Text>
  75. </View>
  76. <View style={[styles.checkbox, isSelected && styles.checkboxSelected]}>
  77. {isSelected && <Ionicons name="checkmark" size={12} color="#fff" />}
  78. </View>
  79. </TouchableOpacity>
  80. );
  81. };
  82. if (!visible) return null;
  83. return (
  84. <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
  85. <View style={styles.overlay}>
  86. <View style={styles.container}>
  87. <View style={styles.header}>
  88. <Text style={styles.headerTitle}>选择添加材料</Text>
  89. <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
  90. <Ionicons name="close" size={24} color="#333" />
  91. </TouchableOpacity>
  92. </View>
  93. <FlatList
  94. data={list}
  95. renderItem={renderItem}
  96. keyExtractor={item => item.id}
  97. onEndReached={handleLoadMore}
  98. onEndReachedThreshold={0.3}
  99. contentContainerStyle={styles.listContent}
  100. ListFooterComponent={() => loading && <ActivityIndicator />}
  101. ListEmptyComponent={<Text style={styles.emptyText}>暂无可用商品</Text>}
  102. />
  103. <TouchableOpacity style={styles.confirmBtn} onPress={handleConfirm}>
  104. <Text style={styles.confirmText}>确认添加 ({selectedIds.length})</Text>
  105. </TouchableOpacity>
  106. </View>
  107. </View>
  108. </Modal>
  109. );
  110. });
  111. export default WishMaterialModal;
  112. const styles = StyleSheet.create({
  113. overlay: {
  114. flex: 1,
  115. backgroundColor: 'rgba(0,0,0,0.5)',
  116. justifyContent: 'flex-end',
  117. },
  118. container: {
  119. backgroundColor: '#fff',
  120. borderTopLeftRadius: 20,
  121. borderTopRightRadius: 20,
  122. height: '70%',
  123. paddingBottom: 30,
  124. },
  125. header: {
  126. flexDirection: 'row',
  127. justifyContent: 'center',
  128. alignItems: 'center',
  129. padding: 15,
  130. borderBottomWidth: 1,
  131. borderBottomColor: '#eee',
  132. },
  133. headerTitle: {
  134. fontSize: 18,
  135. fontWeight: 'bold',
  136. color: '#333',
  137. },
  138. closeBtn: {
  139. position: 'absolute',
  140. right: 15,
  141. },
  142. listContent: {
  143. padding: 15,
  144. },
  145. item: {
  146. flexDirection: 'row',
  147. alignItems: 'center',
  148. padding: 10,
  149. marginBottom: 10,
  150. backgroundColor: '#f9f9f9',
  151. borderRadius: 8,
  152. },
  153. itemImg: {
  154. width: 60,
  155. height: 60,
  156. borderRadius: 4,
  157. },
  158. itemInfo: {
  159. flex: 1,
  160. marginLeft: 10,
  161. },
  162. itemName: {
  163. fontSize: 14,
  164. color: '#333',
  165. fontWeight: 'bold',
  166. },
  167. itemMagic: {
  168. fontSize: 12,
  169. color: '#666',
  170. marginTop: 5,
  171. },
  172. checkbox: {
  173. width: 22,
  174. height: 22,
  175. borderRadius: 11,
  176. borderWidth: 1,
  177. borderColor: '#ccc',
  178. justifyContent: 'center',
  179. alignItems: 'center',
  180. backgroundColor: '#fff',
  181. },
  182. checkboxSelected: {
  183. backgroundColor: '#FFAD00',
  184. borderColor: '#FFAD00',
  185. },
  186. confirmBtn: {
  187. backgroundColor: '#FFAD00',
  188. marginHorizontal: 20,
  189. paddingVertical: 12,
  190. borderRadius: 25,
  191. alignItems: 'center',
  192. marginTop: 10,
  193. },
  194. confirmText: {
  195. color: '#fff',
  196. fontSize: 16,
  197. fontWeight: 'bold',
  198. },
  199. emptyText: {
  200. textAlign: 'center',
  201. marginTop: 50,
  202. color: '#999',
  203. },
  204. });