BoxPopup.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { Image } from 'expo-image';
  2. import React, { forwardRef, useImperativeHandle, useState } from 'react';
  3. import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  4. import { Images } from '@/constants/images';
  5. interface BoxItem {
  6. boxNumber: number;
  7. leftQuantity: number;
  8. quantity: number;
  9. isCompleted: boolean;
  10. }
  11. interface BoxPopupProps {
  12. onSelect: (item: BoxItem) => void;
  13. }
  14. export interface BoxPopupRef {
  15. open: (list: BoxItem[]) => void;
  16. close: () => void;
  17. }
  18. export const BoxPopup = forwardRef<BoxPopupRef, BoxPopupProps>(({ onSelect }, ref) => {
  19. const [activeBucket, setActiveBucket] = useState(0);
  20. useImperativeHandle(ref, () => ({
  21. open: (data: BoxItem[]) => {
  22. setList(data);
  23. setActiveBucket(0); // Reset to first bucket
  24. setVisible(true);
  25. },
  26. close: () => setVisible(false),
  27. }));
  28. const handleSelect = (item: BoxItem) => {
  29. onSelect(item);
  30. setVisible(false);
  31. };
  32. // Bucket logic
  33. const BUCKET_SIZE = 100;
  34. const buckets = Math.ceil(list.length / BUCKET_SIZE);
  35. const displayList = list.slice(activeBucket * BUCKET_SIZE, (activeBucket + 1) * BUCKET_SIZE);
  36. return (
  37. <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
  38. <View style={styles.overlay}>
  39. <View style={styles.container}>
  40. {/* 标题区域 */}
  41. <View style={styles.titleSection}>
  42. <View style={styles.titleBg}>
  43. <Text style={styles.titleText}>选择盒子</Text>
  44. </View>
  45. <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
  46. <Text style={styles.closeBtnText}>×</Text>
  47. </TouchableOpacity>
  48. </View>
  49. {/* 区间选择器 (仅当数量超过100时显示) */}
  50. {buckets > 1 && (
  51. <View style={styles.bucketContainer}>
  52. <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.bucketScroll}>
  53. {Array.from({ length: buckets }).map((_, index) => {
  54. const start = index * BUCKET_SIZE + 1;
  55. const end = Math.min((index + 1) * BUCKET_SIZE, list.length);
  56. const isActive = activeBucket === index;
  57. return (
  58. <TouchableOpacity
  59. key={index}
  60. style={[styles.bucketItem, isActive && styles.bucketItemActive]}
  61. onPress={() => setActiveBucket(index)}
  62. >
  63. <Text style={[styles.bucketText, isActive && styles.bucketTextActive]}>
  64. {start}~{end}
  65. </Text>
  66. </TouchableOpacity>
  67. );
  68. })}
  69. </ScrollView>
  70. </View>
  71. )}
  72. {/* 盒子列表 */}
  73. <View style={styles.content}>
  74. <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
  75. <View style={styles.grid}>
  76. {displayList.map((item, index) => (
  77. <TouchableOpacity key={item.boxNumber} style={styles.item} onPress={() => handleSelect(item)}>
  78. <View style={styles.imgBox}>
  79. <Image
  80. source={{ uri: item.isCompleted ? Images.box.detail.packagingBox2 : Images.box.detail.packagingBox1 }}
  81. style={styles.boxImg}
  82. contentFit="contain"
  83. />
  84. </View>
  85. <View style={styles.numBadge}>
  86. <Text style={styles.numText}>{item.boxNumber}箱</Text>
  87. </View>
  88. <Text style={styles.remaining}>
  89. {item.leftQuantity}/{item.quantity}
  90. </Text>
  91. </TouchableOpacity>
  92. ))}
  93. </View>
  94. </ScrollView>
  95. </View>
  96. </View>
  97. </View>
  98. </Modal>
  99. );
  100. });
  101. const styles = StyleSheet.create({
  102. overlay: {
  103. flex: 1,
  104. backgroundColor: 'rgba(0,0,0,0.5)',
  105. justifyContent: 'flex-end',
  106. },
  107. container: {
  108. backgroundColor: '#ffc901',
  109. borderTopLeftRadius: 10,
  110. borderTopRightRadius: 10,
  111. maxHeight: '85%', // Increased slightly
  112. paddingBottom: 20,
  113. },
  114. titleSection: {
  115. position: 'relative',
  116. alignItems: 'center',
  117. paddingVertical: 15,
  118. },
  119. titleBg: {
  120. backgroundColor: '#ff8c16',
  121. paddingHorizontal: 40,
  122. paddingVertical: 10,
  123. borderRadius: 20,
  124. },
  125. titleText: {
  126. color: '#fff',
  127. fontSize: 16,
  128. fontWeight: 'bold',
  129. },
  130. closeBtn: {
  131. position: 'absolute',
  132. right: 15,
  133. top: 10,
  134. width: 30,
  135. height: 30,
  136. justifyContent: 'center',
  137. alignItems: 'center',
  138. },
  139. closeBtnText: {
  140. fontSize: 24,
  141. color: '#333',
  142. fontWeight: 'bold',
  143. },
  144. // Bucket Styles
  145. bucketContainer: {
  146. marginBottom: 10,
  147. height: 40,
  148. },
  149. bucketScroll: {
  150. paddingHorizontal: 10,
  151. },
  152. bucketItem: {
  153. paddingHorizontal: 15,
  154. paddingVertical: 6,
  155. borderRadius: 15,
  156. backgroundColor: '#fff',
  157. marginRight: 10,
  158. borderWidth: 1,
  159. borderColor: '#eee',
  160. justifyContent: 'center',
  161. },
  162. bucketItemActive: {
  163. backgroundColor: '#ff8c16',
  164. borderColor: '#ff8c16',
  165. },
  166. bucketText: {
  167. fontSize: 12,
  168. color: '#333',
  169. fontWeight: '500',
  170. },
  171. bucketTextActive: {
  172. color: '#fff',
  173. fontWeight: 'bold',
  174. },
  175. content: {
  176. backgroundColor: '#fff',
  177. marginHorizontal: 10,
  178. borderRadius: 13,
  179. borderWidth: 1,
  180. borderColor: '#000',
  181. flex: 1, // Let it take remaining space
  182. },
  183. scrollView: {
  184. padding: 10,
  185. },
  186. grid: {
  187. flexDirection: 'row',
  188. flexWrap: 'wrap',
  189. paddingBottom: 20,
  190. },
  191. item: {
  192. width: '25%',
  193. alignItems: 'center',
  194. marginBottom: 15,
  195. },
  196. imgBox: {
  197. width: 50, // Slightly smaller to fit better
  198. height: 50,
  199. },
  200. boxImg: {
  201. width: '100%',
  202. height: '100%',
  203. },
  204. numBadge: {
  205. backgroundColor: '#959595',
  206. borderRadius: 15,
  207. paddingHorizontal: 8,
  208. paddingVertical: 2,
  209. marginTop: 5,
  210. },
  211. numText: {
  212. color: '#fff',
  213. fontSize: 10, // Smaller font
  214. fontWeight: '500',
  215. },
  216. remaining: {
  217. fontSize: 10,
  218. color: '#999',
  219. fontWeight: 'bold',
  220. marginTop: 3,
  221. },
  222. });