| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import { Image } from 'expo-image';
- import React, { forwardRef, useImperativeHandle, useState } from 'react';
- import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
- import { Images } from '@/constants/images';
- interface BoxItem {
- boxNumber: number;
- leftQuantity: number;
- quantity: number;
- isCompleted: boolean;
- }
- interface BoxPopupProps {
- onSelect: (item: BoxItem) => void;
- }
- export interface BoxPopupRef {
- open: (list: BoxItem[]) => void;
- close: () => void;
- }
- export const BoxPopup = forwardRef<BoxPopupRef, BoxPopupProps>(({ onSelect }, ref) => {
- const [activeBucket, setActiveBucket] = useState(0);
- useImperativeHandle(ref, () => ({
- open: (data: BoxItem[]) => {
- setList(data);
- setActiveBucket(0); // Reset to first bucket
- setVisible(true);
- },
- close: () => setVisible(false),
- }));
- const handleSelect = (item: BoxItem) => {
- onSelect(item);
- setVisible(false);
- };
- // Bucket logic
- const BUCKET_SIZE = 100;
- const buckets = Math.ceil(list.length / BUCKET_SIZE);
- const displayList = list.slice(activeBucket * BUCKET_SIZE, (activeBucket + 1) * BUCKET_SIZE);
- return (
- <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
- <View style={styles.overlay}>
- <View style={styles.container}>
- {/* 标题区域 */}
- <View style={styles.titleSection}>
- <View style={styles.titleBg}>
- <Text style={styles.titleText}>选择盒子</Text>
- </View>
- <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
- <Text style={styles.closeBtnText}>×</Text>
- </TouchableOpacity>
- </View>
- {/* 区间选择器 (仅当数量超过100时显示) */}
- {buckets > 1 && (
- <View style={styles.bucketContainer}>
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.bucketScroll}>
- {Array.from({ length: buckets }).map((_, index) => {
- const start = index * BUCKET_SIZE + 1;
- const end = Math.min((index + 1) * BUCKET_SIZE, list.length);
- const isActive = activeBucket === index;
- return (
- <TouchableOpacity
- key={index}
- style={[styles.bucketItem, isActive && styles.bucketItemActive]}
- onPress={() => setActiveBucket(index)}
- >
- <Text style={[styles.bucketText, isActive && styles.bucketTextActive]}>
- {start}~{end}
- </Text>
- </TouchableOpacity>
- );
- })}
- </ScrollView>
- </View>
- )}
- {/* 盒子列表 */}
- <View style={styles.content}>
- <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
- <View style={styles.grid}>
- {displayList.map((item, index) => (
- <TouchableOpacity key={item.boxNumber} style={styles.item} onPress={() => handleSelect(item)}>
- <View style={styles.imgBox}>
- <Image
- source={{ uri: item.isCompleted ? Images.box.detail.packagingBox2 : Images.box.detail.packagingBox1 }}
- style={styles.boxImg}
- contentFit="contain"
- />
- </View>
- <View style={styles.numBadge}>
- <Text style={styles.numText}>{item.boxNumber}箱</Text>
- </View>
- <Text style={styles.remaining}>
- {item.leftQuantity}/{item.quantity}
- </Text>
- </TouchableOpacity>
- ))}
- </View>
- </ScrollView>
- </View>
- </View>
- </View>
- </Modal>
- );
- });
- const styles = StyleSheet.create({
- overlay: {
- flex: 1,
- backgroundColor: 'rgba(0,0,0,0.5)',
- justifyContent: 'flex-end',
- },
- container: {
- backgroundColor: '#ffc901',
- borderTopLeftRadius: 10,
- borderTopRightRadius: 10,
- maxHeight: '85%', // Increased slightly
- paddingBottom: 20,
- },
- titleSection: {
- position: 'relative',
- alignItems: 'center',
- paddingVertical: 15,
- },
- titleBg: {
- backgroundColor: '#ff8c16',
- paddingHorizontal: 40,
- paddingVertical: 10,
- borderRadius: 20,
- },
- titleText: {
- color: '#fff',
- fontSize: 16,
- fontWeight: 'bold',
- },
- closeBtn: {
- position: 'absolute',
- right: 15,
- top: 10,
- width: 30,
- height: 30,
- justifyContent: 'center',
- alignItems: 'center',
- },
- closeBtnText: {
- fontSize: 24,
- color: '#333',
- fontWeight: 'bold',
- },
- // Bucket Styles
- bucketContainer: {
- marginBottom: 10,
- height: 40,
- },
- bucketScroll: {
- paddingHorizontal: 10,
- },
- bucketItem: {
- paddingHorizontal: 15,
- paddingVertical: 6,
- borderRadius: 15,
- backgroundColor: '#fff',
- marginRight: 10,
- borderWidth: 1,
- borderColor: '#eee',
- justifyContent: 'center',
- },
- bucketItemActive: {
- backgroundColor: '#ff8c16',
- borderColor: '#ff8c16',
- },
- bucketText: {
- fontSize: 12,
- color: '#333',
- fontWeight: '500',
- },
- bucketTextActive: {
- color: '#fff',
- fontWeight: 'bold',
- },
- content: {
- backgroundColor: '#fff',
- marginHorizontal: 10,
- borderRadius: 13,
- borderWidth: 1,
- borderColor: '#000',
- flex: 1, // Let it take remaining space
- },
- scrollView: {
- padding: 10,
- },
- grid: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- paddingBottom: 20,
- },
- item: {
- width: '25%',
- alignItems: 'center',
- marginBottom: 15,
- },
- imgBox: {
- width: 50, // Slightly smaller to fit better
- height: 50,
- },
- boxImg: {
- width: '100%',
- height: '100%',
- },
- numBadge: {
- backgroundColor: '#959595',
- borderRadius: 15,
- paddingHorizontal: 8,
- paddingVertical: 2,
- marginTop: 5,
- },
- numText: {
- color: '#fff',
- fontSize: 10, // Smaller font
- fontWeight: '500',
- },
- remaining: {
- fontSize: 10,
- color: '#999',
- fontWeight: 'bold',
- marginTop: 3,
- },
- });
|