zbb 3 miesięcy temu
rodzic
commit
4673873974

+ 2 - 2
app/weal/catchDoll.tsx

@@ -129,7 +129,7 @@ export default function CatchDollScreen() {
 
   const getDetail = async () => {
     const res = await Service.catchDollDetail();
-    if (res.code === '0') {
+    if (res.code == 0) {
       setLuckWheelGoodsList(res.data.luckWheelGoodsList);
     } else {
       Alert.alert('提示', res.msg);
@@ -170,7 +170,7 @@ export default function CatchDollScreen() {
     setLotteryFlag(true);
     const res = await Service.dollLottery({ quantity });
 
-    if (res.code === '0') {
+    if (res.code == 0) {
       getMolibi();
       // Animate Switch
       switchRotateVal.value = withSequence(

+ 217 - 0
app/weal/components/WishMaterialModal.tsx

@@ -0,0 +1,217 @@
+import ServiceAward from '@/services/award';
+import { Ionicons } from '@expo/vector-icons';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import { ActivityIndicator, FlatList, Image, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+export interface WishMaterialModalRef {
+    show: (callback: (selected: any[]) => void) => void;
+    close: () => void;
+}
+
+export const WishMaterialModal = forwardRef<WishMaterialModalRef, {}>((props, ref) => {
+    const [visible, setVisible] = useState(false);
+    const [list, setList] = useState<any[]>([]);
+    const [selectedIds, setSelectedIds] = useState<string[]>([]);
+    const [loading, setLoading] = useState(false);
+    const [onConfirm, setOnConfirm] = useState<((selected: any[]) => void) | null>(null);
+    const [page, setPage] = useState(1);
+    const [hasMore, setHasMore] = useState(true);
+
+    useImperativeHandle(ref, () => ({
+        show: (callback) => {
+            setVisible(true);
+            setOnConfirm(() => callback);
+            setPage(1);
+            setList([]);
+            setSelectedIds([]);
+            loadData(1);
+        },
+        close: () => setVisible(false),
+    }));
+
+    const loadData = async (curPage: number) => {
+        setLoading(true);
+        try {
+            // Fetch inventory/store items
+            const data = await ServiceAward.getStore(curPage, 20);
+            const records = (Array.isArray(data) ? data : data?.records) || [];
+
+            if (records.length > 0) {
+                setList(prev => curPage === 1 ? records : [...prev, ...records]);
+                setHasMore(records.length === 20);
+            } else {
+                setList(prev => curPage === 1 ? [] : prev);
+                setHasMore(false);
+            }
+        } catch (error) {
+            console.error(error);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleLoadMore = () => {
+        if (!loading && hasMore) {
+            const nextPage = page + 1;
+            setPage(nextPage);
+            loadData(nextPage);
+        }
+    };
+
+    const toggleSelect = (id: string) => {
+        // Multi-select allowed? Usually yes for materials.
+        setSelectedIds(prev =>
+            prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
+        );
+    };
+
+    const handleConfirm = () => {
+        const selectedGoods = list.filter(item => selectedIds.includes(item.id));
+        if (onConfirm) {
+            onConfirm(selectedGoods);
+        }
+        setVisible(false);
+    };
+
+    const renderItem = ({ item }: { item: any }) => {
+        const isSelected = selectedIds.includes(item.id);
+        return (
+            <TouchableOpacity style={styles.item} onPress={() => toggleSelect(item.id)}>
+                <Image source={{ uri: item.spu.cover }} style={styles.itemImg} />
+                <View style={styles.itemInfo}>
+                    <Text style={styles.itemName} numberOfLines={1}>{item.spu.name}</Text>
+                    <Text style={styles.itemMagic}>价值: {item.magicAmount} 果实</Text>
+                </View>
+                <View style={[styles.checkbox, isSelected && styles.checkboxSelected]}>
+                    {isSelected && <Ionicons name="checkmark" size={12} color="#fff" />}
+                </View>
+            </TouchableOpacity>
+        );
+    };
+
+    if (!visible) return null;
+
+    return (
+        <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
+            <View style={styles.overlay}>
+                <View style={styles.container}>
+                    <View style={styles.header}>
+                        <Text style={styles.headerTitle}>选择添加材料</Text>
+                        <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
+                            <Ionicons name="close" size={24} color="#333" />
+                        </TouchableOpacity>
+                    </View>
+                    <FlatList
+                        data={list}
+                        renderItem={renderItem}
+                        keyExtractor={item => item.id}
+                        onEndReached={handleLoadMore}
+                        onEndReachedThreshold={0.3}
+                        contentContainerStyle={styles.listContent}
+                        ListFooterComponent={() => loading && <ActivityIndicator />}
+                        ListEmptyComponent={<Text style={styles.emptyText}>暂无可用商品</Text>}
+                    />
+                    <TouchableOpacity style={styles.confirmBtn} onPress={handleConfirm}>
+                        <Text style={styles.confirmText}>确认添加 ({selectedIds.length})</Text>
+                    </TouchableOpacity>
+                </View>
+            </View>
+        </Modal>
+    );
+});
+
+export default WishMaterialModal;
+
+const styles = StyleSheet.create({
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.5)',
+        justifyContent: 'flex-end',
+    },
+    container: {
+        backgroundColor: '#fff',
+        borderTopLeftRadius: 20,
+        borderTopRightRadius: 20,
+        height: '70%',
+        paddingBottom: 30,
+    },
+    header: {
+        flexDirection: 'row',
+        justifyContent: 'center',
+        alignItems: 'center',
+        padding: 15,
+        borderBottomWidth: 1,
+        borderBottomColor: '#eee',
+    },
+    headerTitle: {
+        fontSize: 18,
+        fontWeight: 'bold',
+        color: '#333',
+    },
+    closeBtn: {
+        position: 'absolute',
+        right: 15,
+    },
+    listContent: {
+        padding: 15,
+    },
+    item: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        padding: 10,
+        marginBottom: 10,
+        backgroundColor: '#f9f9f9',
+        borderRadius: 8,
+    },
+    itemImg: {
+        width: 60,
+        height: 60,
+        borderRadius: 4,
+    },
+    itemInfo: {
+        flex: 1,
+        marginLeft: 10,
+    },
+    itemName: {
+        fontSize: 14,
+        color: '#333',
+        fontWeight: 'bold',
+    },
+    itemMagic: {
+        fontSize: 12,
+        color: '#666',
+        marginTop: 5,
+    },
+    checkbox: {
+        width: 22,
+        height: 22,
+        borderRadius: 11,
+        borderWidth: 1,
+        borderColor: '#ccc',
+        justifyContent: 'center',
+        alignItems: 'center',
+        backgroundColor: '#fff',
+    },
+    checkboxSelected: {
+        backgroundColor: '#FFAD00',
+        borderColor: '#FFAD00',
+    },
+    confirmBtn: {
+        backgroundColor: '#FFAD00',
+        marginHorizontal: 20,
+        paddingVertical: 12,
+        borderRadius: 25,
+        alignItems: 'center',
+        marginTop: 10,
+    },
+    confirmText: {
+        color: '#fff',
+        fontSize: 16,
+        fontWeight: 'bold',
+    },
+    emptyText: {
+        textAlign: 'center',
+        marginTop: 50,
+        color: '#999',
+    },
+});

+ 146 - 0
app/weal/components/WishRecordModal.tsx

@@ -0,0 +1,146 @@
+import { Images } from '@/constants/images';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import { FlatList, Image, ImageBackground, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+export interface WishRecordModalRef {
+    show: () => void;
+    close: () => void;
+}
+
+export const WishRecordModal = forwardRef<WishRecordModalRef, {}>((props, ref) => {
+    const [visible, setVisible] = useState(false);
+    const [list, setList] = useState<any[]>([]);
+
+    useImperativeHandle(ref, () => ({
+        show: () => {
+            setVisible(true);
+            loadData();
+        },
+        close: () => setVisible(false),
+    }));
+
+    const loadData = async () => {
+        // Reuse catch record API or a new one if specified, for now assuming similar structure
+        // If there is no specific API for wish records yet, we might need to add one.
+        // Assuming 'getWealRecord' but maybe filtered by type? 
+        // For waiting for backend implementation, mock empty or use generic record.
+        // Or if 'services/weal.ts' didn't have specific wish record, we stick to placeholder or generic.
+        // Actually, let's use a placeholder for now as per plan, or maybe 'getWealRecord'.
+        try {
+            // const res = await Service.getWishRecord(); // Need to implement if separate
+            // For now, let's just show structure
+        } catch (e) {
+            console.error(e);
+        }
+    };
+
+    const renderItem = ({ item }: { item: any }) => (
+        <View style={styles.item}>
+            <Text style={styles.itemText}>{item.createTime}</Text>
+            <Text style={styles.itemText}>{item.goodsName}</Text>
+        </View>
+    );
+
+    if (!visible) return null;
+
+    return (
+        <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
+            <View style={styles.overlay}>
+                <View style={styles.contentContainer}>
+                    <ImageBackground style={styles.content} source={{ uri: Images.welfare.welfareDialogBg }} resizeMode="stretch">
+                        <View style={styles.titleBox}>
+                            <ImageBackground style={styles.titleBg} source={{ uri: Images.common.qijiDialogTitBg }} resizeMode="stretch">
+                                <Text style={styles.titleText}>祈愿记录</Text>
+                            </ImageBackground>
+                        </View>
+                        <View style={styles.closeBox}>
+                            <TouchableOpacity onPress={() => setVisible(false)}>
+                                <Image style={styles.close} source={{ uri: Images.common.closeBut }} />
+                            </TouchableOpacity>
+                        </View>
+                        <View style={styles.listContainer}>
+                            <FlatList
+                                data={list}
+                                renderItem={renderItem}
+                                keyExtractor={(item, index) => index.toString()}
+                                ListEmptyComponent={<Text style={styles.emptyText}>暂无记录</Text>}
+                            />
+                        </View>
+                    </ImageBackground>
+                </View>
+            </View>
+        </Modal>
+    );
+});
+
+export default WishRecordModal;
+
+const styles = StyleSheet.create({
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.7)',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    contentContainer: {
+        width: 320,
+        height: 450,
+        alignItems: 'center',
+        justifyContent: 'center',
+    },
+    content: {
+        width: '100%',
+        height: '100%',
+        alignItems: 'center',
+        paddingTop: 40,
+        paddingBottom: 20,
+    },
+    titleBox: {
+        position: 'absolute',
+        top: -15,
+        zIndex: 1,
+    },
+    titleBg: {
+        width: 180,
+        height: 50,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    titleText: {
+        color: '#fff',
+        fontSize: 18,
+        fontWeight: 'bold',
+        marginTop: -5,
+    },
+    closeBox: {
+        position: 'absolute',
+        top: 0,
+        right: -5,
+        zIndex: 2,
+    },
+    close: {
+        width: 30,
+        height: 30,
+    },
+    listContainer: {
+        flex: 1,
+        width: '90%',
+        marginTop: 20,
+    },
+    item: {
+        flexDirection: 'row',
+        justifyContent: 'space-between',
+        paddingVertical: 10,
+        borderBottomWidth: 1,
+        borderBottomColor: '#eee',
+    },
+    itemText: {
+        fontSize: 14,
+        color: '#333',
+    },
+    emptyText: {
+        textAlign: 'center',
+        marginTop: 50,
+        color: '#999',
+    },
+});

+ 87 - 0
app/weal/components/WishRuleModal.tsx

@@ -0,0 +1,87 @@
+import { Images } from '@/constants/images';
+import { Image } from 'expo-image';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import { ImageBackground, Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
+
+export interface WishRuleModalRef {
+    show: () => void;
+    close: () => void;
+}
+
+export const WishRuleModal = forwardRef<WishRuleModalRef, {}>((props, ref) => {
+    const [visible, setVisible] = useState(false);
+
+    useImperativeHandle(ref, () => ({
+        show: () => setVisible(true),
+        close: () => setVisible(false),
+    }));
+
+    if (!visible) return null;
+
+    return (
+        <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
+            <View style={styles.overlay}>
+                <View style={styles.contentContainer}>
+                    <ImageBackground style={styles.content} source={{ uri: Images.welfare.welfareDialogBg }} resizeMode="contain">
+                        <View style={styles.titleBox}>
+                            <Image style={styles.title} source={{ uri: Images.welfare.qijiWelfareDollRule }} resizeMode="contain" />
+                        </View>
+                        <View style={styles.closeBox}>
+                            <TouchableOpacity onPress={() => setVisible(false)}>
+                                <Image style={styles.close} source={{ uri: Images.common.closeBut }} />
+                            </TouchableOpacity>
+                        </View>
+                        <Image source={{ uri: Images.welfare.catchDollRule }} style={styles.ruleImage} contentFit="contain" />
+                    </ImageBackground>
+                </View>
+            </View>
+        </Modal>
+    );
+});
+
+export default WishRuleModal;
+
+const styles = StyleSheet.create({
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.7)',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    contentContainer: {
+        width: 300,
+        height: 400,
+        alignItems: 'center',
+        justifyContent: 'center',
+    },
+    content: {
+        width: '100%',
+        height: '100%',
+        alignItems: 'center',
+        justifyContent: 'center',
+    },
+    titleBox: {
+        position: 'absolute',
+        top: -20,
+        zIndex: 1,
+    },
+    title: {
+        width: 200,
+        height: 50,
+    },
+    closeBox: {
+        position: 'absolute',
+        top: 0,
+        right: -10,
+        zIndex: 2,
+    },
+    close: {
+        width: 30,
+        height: 30,
+    },
+    ruleImage: {
+        width: 260,
+        height: 300,
+        marginTop: 20,
+    },
+});

+ 189 - 29
app/weal/wish.tsx

@@ -1,8 +1,11 @@
-import { Image } from 'expo-image';
+import { Images } from '@/constants/images';
+import Service from '@/services/weal';
 import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
+  Alert,
   Dimensions,
+  Image,
   ImageBackground,
   ScrollView,
   StatusBar,
@@ -12,9 +15,9 @@ import {
   View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
-
-import { Images } from '@/constants/images';
-import { get } from '@/services/http';
+import { WishMaterialModal, WishMaterialModalRef } from './components/WishMaterialModal';
+import { WishRecordModal, WishRecordModalRef } from './components/WishRecordModal';
+import { WishRuleModal, WishRuleModalRef } from './components/WishRuleModal';
 
 const { width: SCREEN_WIDTH } = Dimensions.get('window');
 const CDN_BASE = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart';
@@ -44,6 +47,7 @@ interface WishItem {
 interface GoodsItem {
   id: string;
   spu: { cover: string };
+  magicAmount: number;
 }
 
 export default function WishScreen() {
@@ -53,12 +57,21 @@ export default function WishScreen() {
   const [active, setActive] = useState(0);
   const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
   const [progress, setProgress] = useState(0);
+  const [magicAmount, setMagicAmount] = useState(0);
+
+  // Modals
+  const ruleRef = useRef<WishRuleModalRef>(null);
+  const recordRef = useRef<WishRecordModalRef>(null);
+  const materialRef = useRef<WishMaterialModalRef>(null);
 
   const loadData = useCallback(async () => {
     try {
-      const res = await get('/api/wish/list');
-      if (res.data && res.data.length > 0) {
-        setTableData(res.data);
+      const data = await Service.getWishList();
+      // Ensure data is array
+      const list = Array.isArray(data) ? data : (data?.records || []);
+
+      if (list && list.length > 0) {
+        setTableData(list);
       }
     } catch (error) {
       console.error('加载祈愿数据失败:', error);
@@ -69,21 +82,129 @@ export default function WishScreen() {
     loadData();
   }, [loadData]);
 
+  // Update progress when active item changes or goodsList changes
+  useEffect(() => {
+    const updateProgress = async () => {
+      if (tableData.length > 0 && tableData[active]) {
+        const current = tableData[active];
+        if (goodsList.length === 0) {
+          setProgress(0);
+          setMagicAmount(0);
+        } else {
+          // Call preview to get progress
+          const inventoryIds = goodsList.map(g => g.id);
+          try {
+            const res = await Service.wishPreview({
+              substituteGoodsId: current.id,
+              inventoryIds
+            });
+            if (res) {
+              let p = (res.progress || 0) * 100;
+              if (p > 100) p = 100;
+              setProgress(p);
+              setMagicAmount(res.magicAmount || 0);
+            }
+          } catch (e) {
+            console.error(e);
+          }
+        }
+      } else {
+        setProgress(0);
+        setMagicAmount(0);
+      }
+    };
+    updateProgress();
+  }, [tableData, active, goodsList]);
+
   const handlePrev = () => {
-    if (active > 0) setActive(active - 1);
+    if (active > 0) {
+      setActive(active - 1);
+      setGoodsList([]);
+    }
   };
 
   const handleNext = () => {
-    if (active < tableData.length - 1) setActive(active + 1);
+    if (active < tableData.length - 1) {
+      setActive(active + 1);
+      setGoodsList([]);
+    }
   };
 
   const currentItem = tableData[active];
-  const remaining = currentItem ? currentItem.quantity - currentItem.completeQuantity : 0;
+  const serverRemaining = currentItem ? currentItem.quantity - currentItem.completeQuantity : 0;
+  // Visual remaining doesn't update until submit in this flow
+  const remaining = serverRemaining;
 
   const removeGoods = (item: GoodsItem) => {
     setGoodsList(goodsList.filter((g) => g.id !== item.id));
   };
 
+  const openMaterialModal = () => {
+    if (!currentItem) return;
+    materialRef.current?.show((selected) => {
+      // Add selected items to goodsList
+      setGoodsList(prev => {
+        const newIds = new Set(prev.map(i => i.id));
+        const toAdd = selected.filter(i => !newIds.has(i.id));
+        return [...prev, ...toAdd];
+      });
+    });
+  };
+
+  const submitWish = async () => {
+    try {
+      const inventoryIds = goodsList.map(g => g.id);
+
+      console.log('Submitting Wish:', { substituteGoodsId: currentItem.id, inventoryIds });
+
+      const res = await Service.wishSubmit({
+        substituteGoodsId: currentItem.id,
+        inventoryIds
+      });
+
+      console.log('Submit Res:', res);
+
+      // Fix: Use loose equality since backend returns string "0"
+      if (res.code == 0 || res.success) {
+        Alert.alert('成功', '点亮成功!');
+        setGoodsList([]);
+        loadData();
+      } else {
+        Alert.alert('提交失败', `Code: ${res.code}, Msg: ${res.msg}`);
+      }
+
+    } catch (error) {
+      console.error('LightUp Error:', error);
+      Alert.alert('执行异常', String(error));
+    }
+  };
+
+  const handleLightUp = async () => {
+    if (!currentItem) return;
+    if (progress < 100) {
+      if (goodsList.length === 0) {
+        Alert.alert('提示', '请先添加材料');
+        return;
+      }
+      Alert.alert('提示', '进度未满100%,无法点亮');
+      return;
+    }
+
+    Alert.alert(
+      '确认点亮',
+      '是否确认消耗所选材料进行点亮?',
+      [
+        { text: '取消', style: 'cancel' },
+        {
+          text: '确认',
+          onPress: () => {
+            submitWish();
+          }
+        }
+      ]
+    );
+  };
+
   return (
     <View style={styles.container}>
       <StatusBar barStyle="light-content" />
@@ -98,8 +219,8 @@ export default function WishScreen() {
         </View>
 
         {/* 规则按钮 */}
-        <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 160 }]}>
-          <Image source={{ uri: wishImages.rule }} style={styles.ruleBtnImg} contentFit="contain" />
+        <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 160 }]} onPress={() => ruleRef.current?.show()}>
+          <Image source={{ uri: wishImages.rule }} style={styles.ruleBtnImg} resizeMode="contain" />
         </TouchableOpacity>
 
         <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
@@ -107,7 +228,7 @@ export default function WishScreen() {
 
           {/* 标题图片 */}
           <View style={styles.titleBox}>
-            <Image source={{ uri: wishImages.title }} style={styles.titleImg} contentFit="contain" />
+            <Image source={{ uri: wishImages.title }} style={styles.titleImg} resizeMode="contain" />
           </View>
 
           {/* 卡片轮播区域 */}
@@ -115,7 +236,7 @@ export default function WishScreen() {
             {/* 左箭头 */}
             {active > 0 && (
               <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
-                <Image source={{ uri: wishImages.left }} style={styles.arrowImg} contentFit="contain" />
+                <Image source={{ uri: wishImages.left }} style={styles.arrowImg} resizeMode="contain" />
               </TouchableOpacity>
             )}
 
@@ -124,7 +245,7 @@ export default function WishScreen() {
               {currentItem ? (
                 <View style={styles.card}>
                   <View style={styles.imgBox}>
-                    <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} contentFit="contain" />
+                    <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} resizeMode="contain" />
                     {/* 仅剩标签 */}
                     <View style={styles.remainingBox}>
                       <Text style={styles.remainingText}>仅剩:{remaining}</Text>
@@ -146,7 +267,7 @@ export default function WishScreen() {
             {/* 右箭头 */}
             {active < tableData.length - 1 && (
               <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
-                <Image source={{ uri: wishImages.right }} style={styles.arrowImg} contentFit="contain" />
+                <Image source={{ uri: wishImages.right }} style={styles.arrowImg} resizeMode="contain" />
               </TouchableOpacity>
             )}
           </View>
@@ -162,7 +283,17 @@ export default function WishScreen() {
 
           {/* 进度条区域 */}
           <View style={styles.progressSection}>
-            <Text style={styles.progressLabel}>进度:</Text>
+            <View style={styles.progressLabelBox}>
+              <Text style={styles.progressLabel}>进度:</Text>
+            </View>
+
+            {/* Show Magic Return if 100% and magicAmount > 0 */}
+            {progress >= 100 && magicAmount > 0 && (
+              <View style={styles.magicBubble}>
+                <Text style={styles.magicText}>返还: {magicAmount}果实</Text>
+              </View>
+            )}
+
             <View style={styles.progressBar}>
               <View style={[styles.progressFill, { width: `${progress}%` }]} />
             </View>
@@ -174,9 +305,9 @@ export default function WishScreen() {
             <Text style={styles.addTitle}>材料添加</Text>
             <View style={styles.addMain}>
               {/* 添加按钮 */}
-              <TouchableOpacity style={styles.addBtn}>
+              <TouchableOpacity style={styles.addBtn} onPress={openMaterialModal}>
                 <ImageBackground source={{ uri: wishImages.addBg }} style={styles.addBtnBg} resizeMode="contain">
-                  <Image source={{ uri: wishImages.add }} style={styles.addIcon} contentFit="contain" />
+                  <Image source={{ uri: wishImages.add }} style={styles.addIcon} resizeMode="contain" />
                   <Text style={styles.addBtnText}>添加</Text>
                 </ImageBackground>
               </TouchableOpacity>
@@ -187,10 +318,10 @@ export default function WishScreen() {
                   {goodsList.map((item, index) => (
                     <View key={item.id || index} style={styles.addItem}>
                       <ImageBackground source={{ uri: wishImages.addLiBg }} style={styles.addItemBg} resizeMode="contain">
-                        <Image source={{ uri: item.spu?.cover }} style={styles.addItemImg} contentFit="cover" />
+                        <Image source={{ uri: item.spu?.cover }} style={styles.addItemImg} resizeMode="cover" />
                       </ImageBackground>
                       <TouchableOpacity style={styles.closeBtn} onPress={() => removeGoods(item)}>
-                        <Image source={{ uri: wishImages.addClose }} style={styles.closeIcon} contentFit="contain" />
+                        <Image source={{ uri: wishImages.addClose }} style={styles.closeIcon} resizeMode="contain" />
                       </TouchableOpacity>
                     </View>
                   ))}
@@ -200,15 +331,29 @@ export default function WishScreen() {
           </ImageBackground>
 
           {/* 底部按钮 */}
-          <TouchableOpacity style={styles.bottomBtn}>
-            <ImageBackground source={{ uri: Images.common.butBgHui }} style={styles.bottomBtnBg} resizeMode="contain">
-              <Text style={styles.bottomBtnText}>{progress >= 100 ? '点亮心愿' : '等待参与'}</Text>
-            </ImageBackground>
-          </TouchableOpacity>
+          <View style={styles.bottomContainer}>
+            {/* Record Link (Optional, if design has it outside standard flow, adding it near bottom or top) 
+                Wait, original design had "Rule" on left, maybe "Record" is not prominently displayed or inside Rule?
+                Let's stick to current design.
+             */}
+            <TouchableOpacity style={styles.bottomBtn} onPress={handleLightUp}>
+              <ImageBackground
+                source={{ uri: progress >= 100 || goodsList.length > 0 ? Images.common.butBgL : Images.common.butBgHui }}
+                style={styles.bottomBtnBg}
+                resizeMode="contain"
+              >
+                <Text style={styles.bottomBtnText}>{progress >= 100 ? '点亮心愿' : (goodsList.length > 0 ? '确认添加' : '等待参与')}</Text>
+              </ImageBackground>
+            </TouchableOpacity>
+          </View>
 
           <Text style={styles.bottomTip}>*本活动最终解释权归本平台所有</Text>
           <View style={{ height: 100 }} />
         </ScrollView>
+
+        <WishRuleModal ref={ruleRef} />
+        <WishRecordModal ref={recordRef} />
+        <WishMaterialModal ref={materialRef} />
       </ImageBackground>
     </View>
   );
@@ -301,8 +446,22 @@ const styles = StyleSheet.create({
     justifyContent: 'center',
     marginTop: 5,
     paddingHorizontal: 20,
+    position: 'relative',
+    zIndex: 20,
+  },
+  progressLabelBox: { width: 40 },
+  progressLabel: { color: '#fff', fontSize: 12 },
+  magicBubble: {
+    position: 'absolute',
+    right: 0,
+    top: -24,
+    zIndex: 10,
+    backgroundColor: '#f44336',
+    borderRadius: 4,
+    paddingHorizontal: 8,
+    paddingVertical: 3,
   },
-  progressLabel: { color: '#fff', fontSize: 12, width: 40 },
+  magicText: { color: '#fff', fontSize: 10 },
   progressBar: {
     flex: 1,
     height: 14,
@@ -368,7 +527,8 @@ const styles = StyleSheet.create({
   closeIcon: { width: '100%', height: '100%' },
 
   // 底部按钮
-  bottomBtn: { alignItems: 'center', marginTop: -10 },
+  bottomContainer: { alignItems: 'center', marginTop: -10 },
+  bottomBtn: { alignItems: 'center' },
   bottomBtnBg: {
     width: 160,
     height: 60,

+ 20 - 0
services/weal.ts

@@ -14,6 +14,12 @@ const apis = {
     LUCKNUMLIST: '/api/luckRoom/mySign',
     ROOM_TYPE: '/api/luckRoom/roomType',
     DATE_TIME_SCOPE: '/api/wallet/dateTimeScope', // 用于首页活动时间判断
+
+    // Wish APIs
+    // Wish APIs (Substitute Goods)
+    WISH_LIST: '/api/substituteGoods/list',
+    WISH_PREVIEW: '/api/substituteOrder/preSubmit',
+    WISH_SUBMIT: '/api/substituteOrder/submit',
 };
 
 export const getWealList = async (current: number, size: number, keyword?: string, type?: string, loading = false) => {
@@ -106,5 +112,19 @@ export default {
         const res = await post('/api/activity/dollMachine/pageAllParticipate', params, { loading });
         return res.data;
     },
+
+    // 祈愿相关 API (置换)
+    getWishList: async () => {
+        const res = await post(apis.WISH_LIST);
+        return res.data;
+    },
+    wishPreview: async (params: { substituteGoodsId: string, inventoryIds: string[] }) => {
+        const res = await post(apis.WISH_PREVIEW, params);
+        return res.data;
+    },
+    wishSubmit: async (params: { substituteGoodsId: string, inventoryIds: string[] }) => {
+        const res = await post(apis.WISH_SUBMIT, params);
+        return res;
+    }
 };