|
|
@@ -0,0 +1,341 @@
|
|
|
+import { Image } from 'expo-image';
|
|
|
+import React, { useEffect, useState } from 'react';
|
|
|
+import {
|
|
|
+ Alert,
|
|
|
+ Modal,
|
|
|
+ Platform,
|
|
|
+ ScrollView,
|
|
|
+ StyleSheet,
|
|
|
+ Text,
|
|
|
+ TextInput,
|
|
|
+ TouchableOpacity,
|
|
|
+ View,
|
|
|
+} from 'react-native';
|
|
|
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
+
|
|
|
+import { Images } from '@/constants/images';
|
|
|
+import { transferOrderSubmit } from '@/services/user';
|
|
|
+
|
|
|
+interface GroupedGoods {
|
|
|
+ total: number;
|
|
|
+ data: {
|
|
|
+ id: string;
|
|
|
+ cover: string;
|
|
|
+ spuId: string;
|
|
|
+ level: string;
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+interface TransferModalProps {
|
|
|
+ visible: boolean;
|
|
|
+ selectedItems: Array<{
|
|
|
+ id: string;
|
|
|
+ spu: { id: string; name: string; cover: string };
|
|
|
+ level: string;
|
|
|
+ }>;
|
|
|
+ onClose: () => void;
|
|
|
+ onSuccess: () => void;
|
|
|
+}
|
|
|
+
|
|
|
+export default function TransferModal({
|
|
|
+ visible,
|
|
|
+ selectedItems,
|
|
|
+ onClose,
|
|
|
+ onSuccess,
|
|
|
+}: TransferModalProps) {
|
|
|
+ const insets = useSafeAreaInsets();
|
|
|
+ const [recipientId, setRecipientId] = useState('');
|
|
|
+ const [checked, setChecked] = useState(true);
|
|
|
+ const [submitting, setSubmitting] = useState(false);
|
|
|
+ const [goodsList, setGoodsList] = useState<GroupedGoods[]>([]);
|
|
|
+
|
|
|
+ const showAlert = (msg: string) => {
|
|
|
+ if (Platform.OS === 'web') window.alert(msg);
|
|
|
+ else Alert.alert('提示', msg);
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (visible && selectedItems.length > 0) {
|
|
|
+ // 合并同款商品
|
|
|
+ const goodsMap: Record<string, GroupedGoods> = {};
|
|
|
+ selectedItems.forEach((item) => {
|
|
|
+ const key = `${item.spu.id}_${item.level}`;
|
|
|
+ if (goodsMap[key]) {
|
|
|
+ goodsMap[key].total += 1;
|
|
|
+ } else {
|
|
|
+ goodsMap[key] = {
|
|
|
+ total: 1,
|
|
|
+ data: {
|
|
|
+ id: item.id,
|
|
|
+ cover: item.spu.cover,
|
|
|
+ spuId: item.spu.id,
|
|
|
+ level: item.level,
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ setGoodsList(Object.values(goodsMap));
|
|
|
+ setRecipientId('');
|
|
|
+ }
|
|
|
+ }, [visible, selectedItems]);
|
|
|
+
|
|
|
+ const handleSubmit = async () => {
|
|
|
+ if (!recipientId.trim()) {
|
|
|
+ showAlert('请输入受赠人ID');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!checked) {
|
|
|
+ showAlert('请阅读并同意《转赠风险及责任声明》');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (submitting) return;
|
|
|
+ setSubmitting(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const inventoryIds = selectedItems.map((item) => item.id);
|
|
|
+ const levels = selectedItems.map((item) => item.level);
|
|
|
+ const res: any = await transferOrderSubmit({
|
|
|
+ inventoryIds,
|
|
|
+ levels,
|
|
|
+ toUserShortId: recipientId.trim(),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res && res.success) {
|
|
|
+ showAlert('转赠成功');
|
|
|
+ onClose();
|
|
|
+ onSuccess();
|
|
|
+ } else {
|
|
|
+ showAlert(res?.msg || res?.message || '转赠失败');
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('转赠失败:', err);
|
|
|
+ showAlert('转赠失败,请稍后再试');
|
|
|
+ }
|
|
|
+ setSubmitting(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Modal
|
|
|
+ visible={visible}
|
|
|
+ transparent
|
|
|
+ animationType="slide"
|
|
|
+ onRequestClose={onClose}
|
|
|
+ >
|
|
|
+ <View style={styles.overlay}>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.overlayBg}
|
|
|
+ onPress={onClose}
|
|
|
+ activeOpacity={1}
|
|
|
+ />
|
|
|
+ <View style={[styles.container, { paddingBottom: insets.bottom + 20 }]}>
|
|
|
+ {/* 标题 */}
|
|
|
+ <View style={styles.header}>
|
|
|
+ <Text style={styles.title}>转赠</Text>
|
|
|
+ <TouchableOpacity style={styles.closeBtn} onPress={onClose}>
|
|
|
+ <Text style={styles.closeBtnText}>×</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 商品列表 */}
|
|
|
+ <View style={styles.goodsSection}>
|
|
|
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
|
+ {goodsList.map((goods, idx) => (
|
|
|
+ <View key={idx} style={styles.goodsItem}>
|
|
|
+ <Image
|
|
|
+ source={{ uri: goods.data.cover }}
|
|
|
+ style={styles.goodsImg}
|
|
|
+ contentFit="contain"
|
|
|
+ />
|
|
|
+ <View style={styles.goodsCount}>
|
|
|
+ <Text style={styles.goodsCountText}>x{goods.total}</Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </ScrollView>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 受赠人ID输入 */}
|
|
|
+ <View style={styles.inputSection}>
|
|
|
+ <Text style={styles.inputLabel}>受赠人ID</Text>
|
|
|
+ <TextInput
|
|
|
+ style={styles.input}
|
|
|
+ value={recipientId}
|
|
|
+ onChangeText={setRecipientId}
|
|
|
+ placeholder="请输入受赠人ID"
|
|
|
+ placeholderTextColor="#999"
|
|
|
+ keyboardType="default"
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 转赠声明 */}
|
|
|
+ <View style={styles.agreementRow}>
|
|
|
+ <TouchableOpacity style={styles.agreementText} onPress={() => {}}>
|
|
|
+ <Text style={styles.agreementLabel}>已阅读并同意</Text>
|
|
|
+ <Text style={styles.agreementLink}>《转赠风险及责任声明》</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.radio, checked && styles.radioChecked]}
|
|
|
+ onPress={() => setChecked(!checked)}
|
|
|
+ >
|
|
|
+ {checked && <Text style={styles.radioIcon}>✓</Text>}
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 提交按钮 */}
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.submitBtn}
|
|
|
+ onPress={handleSubmit}
|
|
|
+ disabled={submitting}
|
|
|
+ activeOpacity={0.7}
|
|
|
+ >
|
|
|
+ <Text style={styles.submitBtnText}>
|
|
|
+ {submitting ? '提交中...' : '立即转赠'}
|
|
|
+ </Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ <View style={styles.fill} />
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </Modal>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+const styles = StyleSheet.create({
|
|
|
+ overlay: { flex: 1, justifyContent: 'flex-end' },
|
|
|
+ overlayBg: {
|
|
|
+ position: 'absolute',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: 'rgba(0,0,0,0.5)',
|
|
|
+ },
|
|
|
+ container: {
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ borderTopLeftRadius: 15,
|
|
|
+ borderTopRightRadius: 15,
|
|
|
+ paddingHorizontal: 14,
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ paddingTop: 22,
|
|
|
+ paddingBottom: 5,
|
|
|
+ position: 'relative',
|
|
|
+ },
|
|
|
+ title: { fontSize: 16, fontWeight: 'bold', color: '#000' },
|
|
|
+ closeBtn: {
|
|
|
+ position: 'absolute',
|
|
|
+ right: 0,
|
|
|
+ top: 15,
|
|
|
+ width: 24,
|
|
|
+ height: 24,
|
|
|
+ backgroundColor: '#ebebeb',
|
|
|
+ borderRadius: 12,
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ closeBtnText: { fontSize: 18, color: '#a2a2a2', lineHeight: 20 },
|
|
|
+ goodsSection: { paddingVertical: 10 },
|
|
|
+ goodsItem: {
|
|
|
+ width: 79,
|
|
|
+ height: 103,
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ borderRadius: 6,
|
|
|
+ marginRight: 8,
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ position: 'relative',
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: '#eaeaea',
|
|
|
+ },
|
|
|
+ goodsImg: { width: 73, height: 85 },
|
|
|
+ goodsCount: {
|
|
|
+ position: 'absolute',
|
|
|
+ top: 0,
|
|
|
+ right: 0,
|
|
|
+ backgroundColor: '#ff6b00',
|
|
|
+ borderRadius: 2,
|
|
|
+ paddingHorizontal: 4,
|
|
|
+ paddingVertical: 2,
|
|
|
+ },
|
|
|
+ goodsCountText: { color: '#fff', fontSize: 10, fontWeight: 'bold' },
|
|
|
+ inputSection: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ backgroundColor: '#f8f8f8',
|
|
|
+ borderRadius: 6,
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ paddingVertical: 10,
|
|
|
+ marginVertical: 15,
|
|
|
+ shadowColor: '#000',
|
|
|
+ shadowOffset: { width: 0, height: 1 },
|
|
|
+ shadowOpacity: 0.05,
|
|
|
+ shadowRadius: 3,
|
|
|
+ },
|
|
|
+ inputLabel: {
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: '500',
|
|
|
+ color: '#333',
|
|
|
+ },
|
|
|
+ input: {
|
|
|
+ flex: 1,
|
|
|
+ marginLeft: 10,
|
|
|
+ textAlign: 'right',
|
|
|
+ height: 30,
|
|
|
+ fontSize: 14,
|
|
|
+ color: '#666',
|
|
|
+ },
|
|
|
+ agreementRow: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ paddingVertical: 10,
|
|
|
+ },
|
|
|
+ agreementText: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ agreementLabel: {
|
|
|
+ fontSize: 12,
|
|
|
+ color: '#333',
|
|
|
+ },
|
|
|
+ agreementLink: {
|
|
|
+ fontSize: 12,
|
|
|
+ color: '#ff9600',
|
|
|
+ },
|
|
|
+ radio: {
|
|
|
+ width: 20,
|
|
|
+ height: 20,
|
|
|
+ borderRadius: 10,
|
|
|
+ borderWidth: 2,
|
|
|
+ borderColor: '#ccc',
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ radioChecked: {
|
|
|
+ backgroundColor: '#ff9600',
|
|
|
+ borderColor: '#ff9600',
|
|
|
+ },
|
|
|
+ radioIcon: {
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ },
|
|
|
+ submitBtn: {
|
|
|
+ backgroundColor: '#ff9600',
|
|
|
+ borderRadius: 22,
|
|
|
+ height: 44,
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ marginTop: 5,
|
|
|
+ },
|
|
|
+ submitBtnText: {
|
|
|
+ color: '#fff',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ },
|
|
|
+ fill: { height: 25 },
|
|
|
+});
|