Jelajahi Sumber

福利房板块

zbb 3 bulan lalu
induk
melakukan
8900b2da8e

+ 8 - 0
app/weal/_layout.tsx

@@ -3,9 +3,17 @@ import { Stack } from 'expo-router';
 export default function WealLayout() {
   return (
     <Stack screenOptions={{ headerShown: false }}>
+      <Stack.Screen name="index" />
       <Stack.Screen name="room" />
+      <Stack.Screen name="detail" />
+      <Stack.Screen name="record" />
+      <Stack.Screen name="create" />
+      <Stack.Screen name="create_list" />
+      <Stack.Screen name="store_choose" />
       <Stack.Screen name="catchDoll" />
       <Stack.Screen name="wish" />
     </Stack>
+
   );
 }
+

+ 93 - 0
app/weal/components/RuleModal.tsx

@@ -0,0 +1,93 @@
+import { Ionicons } from '@expo/vector-icons';
+import { ImageBackground } from 'expo-image';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import {
+    Modal,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View
+} from 'react-native';
+
+import { Images } from '@/constants/images';
+
+export interface RuleModalRef {
+    show: () => void;
+    close: () => void;
+}
+
+export const RuleModal = forwardRef<RuleModalRef>((_, ref) => {
+    const [visible, setVisible] = useState(false);
+
+    useImperativeHandle(ref, () => ({
+        show: () => setVisible(true),
+        close: () => setVisible(false),
+    }));
+
+    return (
+        <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
+            <View style={styles.overlay}>
+                <View style={styles.contentContainer}>
+                    <ImageBackground
+                        source={{ uri: Images.mine.dialogContentBg }}
+                        style={styles.bgContainer}
+                        resizeMode="stretch"
+                    >
+                        <View style={styles.textContent}>
+                            <Text style={styles.text}>1.房间仅可用于免费赠送手办或其它礼品</Text>
+                            <Text style={styles.text}>2.您可以查看完整版《用户协议》和《隐私政策》 以便了解我们收集、使用、存储信息的情况,以及对信息的保护措施</Text>
+                            <Text style={styles.text}>3.不支持设置私密房间</Text>
+                            <Text style={styles.text}>4.参与热度仅代表房间热度,不代表具体人数</Text>
+                        </View>
+                    </ImageBackground>
+                    <View style={styles.closeContainer}>
+                        <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
+                            <Ionicons name="close" size={20} color="#444" />
+                        </TouchableOpacity>
+                    </View>
+                </View>
+            </View>
+        </Modal>
+    );
+});
+
+const styles = StyleSheet.create({
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.6)',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    contentContainer: {
+        width: '100%',
+        alignItems: 'center',
+    },
+    bgContainer: {
+        width: 300, // 600rpx approx
+        height: 300, // 600rpx approx
+        justifyContent: 'flex-start',
+        paddingHorizontal: 25, // 50rpx
+        paddingTop: 75, // 150rpx
+    },
+    textContent: {
+        width: '100%',
+    },
+    text: {
+        fontSize: 12,
+        color: '#333',
+        marginBottom: 8,
+        lineHeight: 18,
+    },
+    closeContainer: {
+        marginTop: 20,
+        alignItems: 'center',
+    },
+    closeBtn: {
+        width: 32,
+        height: 32,
+        borderRadius: 16,
+        backgroundColor: '#D8D8D8',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+});

+ 554 - 0
app/weal/create.tsx

@@ -0,0 +1,554 @@
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import React, { useEffect, useState } from 'react';
+import {
+    Alert,
+    FlatList,
+    Image,
+    ImageBackground,
+    Modal,
+    ScrollView,
+    StyleSheet,
+    Switch,
+    Text,
+    TextInput,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { createRoom } from '@/services/weal';
+import event from '@/utils/event';
+
+const DateTimePickerModal = ({ visible, onClose, onConfirm }: any) => {
+    const now = new Date();
+    const [selDate, setSelDate] = useState(now.toISOString().split('T')[0]);
+    const [selHour, setSelHour] = useState(String(now.getHours() + 1).padStart(2, '0'));
+    const [selMin, setSelMin] = useState('00');
+
+    const dates = Array.from({ length: 30 }, (_, i) => {
+        const d = new Date();
+        d.setDate(d.getDate() + i);
+        return d.toISOString().split('T')[0];
+    });
+    // Handle wrap around for hours if needed, though simple 0-23 list is fine for selection
+    const hours = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
+    const mins = ['00', '10', '20', '30', '40', '50'];
+
+    useEffect(() => {
+        if (visible) {
+            if (!selDate) setSelDate(dates[0]);
+        }
+    }, [visible]);
+
+    return (
+        <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
+            <TouchableOpacity style={styles.modalOverlay as any} activeOpacity={1} onPress={onClose}>
+                <TouchableOpacity activeOpacity={1} style={styles.pickerContent as any}>
+                    <View style={styles.pickerHeader as any}>
+                        <TouchableOpacity onPress={onClose}><Text style={styles.cancelText as any}>取消</Text></TouchableOpacity>
+                        <Text style={styles.pickerTitle as any}>设置开奖时间</Text>
+                        <TouchableOpacity onPress={() => onConfirm(`${selDate} ${selHour}:${selMin}:00`)}>
+                            <Text style={styles.confirmText as any}>确定</Text>
+                        </TouchableOpacity>
+                    </View>
+                    <View style={styles.pickerBody as any}>
+                        <FlatList
+                            data={dates}
+                            keyExtractor={i => i}
+                            style={{ flex: 2 }}
+                            renderItem={({ item }) => (
+                                <TouchableOpacity
+                                    style={[styles.pickerItem as any, selDate === item && styles.pickerItemActive]}
+                                    onPress={() => setSelDate(item)}
+                                >
+                                    <Text style={[styles.pickerItemText as any, selDate === item && styles.pickerItemTextActive]}>{item}</Text>
+                                </TouchableOpacity>
+                            )}
+                        />
+                        <FlatList
+                            data={hours}
+                            keyExtractor={i => i}
+                            style={{ flex: 1 }}
+                            renderItem={({ item }) => (
+                                <TouchableOpacity
+                                    style={[styles.pickerItem as any, selHour === item && styles.pickerItemActive]}
+                                    onPress={() => setSelHour(item)}
+                                >
+                                    <Text style={[styles.pickerItemText as any, selHour === item && styles.pickerItemTextActive]}>{item}时</Text>
+                                </TouchableOpacity>
+                            )}
+                        />
+                        <FlatList
+                            data={mins}
+                            keyExtractor={i => i}
+                            style={{ flex: 1 }}
+                            renderItem={({ item }) => (
+                                <TouchableOpacity
+                                    style={[styles.pickerItem as any, selMin === item && styles.pickerItemActive]}
+                                    onPress={() => setSelMin(item)}
+                                >
+                                    <Text style={[styles.pickerItemText as any, selMin === item && styles.pickerItemTextActive]}>{item}分</Text>
+                                </TouchableOpacity>
+                            )}
+                        />
+                    </View>
+                </TouchableOpacity>
+            </TouchableOpacity>
+        </Modal>
+    );
+};
+
+const CreateScreen = () => {
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+
+    const [name, setName] = useState('');
+    const [description, setDescription] = useState('');
+    const [password, setPassword] = useState('');
+    const [mode, setMode] = useState(0); // 0: 进房即参加, 1: 审核后参加
+    const [luckTime, setLuckTime] = useState('');
+    const [goodsList, setGoodsList] = useState<any[]>([]);
+    const [checked, setChecked] = useState(true);
+    const [showPicker, setShowPicker] = useState(false);
+
+    useEffect(() => {
+        const subscription = event.on(event.keys.STORE_CHOOSE, (goods: any[]) => {
+            setGoodsList(prev => {
+                // 过滤掉已经存在的,避免重复添加
+                const existingIds = prev.map(g => g.id);
+                const uniqueNewGoods = goods.filter(g => !existingIds.includes(g.id));
+                return [...prev, ...uniqueNewGoods];
+            });
+        });
+        return () => subscription.remove();
+    }, []);
+
+    const handleSubmit = async () => {
+        if (!name || !description || !luckTime) {
+            Alert.alert('提示', '请完善表单');
+            return;
+        }
+
+        const selectedTime = new Date(luckTime.replace(/-/g, '/'));
+        if (selectedTime <= new Date()) {
+            Alert.alert('提示', '开奖时间必须在当前时间之后');
+            return;
+        }
+
+        if (goodsList.length === 0) {
+            Alert.alert('提示', '请完善赠品');
+            return;
+        }
+        if (!checked) {
+            Alert.alert('提示', '请阅读并同意协议');
+            return;
+        }
+
+        Alert.alert('确定', '确定要创建房间吗?', [
+            { text: '取消', style: 'cancel' },
+            {
+                text: '确定',
+                onPress: async () => {
+                    const params: any = {
+                        description,
+                        inventoryIds: goodsList.map(g => g.id),
+                        luckTime,
+                        name,
+                        password,
+                    };
+                    if (password) {
+                        params.participateMode = mode;
+                    }
+
+                    try {
+                        const res: any = await createRoom(params);
+                        if (res && res.success) {
+                            Alert.alert('成功', '创建成功');
+                            router.back();
+                        } else {
+                            Alert.alert('创建失败', res?.msg || '操作失败');
+                        }
+                    } catch (error) {
+                        Alert.alert('错误', '发生未知错误');
+                    }
+                }
+            }
+        ]);
+    };
+
+    const handleAddGoods = () => {
+        router.push('/weal/store_choose');
+    };
+
+    const handleRemoveGoods = (index: number) => {
+        const newList = [...goodsList];
+        newList.splice(index, 1);
+        setGoodsList(newList);
+    };
+
+    return (
+        <View style={styles.container}>
+            <ImageBackground
+                source={{ uri: Images.common.commonBg }}
+                style={styles.background}
+                resizeMode="cover"
+            >
+                <View style={[styles.header, { paddingTop: insets.top }]}>
+                    <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
+                        <Ionicons name="chevron-back" size={24} color="#fff" />
+                    </TouchableOpacity>
+                    <Text style={styles.title}>创建房间</Text>
+                    <View style={styles.placeholder} />
+                </View>
+
+                <ScrollView contentContainerStyle={styles.scrollContent}>
+                    <View style={styles.tipsBar}>
+                        <Ionicons name="information-circle" size={16} color="#fff" />
+                        <Text style={styles.tipsText}>房间名称及房间介绍不得带有宣传与本平台无关的内容</Text>
+                    </View>
+
+                    <View style={styles.formContainer}>
+                        <View style={styles.formItem}>
+                            <Text style={styles.label}>房间名:</Text>
+                            <TextInput
+                                style={styles.input}
+                                placeholder="输入房间名称,最多15个汉字"
+                                placeholderTextColor="#777"
+                                maxLength={15}
+                                value={name}
+                                onChangeText={setName}
+                            />
+                        </View>
+
+                        <View style={styles.formItem}>
+                            <Text style={styles.label}>活动介绍:</Text>
+                            <TextInput
+                                style={styles.input}
+                                placeholder="输入活动介绍,最多15个汉字"
+                                placeholderTextColor="#777"
+                                maxLength={15}
+                                value={description}
+                                onChangeText={setDescription}
+                            />
+                        </View>
+
+                        <View style={styles.formItem}>
+                            <Text style={styles.label}>进房口令:</Text>
+                            <TextInput
+                                style={styles.input}
+                                placeholder="(可选)用户输入口令才能进房间"
+                                placeholderTextColor="#777"
+                                maxLength={15}
+                                value={password}
+                                onChangeText={setPassword}
+                            />
+                        </View>
+
+                        {!!password && (
+                            <View style={styles.formItem}>
+                                <Text style={styles.label}>参加模式:</Text>
+                                <View style={styles.radioGroup}>
+                                    <TouchableOpacity style={styles.radioItem} onPress={() => setMode(0)}>
+                                        <Ionicons name={mode === 0 ? "radio-button-on" : "radio-button-off"} size={20} color={mode === 0 ? "#F8D668" : "#fff"} />
+                                        <Text style={styles.radioText}>进房即参加</Text>
+                                    </TouchableOpacity>
+                                    <TouchableOpacity style={styles.radioItem} onPress={() => setMode(1)}>
+                                        <Ionicons name={mode === 1 ? "radio-button-on" : "radio-button-off"} size={20} color={mode === 1 ? "#F8D668" : "#fff"} />
+                                        <Text style={styles.radioText}>审核后参加</Text>
+                                    </TouchableOpacity>
+                                </View>
+                            </View>
+                        )}
+
+                        <TouchableOpacity
+                            style={styles.formItem as any}
+                            onPress={() => setShowPicker(true)}
+                        >
+                            <Text style={styles.label}>开奖时间:</Text>
+                            <Text style={[styles.inputValue, !luckTime && styles.placeholderText]}>
+                                {luckTime || '设置开奖时间(一个月内)'}
+                            </Text>
+                        </TouchableOpacity>
+
+                        <View style={styles.goodsSection as any}>
+                            <Text style={styles.label}>赠品:</Text>
+                            <View style={styles.goodsList as any}>
+                                {goodsList.map((item, index) => (
+                                    <View key={item.id} style={styles.goodsItem as any}>
+                                        <Image source={{ uri: item.spu.cover }} style={styles.goodsImg} />
+                                        <TouchableOpacity
+                                            style={styles.removeBtn as any}
+                                            onPress={() => handleRemoveGoods(index)}
+                                        >
+                                            <Ionicons name="close-circle" size={20} color="#ff4d4f" />
+                                        </TouchableOpacity>
+                                    </View>
+                                ))}
+                                <TouchableOpacity style={styles.addBtn as any} onPress={handleAddGoods}>
+                                    <Ionicons name="add" size={40} color="rgba(255,255,255,0.5)" />
+                                </TouchableOpacity>
+                            </View>
+                        </View>
+                    </View>
+                </ScrollView>
+
+                <DateTimePickerModal
+                    visible={showPicker}
+                    onClose={() => setShowPicker(false)}
+                    onConfirm={(val: string) => {
+                        setLuckTime(val);
+                        setShowPicker(false);
+                    }}
+                />
+
+                <View style={[styles.footer as any, { paddingBottom: Math.max(insets.bottom, 20) }]}>
+                    <View style={styles.agreementRow as any}>
+                        <Text style={styles.agreementText}>阅读并同意</Text>
+                        <TouchableOpacity><Text style={styles.agreementLink}>《福利房使用协议》</Text></TouchableOpacity>
+                        <Switch
+                            value={checked}
+                            onValueChange={setChecked}
+                            trackColor={{ false: "#767577", true: "#81b0ff" }}
+                            thumbColor={checked ? "#f5dd4b" : "#f4f3f4"}
+                            style={styles.switch}
+                        />
+                    </View>
+                    <TouchableOpacity style={styles.submitBtn as any} onPress={handleSubmit}>
+                        <ImageBackground
+                            source={{ uri: Images.common.butBgL }}
+                            style={styles.submitBtnBg as any}
+                            resizeMode="stretch"
+                        >
+                            <Text style={styles.submitBtnText}>确认创建</Text>
+                        </ImageBackground>
+                    </TouchableOpacity>
+                </View>
+
+            </ImageBackground>
+        </View>
+    );
+};
+
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+    },
+    background: {
+        flex: 1,
+    },
+    header: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        justifyContent: 'space-between',
+        paddingHorizontal: 15,
+        height: 90,
+    },
+    backBtn: {
+        width: 40,
+        height: 40,
+        justifyContent: 'center',
+    },
+    title: {
+        fontSize: 18,
+        fontWeight: 'bold',
+        color: '#fff',
+    },
+    placeholder: {
+        width: 40,
+    },
+    scrollContent: {
+        paddingBottom: 200,
+    },
+    tipsBar: {
+        backgroundColor: '#209AE5',
+        flexDirection: 'row',
+        alignItems: 'center',
+        padding: 10,
+        paddingHorizontal: 15,
+    },
+    tipsText: {
+        color: '#fff',
+        fontSize: 12,
+        marginLeft: 8,
+    },
+    formContainer: {
+        backgroundColor: 'rgba(255, 255, 255, 0.1)',
+        margin: 15,
+        padding: 15,
+        borderRadius: 8,
+    },
+    formItem: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        paddingVertical: 15,
+        borderBottomWidth: 1,
+        borderBottomColor: 'rgba(255,255,255,0.1)',
+    },
+    label: {
+        width: 80,
+        fontSize: 14,
+        color: '#fff',
+    },
+    input: {
+        flex: 1,
+        fontSize: 14,
+        color: '#fff',
+        padding: 0,
+    },
+    inputValue: {
+        flex: 1,
+        fontSize: 14,
+        color: '#fff',
+    },
+    placeholderText: {
+        color: '#777',
+    },
+    radioGroup: {
+        flex: 1,
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+    },
+    radioItem: {
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    radioText: {
+        color: '#fff',
+        fontSize: 12,
+        marginLeft: 5,
+    },
+    goodsSection: {
+        paddingVertical: 15,
+    },
+    goodsList: {
+        flexDirection: 'row',
+        flexWrap: 'wrap',
+        marginTop: 10,
+    },
+    goodsItem: {
+        width: 80,
+        height: 80,
+        marginRight: 10,
+        marginBottom: 10,
+        backgroundColor: 'rgba(255,255,255,0.1)',
+        borderRadius: 8,
+        padding: 5,
+        position: 'relative',
+    },
+    goodsImg: {
+        width: '100%',
+        height: '100%',
+        borderRadius: 4,
+    },
+    removeBtn: {
+        position: 'absolute',
+        top: -8,
+        right: -8,
+    },
+    addBtn: {
+        width: 80,
+        height: 80,
+        backgroundColor: 'rgba(255,255,255,0.1)',
+        borderRadius: 8,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    footer: {
+        position: 'absolute',
+        bottom: 0,
+        left: 0,
+        right: 0,
+        alignItems: 'center',
+        paddingTop: 10,
+        backgroundColor: 'rgba(0,0,0,0.5)',
+    },
+    agreementRow: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        marginBottom: 10,
+    },
+    agreementText: {
+        color: '#fff',
+        fontSize: 12,
+    },
+    agreementLink: {
+        color: '#50DAFF',
+        fontSize: 12,
+    },
+    switch: {
+        transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
+        marginLeft: 5,
+    },
+    submitBtn: {
+        width: 200,
+        height: 60,
+    },
+    submitBtnBg: {
+        width: '100%',
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    submitBtnText: {
+        fontSize: 16,
+        fontWeight: '800',
+        color: '#fff',
+    },
+    modalOverlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.6)',
+        justifyContent: 'flex-end',
+    },
+    pickerContent: {
+        backgroundColor: '#fff',
+        borderTopLeftRadius: 20,
+        borderTopRightRadius: 20,
+        height: 350,
+    },
+    pickerHeader: {
+        flexDirection: 'row',
+        justifyContent: 'space-between',
+        alignItems: 'center',
+        padding: 15,
+        borderBottomWidth: 1,
+        borderBottomColor: '#eee',
+    },
+    pickerTitle: {
+        fontSize: 16,
+        fontWeight: 'bold',
+        color: '#333',
+    },
+    cancelText: {
+        color: '#999',
+        fontSize: 14,
+    },
+    confirmText: {
+        color: '#209AE5',
+        fontSize: 14,
+        fontWeight: 'bold',
+    },
+    pickerBody: {
+        flex: 1,
+        flexDirection: 'row',
+    },
+    pickerItem: {
+        height: 44,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    pickerItemActive: {
+        backgroundColor: '#f5f5f5',
+    },
+    pickerItemText: {
+        fontSize: 14,
+        color: '#666',
+    },
+    pickerItemTextActive: {
+        color: '#209AE5',
+        fontWeight: 'bold',
+    },
+});
+
+export default CreateScreen;

+ 308 - 0
app/weal/create_list.tsx

@@ -0,0 +1,308 @@
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+    ActivityIndicator,
+    FlatList,
+    Image,
+    ImageBackground,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getMyCreateList } from '@/services/weal';
+
+const CreateListScreen = () => {
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+    const [list, setList] = useState<any[]>([]);
+    const [loading, setLoading] = useState(false);
+    const [refreshing, setRefreshing] = useState(false);
+    const [page, setPage] = useState(1);
+    const [hasMore, setHasMore] = useState(true);
+
+    const loadData = useCallback(async (isRefresh = false) => {
+        if (loading) return;
+        const curPage = isRefresh ? 1 : page;
+        setLoading(true);
+        try {
+            const data = await getMyCreateList(curPage, 20);
+            if (data) {
+                setList(prev => (isRefresh ? data : [...prev, ...data]));
+                setHasMore(data.length === 20);
+                setPage(curPage + 1);
+            }
+        } catch (err) {
+            console.error(err);
+        } finally {
+            setLoading(false);
+            setRefreshing(false);
+        }
+    }, [loading, page]);
+
+    useEffect(() => {
+        loadData(true);
+    }, []);
+
+    const onRefresh = () => {
+        setRefreshing(true);
+        loadData(true);
+    };
+
+    const onEndReached = () => {
+        if (hasMore && !loading) {
+            loadData();
+        }
+    };
+
+    const getTypeName = (type: string) => {
+        switch (type) {
+            case 'COMMON': return '福利房';
+            case 'PASSWORD': return '口令房';
+            case 'EUROPEAN_GAS': return '欧气房';
+            case 'ACHIEVEMENT': return '成就房';
+            default: return '未知';
+        }
+    };
+
+    const renderItem = ({ item }: { item: any }) => (
+        <TouchableOpacity
+            style={styles.itemContainer}
+            onPress={() => router.push({ pathname: '/weal/detail', params: { id: item.id } })}
+        >
+            <ImageBackground
+                source={{ uri: Images.welfare.roomItemBg }}
+                style={styles.itemBg}
+                resizeMode="stretch"
+            >
+                <View style={styles.itemContent}>
+                    <View style={styles.imgContainer}>
+                        <ImageBackground source={{ uri: Images.welfare.roomItemImgBg }} style={styles.imgBg} resizeMode="contain">
+                            {item.luckRoomGoodsList?.[0]?.spu?.cover && (
+                                <Image
+                                    source={{ uri: item.luckRoomGoodsList[0].spu.cover }}
+                                    style={styles.goodsImg}
+                                />
+                            )}
+                        </ImageBackground>
+                    </View>
+
+                    <View style={styles.infoContainer}>
+                        <Text style={styles.roomName} numberOfLines={1}>{item.name}</Text>
+                        <View style={styles.row}>
+                            <Text style={styles.typeName}>{getTypeName(item.type)}</Text>
+                            <Text style={styles.goodsNum}>共{item.goodsQuantity}件赠品</Text>
+                        </View>
+                    </View>
+
+                    <View style={styles.btnContainer}>
+                        <TouchableOpacity
+                            style={styles.auditBtn}
+                            onPress={() => {
+                                // router.push({ pathname: '/weal/audit_list', params: { id: item.id } })
+                            }}
+                        >
+                            <Text style={styles.auditBtnText}>
+                                {item.status === 1 && item.participateMode === 1 ? '审核列表' : '详情'}
+                            </Text>
+                        </TouchableOpacity>
+                    </View>
+                </View>
+            </ImageBackground>
+        </TouchableOpacity>
+    );
+
+    return (
+        <View style={styles.container}>
+            <ImageBackground
+                source={{ uri: Images.common.commonBg }}
+                style={styles.background}
+                resizeMode="cover"
+            >
+                <View style={[styles.header, { paddingTop: insets.top }]}>
+                    <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
+                        <Ionicons name="chevron-back" size={24} color="#fff" />
+                    </TouchableOpacity>
+                    <Text style={styles.title}>我创建的</Text>
+                    <View style={styles.placeholder} />
+                </View>
+
+                <FlatList
+                    data={list}
+                    renderItem={renderItem}
+                    keyExtractor={(item) => item.id.toString()}
+                    contentContainerStyle={styles.listContent}
+                    onRefresh={onRefresh}
+                    refreshing={refreshing}
+                    onEndReached={onEndReached}
+                    onEndReachedThreshold={0.5}
+                    ListEmptyComponent={() => !loading && (
+                        <View style={styles.emptyContainer as any}>
+                            <Text style={styles.emptyText as any}>暂无创建记录</Text>
+                        </View>
+                    )}
+                    ListFooterComponent={() => loading && (
+                        <ActivityIndicator size="small" color="#fff" style={{ marginVertical: 20 }} />
+                    )}
+                />
+
+                <View style={[styles.footer as any, { paddingBottom: Math.max(insets.bottom, 20) }]}>
+                    <TouchableOpacity
+                        style={styles.createBtn as any}
+                        onPress={() => router.push('/weal/create')}
+                    >
+                        <ImageBackground
+                            source={{ uri: Images.common.loginBtn }}
+                            style={styles.createBtnBg as any}
+                            resizeMode="stretch"
+                        >
+                            <Text style={styles.createBtnText}>创建房间</Text>
+                        </ImageBackground>
+                    </TouchableOpacity>
+                </View>
+
+            </ImageBackground>
+        </View>
+    );
+};
+
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+    },
+    background: {
+        flex: 1,
+    },
+    header: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        justifyContent: 'space-between',
+        paddingHorizontal: 15,
+        height: 90,
+    },
+    backBtn: {
+        width: 40,
+        height: 40,
+        justifyContent: 'center',
+    },
+    title: {
+        fontSize: 18,
+        fontWeight: 'bold',
+        color: '#fff',
+    },
+    placeholder: {
+        width: 40,
+    },
+    listContent: {
+        padding: 15,
+        paddingBottom: 120,
+    },
+    itemContainer: {
+        height: 84,
+        marginBottom: 6,
+    },
+    itemBg: {
+        flex: 1,
+    },
+    itemContent: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        paddingHorizontal: 16,
+        height: '100%',
+    },
+    imgContainer: {
+        width: 58,
+        height: 58,
+        marginRight: 14,
+    },
+    imgBg: {
+        width: '100%',
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+        padding: 7,
+    },
+    goodsImg: {
+        width: '100%',
+        height: '100%',
+    },
+    infoContainer: {
+        flex: 1,
+    },
+    roomName: {
+        fontSize: 14,
+        color: '#fff',
+        fontWeight: '400',
+        marginBottom: 10,
+        textShadowColor: 'rgba(0,0,0,1)',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    row: {
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    typeName: {
+        fontSize: 12,
+        color: '#2E0000',
+        fontWeight: 'bold',
+        marginRight: 10,
+    },
+    goodsNum: {
+        fontSize: 10,
+        color: '#2E0000',
+        fontWeight: 'normal',
+    },
+    btnContainer: {
+        marginLeft: 10,
+    },
+    auditBtn: {
+        paddingHorizontal: 12,
+        paddingVertical: 6,
+    },
+    auditBtnText: {
+        fontSize: 12,
+        color: '#fff',
+        fontWeight: 'bold',
+    },
+    footer: {
+        position: 'absolute',
+        bottom: 0,
+        left: 0,
+        right: 0,
+        alignItems: 'center',
+        paddingTop: 10,
+    },
+    createBtn: {
+        width: 160,
+        height: 60,
+    },
+    createBtnBg: {
+        width: '100%',
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    createBtnText: {
+        fontSize: 15,
+        fontWeight: '800',
+        color: '#fff',
+        textShadowColor: 'rgba(0,0,0,1)',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    emptyContainer: {
+        paddingTop: 100,
+        alignItems: 'center',
+    },
+    emptyText: {
+        color: 'rgba(255,255,255,0.6)',
+        fontSize: 14,
+    },
+});
+
+export default CreateListScreen;

+ 397 - 0
app/weal/detail.tsx

@@ -0,0 +1,397 @@
+import { Image } from 'expo-image';
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import {
+    ActivityIndicator,
+    Alert,
+    ImageBackground,
+    Modal,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TextInput,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getWealDetail, getWinningRecord, joinWealRoom } from '@/services/weal';
+
+const TYPE_MAP: any = {
+    COMMON: { title: '福利房' },
+    PASSWORD: { title: '口令房' },
+    ACHIEVEMENT: { title: '成就房' },
+    EUROPEAN_GAS: { title: '欧气房' },
+    HONOR_ROLL: { title: '荣耀榜' },
+};
+
+export default function WealDetailScreen() {
+    const { id } = useLocalSearchParams<{ id: string }>();
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+    const [loading, setLoading] = useState(true);
+    const [data, setData] = useState<any>(null);
+    const [leftTime, setLeftTime] = useState(0);
+    const [scrollTop, setScrollTop] = useState(0);
+    const [winVisible, setWinVisible] = useState(false);
+    const [winRecords, setWinRecords] = useState([]);
+    const [joinVisible, setJoinVisible] = useState(false);
+    const [password, setPassword] = useState('');
+
+    const timerRef = useRef<any>(null);
+
+    const loadData = useCallback(async (showLoading = false) => {
+        if (showLoading) setLoading(true);
+        try {
+            const res = await getWealDetail(id as string);
+            if (res) {
+                setData(res);
+                setLeftTime(res.leftTime);
+            }
+        } catch (error) {
+            console.error('加载详情失败:', error);
+        }
+        setLoading(false);
+    }, [id]);
+
+    useEffect(() => {
+        loadData(true);
+        return () => stopTimer();
+    }, [loadData]);
+
+    useEffect(() => {
+        if (data?.status === 1 && leftTime > 0) {
+            startTimer();
+        } else {
+            stopTimer();
+        }
+    }, [data, leftTime]);
+
+    const startTimer = () => {
+        stopTimer();
+        timerRef.current = setInterval(() => {
+            setLeftTime(prev => {
+
+                if (prev <= 0) {
+                    stopTimer();
+                    loadData();
+                    return 0;
+                }
+                return prev - 1000;
+            });
+        }, 1000);
+    };
+
+    const stopTimer = () => {
+        if (timerRef.current) {
+            clearInterval(timerRef.current);
+            timerRef.current = null;
+        }
+    };
+
+    const formatLeftTime = () => {
+        if (leftTime <= 0) return '00:00:00';
+        let second = Math.floor(leftTime / 1000);
+        const d = Math.floor(second / (24 * 3600));
+        second %= 24 * 3600;
+        const h = Math.floor(second / 3600);
+        second %= 3600;
+        const m = Math.floor(second / 60);
+        const s = second % 60;
+
+        let res = '';
+        if (d > 0) res += `${d}天`;
+        res += `${h.toString().padStart(2, '0')}时${m.toString().padStart(2, '0')}分${s.toString().padStart(2, '0')}秒`;
+        return res;
+    };
+
+    const handleJoin = async () => {
+        if (data.status !== 1 || data.myParticipatedFlag === 1) return;
+
+        if (data.type === 'PASSWORD') {
+            setJoinVisible(true);
+        } else {
+            try {
+                const res = await joinWealRoom(id as string, '');
+                if (res.success) {
+                    Alert.alert('提示', '加入成功');
+                    loadData();
+                } else {
+                    Alert.alert('错误', res.msg || '加入失败');
+                }
+            } catch (error) {
+                Alert.alert('错误', '请求异常');
+            }
+        }
+    };
+
+    const handleJoinWithPassword = async () => {
+        if (!password) return;
+        try {
+            const res = await joinWealRoom(id as string, password);
+            if (res.success) {
+                Alert.alert('提示', '加入成功');
+                setJoinVisible(false);
+                setPassword('');
+                loadData();
+            } else {
+                Alert.alert('错误', res.msg || '口令错误');
+            }
+        } catch (error) {
+            Alert.alert('错误', '请求异常');
+        }
+    };
+
+    const showWinRecords = async () => {
+        try {
+            const res = await getWinningRecord(id as string);
+            setWinRecords(res || []);
+            setWinVisible(true);
+        } catch (error) {
+            console.error('获取中奖记录失败:', error);
+        }
+    };
+
+    if (loading) {
+        return <View style={styles.loading}><ActivityIndicator color="#fff" /></View>;
+    }
+
+    const headerBg = scrollTop > 50 ? '#333' : 'transparent';
+
+    return (
+        <View style={styles.container}>
+            <StatusBar barStyle="light-content" />
+            <ImageBackground source={{ uri: Images.mine.kaixinMineBg }} style={styles.background} resizeMode="cover">
+                {/* 导航 */}
+                <View style={[styles.nav, { paddingTop: insets.top, backgroundColor: headerBg }]}>
+                    <TouchableOpacity onPress={() => router.back()} style={styles.backBtn}>
+                        <Text style={styles.backText}>←</Text>
+                    </TouchableOpacity>
+                    <Text style={styles.navTitle}>{TYPE_MAP[data.type]?.title || '详情'}</Text>
+                    <View style={styles.placeholder} />
+                </View>
+
+                <ScrollView
+                    style={styles.scrollView}
+                    onScroll={e => setScrollTop(e.nativeEvent.contentOffset.y)}
+                    scrollEventThrottle={16}
+                    showsVerticalScrollIndicator={false}
+                >
+                    <ImageBackground source={{ uri: Images.mine.kaixinMineHeadBg }} style={styles.headerBg} resizeMode="cover" />
+
+                    <View style={styles.content}>
+                        <View style={styles.roomInfo}>
+                            <Text style={styles.roomName}>{data.name}</Text>
+                            <View style={styles.roomType}>
+                                <Text style={styles.roomTypeText}>{TYPE_MAP[data.type]?.title}</Text>
+                            </View>
+                            <Text style={styles.roomDesc} numberOfLines={1}>{data.description}</Text>
+                        </View>
+
+                        {/* 中奖记录按钮 */}
+                        {data.prizeMode !== 1 && data.type !== 'EUROPEAN_GAS' && (
+                            <TouchableOpacity style={styles.recordBtn} onPress={showWinRecords}>
+                                <ImageBackground source={{ uri: Images.welfare.detail.record }} style={styles.recordBtnBg} resizeMode="contain">
+                                    <Image source={{ uri: Images.welfare.detail.recordIcon }} style={styles.recordIcon} contentFit="contain" />
+                                    <Text style={styles.recordBtnText}>中奖记录</Text>
+                                </ImageBackground>
+                            </TouchableOpacity>
+                        )}
+
+                        {/* 赠品池 */}
+                        <View style={styles.goodsCard}>
+                            <View style={styles.cardHeader}>
+                                <Text style={styles.cardTitle}>赠品池</Text>
+                                <Text style={styles.cardNum}>{data.luckRoomGoodsList?.length}件赠品</Text>
+                            </View>
+
+                            <View style={styles.goodsList}>
+                                {data.luckRoomGoodsList?.map((item: any, index: number) => (
+                                    <View key={index} style={styles.goodsItem}>
+                                        <Image source={{ uri: item.spu.cover }} style={styles.goodsImg} contentFit="contain" />
+                                        <Text style={styles.goodsName} numberOfLines={1}>{item.spu.name}</Text>
+                                        <View style={styles.goodsCountTag}>
+                                            <Text style={styles.goodsCountText}>数量:{item.quantity}</Text>
+                                        </View>
+                                    </View>
+                                ))}
+                            </View>
+                        </View>
+
+                        {/* 参与度 */}
+                        <View style={styles.participantSection}>
+                            <View style={styles.cardHeader}>
+                                <Text style={styles.cardTitle}>参与度</Text>
+                                <Text style={styles.cardNum}>{data.participatingList?.length}个玩家</Text>
+                            </View>
+                            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.userList}>
+                                {data.participatingList?.map((user: any, index: number) => (
+                                    <View key={index} style={styles.userItem}>
+                                        <Image source={{ uri: user.avatar }} style={styles.userAvatar} />
+                                        <Text style={styles.userName} numberOfLines={1}>{user.nickname}</Text>
+                                    </View>
+                                ))}
+                            </ScrollView>
+                        </View>
+                    </View>
+                    <View style={{ height: 120 }} />
+                </ScrollView>
+
+                {/* 底部按钮栏 */}
+                <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
+                    {data.status === 1 && (
+                        <View style={styles.timerRow}>
+                            <Text style={styles.timerLabel}>倒计时:</Text>
+                            <Text style={styles.timerValue}>{formatLeftTime()}</Text>
+                        </View>
+                    )}
+                    <TouchableOpacity
+                        style={[styles.joinBtn, (data.myParticipatedFlag === 1 || data.status !== 1) && styles.joinBtnDisabled]}
+                        onPress={handleJoin}
+                        disabled={data.myParticipatedFlag === 1 || data.status !== 1}
+                    >
+                        <ImageBackground
+                            source={{ uri: data.status === 1 ? Images.common.loginBtn : undefined }}
+                            style={styles.joinBtnBg}
+                            resizeMode="stretch"
+                        >
+                            <Text style={styles.joinBtnText}>
+                                {data.status !== 1 ? '已开奖' : data.myParticipatedFlag === 1 ? (
+                                    data.participateMode === 1 && data.myAuditStatus === 0 ? '待审核' :
+                                        data.participateMode === 1 && data.myAuditStatus === 1 ? '审核不通过' :
+                                            '等待开赏'
+                                ) : '加入房间,即可参与'}
+                            </Text>
+                        </ImageBackground>
+                    </TouchableOpacity>
+                </View>
+
+                {/* 口令弹窗 */}
+                <Modal visible={joinVisible} transparent animationType="fade">
+                    <View style={styles.modalOverlay}>
+                        <View style={styles.modalContent}>
+                            <Text style={styles.modalTitle}>请输入房间口令</Text>
+                            <TextInput
+                                style={styles.modalInput}
+                                value={password}
+                                onChangeText={setPassword}
+                                placeholder="请输入口令"
+                            />
+                            <View style={styles.modalBtns}>
+                                <TouchableOpacity style={styles.modalBtn} onPress={() => setJoinVisible(false)}>
+                                    <Text style={styles.modalBtnText}>取消</Text>
+                                </TouchableOpacity>
+                                <TouchableOpacity style={[styles.modalBtn, styles.modalBtnConfirm]} onPress={handleJoinWithPassword}>
+                                    <Text style={styles.modalBtnText}>确认加入</Text>
+                                </TouchableOpacity>
+                            </View>
+                        </View>
+                    </View>
+                </Modal>
+
+                {/* 中奖记录弹窗 */}
+                <Modal visible={winVisible} transparent animationType="slide">
+                    <View style={styles.winOverlay}>
+                        <View style={styles.winContent}>
+                            <View style={styles.winHeader}>
+                                <Text style={styles.winTitle}>中奖记录</Text>
+                                <TouchableOpacity onPress={() => setWinVisible(false)} style={styles.winClose}>
+                                    <Text style={styles.winCloseText}>×</Text>
+                                </TouchableOpacity>
+                            </View>
+                            <ScrollView style={styles.winList}>
+                                {winRecords.length === 0 ? (
+                                    <View style={styles.empty}><Text style={{ color: '#999' }}>暂无记录</Text></View>
+                                ) : winRecords.map((item: any, index: number) => (
+                                    <View key={index} style={styles.winItem}>
+                                        <Image source={{ uri: item.avatar }} style={styles.winAvatar} />
+                                        <Text style={styles.winUser}>{item.nickname}</Text>
+                                        <Text style={styles.winGot}>获得了</Text>
+                                        <Text style={styles.winGoods} numberOfLines={1}>{item.spu.name}</Text>
+                                        <Image source={{ uri: item.spu.cover }} style={styles.winGoodsImg} contentFit="contain" />
+                                    </View>
+                                ))}
+                            </ScrollView>
+                        </View>
+                    </View>
+                </Modal>
+            </ImageBackground>
+        </View>
+    );
+}
+
+const styles = StyleSheet.create({
+    container: { flex: 1 },
+    background: { flex: 1 },
+    loading: { flex: 1, backgroundColor: '#1a1a2e', justifyContent: 'center', alignItems: 'center' },
+    nav: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 15, height: 90, zIndex: 100 },
+    backBtn: { width: 40 },
+    backText: { color: '#fff', fontSize: 24 },
+    navTitle: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
+    placeholder: { width: 40 },
+    scrollView: { flex: 1 },
+    headerBg: { width: '100%', height: 180, position: 'absolute', top: 0 },
+    content: { paddingHorizontal: 15, marginTop: 90 },
+    roomInfo: { alignItems: 'center', marginBottom: 20 },
+    roomName: { color: '#fff', fontSize: 22, fontWeight: 'bold', textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 2 },
+    roomType: { marginTop: 8, paddingVertical: 4, paddingHorizontal: 12, backgroundColor: 'rgba(255,255,255,0.2)', borderRadius: 10 },
+    roomTypeText: { color: '#fff', fontSize: 12 },
+    roomDesc: { marginTop: 10, color: '#BEBBB3', fontSize: 12 },
+
+    recordBtn: { position: 'absolute', right: 0, top: 0, zIndex: 10 },
+    recordBtnBg: { width: 78, height: 26, justifyContent: 'center', alignItems: 'center', flexDirection: 'row' },
+    recordIcon: { width: 16, height: 16, marginRight: 2 },
+    recordBtnText: { color: '#fff', fontSize: 12, fontWeight: 'bold', textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
+
+    goodsCard: { marginTop: 25, backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 15, padding: 15 },
+    cardHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 15 },
+    cardTitle: { color: '#fff', fontSize: 18, fontWeight: 'bold' },
+    cardNum: { color: '#eee', fontSize: 12, marginLeft: 10 },
+    goodsList: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' },
+    goodsItem: { width: '31%', aspectRatio: 0.8, backgroundColor: 'rgba(0,0,0,0.3)', borderRadius: 10, padding: 8, marginBottom: 10, alignItems: 'center' },
+    goodsImg: { width: '80%', height: '60%' },
+    goodsName: { color: '#fff', fontSize: 10, marginTop: 5 },
+    goodsCountTag: { position: 'absolute', left: 0, bottom: 25, backgroundColor: '#FFDD00', paddingHorizontal: 5, borderTopRightRadius: 5, borderBottomRightRadius: 5 },
+    goodsCountText: { color: '#000', fontSize: 8, fontWeight: 'bold' },
+
+    participantSection: { marginTop: 20, backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 15, padding: 15 },
+    userList: { flexDirection: 'row' },
+    userItem: { alignItems: 'center', marginRight: 15, width: 50 },
+    userAvatar: { width: 40, height: 40, borderRadius: 20, borderWidth: 1, borderColor: '#fff' },
+    userName: { color: '#fff', fontSize: 8, marginTop: 5, width: '100%', textAlign: 'center' },
+
+    bottomBar: { position: 'absolute', left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.8)', paddingHorizontal: 15, paddingTop: 10 },
+    timerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: 10 },
+    timerLabel: { color: '#fff', fontSize: 12 },
+    timerValue: { color: '#fdf685', fontSize: 12, fontWeight: 'bold' },
+    joinBtn: { height: 45, borderRadius: 22.5, width: '100%', overflow: 'hidden' },
+    joinBtnDisabled: { backgroundColor: '#666' },
+    joinBtnBg: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center' },
+    joinBtnText: { color: '#fff', fontSize: 14, fontWeight: 'bold' },
+
+    modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', justifyContent: 'center', alignItems: 'center' },
+    modalContent: { width: '80%', backgroundColor: '#fff', borderRadius: 15, padding: 20, alignItems: 'center' },
+    modalTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 20 },
+    modalInput: { width: '100%', height: 45, backgroundColor: '#f5f5f5', borderRadius: 8, paddingHorizontal: 15, marginBottom: 20 },
+    modalBtns: { flexDirection: 'row', justifyContent: 'space-between', width: '100%' },
+    modalBtn: { flex: 0.45, height: 40, justifyContent: 'center', alignItems: 'center', borderRadius: 20, backgroundColor: '#eee' },
+    modalBtnConfirm: { backgroundColor: '#e79018' },
+    modalBtnText: { fontWeight: 'bold' },
+
+    winOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
+    winContent: { backgroundColor: '#fff', height: '60%', borderTopLeftRadius: 20, borderTopRightRadius: 20, padding: 20 },
+    winHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 },
+    winTitle: { fontSize: 18, fontWeight: 'bold' },
+    winClose: { width: 30, height: 30, backgroundColor: '#eee', borderRadius: 15, justifyContent: 'center', alignItems: 'center' },
+    winCloseText: { fontSize: 20, color: '#999' },
+    winList: { flex: 1 },
+    winItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f5f5f5' },
+    winAvatar: { width: 30, height: 30, borderRadius: 15 },
+    winUser: { marginLeft: 10, fontSize: 12, width: 60 },
+    winGot: { fontSize: 12, color: '#999', marginHorizontal: 5 },
+    winGoods: { flex: 1, fontSize: 12 },
+    winGoodsImg: { width: 30, height: 30, marginLeft: 10 },
+    empty: { alignItems: 'center', padding: 30 },
+});

+ 199 - 0
app/weal/index.tsx

@@ -0,0 +1,199 @@
+import { useRouter } from 'expo-router';
+import React, { useEffect, useState } from 'react';
+import {
+    ImageBackground,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getDateTimeScope, getRoomTypePermission } from '@/services/weal';
+
+export default function WealIndexScreen() {
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+    const [showSection, setShowSection] = useState(false);
+    const [startTime, setStartTime] = useState('');
+    const [endTime, setEndTime] = useState('');
+    const [wishSwitch, setWishSwitch] = useState(false);
+    const [roomSwitch, setRoomSwitch] = useState(false);
+
+    useEffect(() => {
+        loadConfig();
+    }, []);
+
+    const loadConfig = async () => {
+        try {
+            // 获取时间范围
+            const timeRes = await getDateTimeScope();
+            if (timeRes && timeRes.startTime && timeRes.endTime) {
+                setStartTime(timeRes.startTime.substring(0, 10));
+                setEndTime(timeRes.endTime.substring(0, 10));
+
+                const now = new Date().getTime();
+                const start = new Date(timeRes.startTime.replace(/-/g, '/')).getTime();
+                const end = new Date(timeRes.endTime.replace(/-/g, '/')).getTime();
+                setShowSection(now >= start && now <= end);
+            }
+
+            // 获取开关配置 (模拟 getParamConfig)
+            // 原项目是 getParamConfig('wish_on') 和 getParamConfig('roomcost_on')
+            // 这里假设 getRoomTypePermission 中包含相关信息,或者需要额外的配置接口
+            const permission = await getRoomTypePermission();
+            setRoomSwitch(permission?.roomConfig !== 0);
+            setWishSwitch(true); // 默认开启或根据实际接口调整
+        } catch (error) {
+            console.error('加载福利房配置失败:', error);
+        }
+    };
+
+    const toRouter = (path: string) => {
+        router.push(path as any);
+    };
+
+    return (
+        <View style={styles.container}>
+            <StatusBar barStyle="light-content" />
+            <ImageBackground source={{ uri: Images.mine.kaixinMineBg }} style={styles.background} resizeMode="cover">
+                {/* 导航栏 */}
+                <View style={[styles.nav, { paddingTop: insets.top }]}>
+                    <Text style={styles.navTitle}>福利</Text>
+                </View>
+
+                <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
+                    {/* 头部标题区 */}
+                    <ImageBackground source={{ uri: Images.welfare.wealTitle }} style={styles.head} resizeMode="contain">
+                        <View style={styles.headContent}>
+                            <Text style={styles.headTitle}>限时福利活动</Text>
+                            <Text style={styles.headSub}>限时活动,不定时开放</Text>
+                        </View>
+                    </ImageBackground>
+
+                    {/* 活动入口列表 */}
+                    {showSection && (
+                        <View style={styles.selectSection}>
+                            {roomSwitch && (
+                                <TouchableOpacity
+                                    activeOpacity={0.8}
+                                    onPress={() => toRouter('/weal/room')}
+                                    style={styles.itemContainer}
+                                >
+                                    <ImageBackground source={{ uri: Images.welfare.indexItem1 }} style={styles.item} resizeMode="stretch">
+                                        <View style={styles.dateTag}>
+                                            <Text style={styles.dateText}>开放时间:{startTime}至{endTime}</Text>
+                                        </View>
+                                    </ImageBackground>
+                                </TouchableOpacity>
+                            )}
+
+                            <TouchableOpacity
+                                activeOpacity={0.8}
+                                onPress={() => toRouter('/weal/catchDoll')}
+                                style={styles.itemContainer}
+                            >
+                                <ImageBackground source={{ uri: Images.welfare.indexItem2 }} style={styles.item} resizeMode="stretch">
+                                    <View style={styles.dateTag}>
+                                        <Text style={styles.dateText}>开放时间:{startTime}至{endTime}</Text>
+                                    </View>
+                                </ImageBackground>
+                            </TouchableOpacity>
+
+                            {wishSwitch && (
+                                <TouchableOpacity
+                                    activeOpacity={0.8}
+                                    onPress={() => toRouter('/weal/wish')}
+                                    style={styles.itemContainer}
+                                >
+                                    <ImageBackground source={{ uri: Images.welfare.indexItem3 }} style={styles.item} resizeMode="stretch">
+                                        <View style={styles.dateTag}>
+                                            <Text style={styles.dateText}>开放时间:{startTime}至{endTime}</Text>
+                                        </View>
+                                    </ImageBackground>
+                                </TouchableOpacity>
+                            )}
+                        </View>
+                    )}
+
+                    <View style={{ height: 100 }} />
+                </ScrollView>
+            </ImageBackground>
+        </View>
+    );
+}
+
+const styles = StyleSheet.create({
+    container: { flex: 1, backgroundColor: '#52504e' },
+    background: { flex: 1 },
+    nav: {
+        height: 80,
+        justifyContent: 'center',
+        alignItems: 'center',
+        zIndex: 100,
+    },
+    navTitle: {
+        fontSize: 16,
+        fontWeight: 'bold',
+        color: '#fff',
+    },
+    scrollView: { flex: 1 },
+    head: {
+        width: '100%',
+        height: 192, // 385rpx
+        justifyContent: 'center',
+        alignItems: 'center',
+        marginTop: 20,
+    },
+    headContent: {
+        alignItems: 'center',
+    },
+    headTitle: {
+        fontSize: 40, // 80rpx
+        fontWeight: 'bold',
+        color: '#FDF685',
+        textShadowColor: '#E85801',
+        textShadowOffset: { width: 1, height: 2 },
+        textShadowRadius: 1,
+    },
+    headSub: {
+        fontSize: 15, // 30rpx
+        fontWeight: '400',
+        color: '#E85801',
+        textShadowColor: '#fff',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+        marginTop: 5,
+    },
+    selectSection: {
+        width: '100%',
+        paddingHorizontal: 0,
+        marginTop: 15,
+    },
+    itemContainer: {
+        width: '100%',
+        height: 130, // 260rpx
+        marginBottom: 15,
+    },
+    item: {
+        flex: 1,
+        position: 'relative',
+    },
+    dateTag: {
+        position: 'absolute',
+        right: 10,
+        top: 15,
+        backgroundColor: '#FFFFFF',
+        borderRadius: 13,
+        paddingHorizontal: 10,
+        paddingVertical: 2,
+    },
+    dateText: {
+        fontSize: 10,
+        fontWeight: '500',
+        color: '#000000',
+    },
+});

+ 191 - 0
app/weal/record.tsx

@@ -0,0 +1,191 @@
+import { Image } from 'expo-image';
+import { useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+    ActivityIndicator,
+    ImageBackground,
+    RefreshControl,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getWealRecord } from '@/services/weal';
+
+interface RecordItem {
+    id: string;
+    name: string;
+    type: string;
+    goodsQuantity: number;
+    officialFlag: number;
+    leftTime: number;
+    user: { avatar: string; username: string };
+    luckRoomGoodsList: { spu: { cover: string } }[];
+    participatingList: any[];
+}
+
+export default function WealRecordScreen() {
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+    const [loading, setLoading] = useState(true);
+    const [refreshing, setRefreshing] = useState(false);
+    const [list, setList] = useState<RecordItem[]>([]);
+    const [pageNum, setPageNum] = useState(1);
+    const [hasMore, setHasMore] = useState(true);
+
+    const loadData = useCallback(async (isRefresh = false) => {
+        if (isRefresh) {
+            setRefreshing(true);
+            setPageNum(1);
+        } else {
+            setLoading(true);
+        }
+
+        try {
+            const page = isRefresh ? 1 : pageNum;
+            const data = await getWealRecord(page, 10);
+            if (data) {
+                if (page === 1) {
+                    setList(data);
+                } else {
+                    setList(prev => [...prev, ...data]);
+                }
+                setHasMore(data.length === 10);
+            }
+        } catch (error) {
+            console.error('加载记录失败:', error);
+        }
+
+        setLoading(false);
+        setRefreshing(false);
+    }, [pageNum]);
+
+    useEffect(() => {
+        loadData();
+    }, [loadData]);
+
+    const handleLoadMore = () => {
+        if (!hasMore || loading || refreshing) return;
+        setPageNum(prev => prev + 1);
+    };
+
+    const isItemType = (type: string) => {
+        const map: Record<string, string> = {
+            COMMON: '福利房',
+            PASSWORD: '口令房',
+            EUROPEAN_GAS: '欧气房',
+            ACHIEVEMENT: '成就房',
+        };
+        return map[type] || type;
+    };
+
+    return (
+        <View style={styles.container}>
+            <StatusBar barStyle="light-content" />
+            <ImageBackground source={{ uri: Images.common.commonBg }} style={styles.background} resizeMode="cover">
+                {/* 导航 */}
+                <View style={[styles.nav, { paddingTop: insets.top }]}>
+                    <TouchableOpacity onPress={() => router.back()} style={styles.backBtn}>
+                        <Text style={styles.backText}>←</Text>
+                    </TouchableOpacity>
+                    <Text style={styles.navTitle}>参与记录</Text>
+                    <View style={styles.placeholder} />
+                </View>
+
+                <ScrollView
+                    style={styles.scrollView}
+                    refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => loadData(true)} tintColor="#fff" />}
+                    onScroll={({ nativeEvent }) => {
+                        const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
+                        if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) {
+                            handleLoadMore();
+                        }
+                    }}
+                    scrollEventThrottle={400}
+                >
+                    <View style={{ height: 20 }} />
+                    <View style={styles.list}>
+                        {list.length === 0 && !loading ? (
+                            <View style={styles.empty}><Text style={styles.emptyText}>暂无记录</Text></View>
+                        ) : list.map((item, index) => (
+                            <TouchableOpacity
+                                key={index}
+                                style={[styles.itemWrapper, item.leftTime < 1 && styles.grayscale]}
+                                activeOpacity={0.8}
+                                onPress={() => router.push({ pathname: '/weal/detail', params: { id: item.id } } as any)}
+                            >
+                                <ImageBackground source={{ uri: Images.welfare.roomItemBg }} style={styles.item} resizeMode="stretch">
+                                    {item.officialFlag === 1 && (
+                                        <View style={styles.officialBadge}>
+                                            <Image source={{ uri: Images.welfare.official }} style={styles.officialImg} contentFit="contain" />
+                                        </View>
+                                    )}
+                                    <ImageBackground source={{ uri: Images.welfare.roomItemImgBg }} style={styles.roomCover} resizeMode="contain">
+                                        <Image source={{ uri: item.luckRoomGoodsList[0]?.spu?.cover }} style={styles.roomCoverImg} contentFit="cover" />
+                                    </ImageBackground>
+                                    <View style={styles.roomInfo}>
+                                        <View style={styles.roomTop}>
+                                            <Text style={styles.roomName} numberOfLines={1}>{item.name}</Text>
+                                            <Text style={styles.roomTypeLabel}>{isItemType(item.type)}</Text>
+                                        </View>
+                                        <View style={styles.roomBottom}>
+                                            <View style={styles.userInfo}>
+                                                <Image source={{ uri: item.user.avatar }} style={styles.userAvatar} />
+                                                <Text style={styles.userName} numberOfLines={1}>{item.user.username}</Text>
+                                            </View>
+                                            <Text style={styles.goodsNum}>共{item.goodsQuantity}件赠品</Text>
+                                            <View style={styles.participantBox}>
+                                                <Image source={{ uri: Images.welfare.participationIcon }} style={styles.participantIcon} />
+                                                <Text style={styles.participantNum}>{item.participatingList.length}</Text>
+                                            </View>
+                                        </View>
+                                    </View>
+                                </ImageBackground>
+                            </TouchableOpacity>
+                        ))}
+                    </View>
+                    {loading && <ActivityIndicator color="#fff" style={{ marginVertical: 20 }} />}
+                    <View style={{ height: 100 }} />
+                </ScrollView>
+            </ImageBackground>
+        </View>
+    );
+}
+
+const styles = StyleSheet.create({
+    container: { flex: 1, backgroundColor: '#1a1a2e' },
+    background: { flex: 1 },
+    nav: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 15, height: 90 },
+    backBtn: { width: 40 },
+    backText: { color: '#fff', fontSize: 24 },
+    navTitle: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
+    placeholder: { width: 40 },
+    scrollView: { flex: 1 },
+    list: { paddingHorizontal: 15 },
+    itemWrapper: { marginBottom: 6 },
+    grayscale: { opacity: 0.6 }, // 模拟原项目的置灰效果
+    item: { width: '100%', height: 84, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16 },
+    officialBadge: { position: 'absolute', right: 0, top: 0, width: 48, height: 22 },
+    officialImg: { width: '100%', height: '100%' },
+    roomCover: { width: 58, height: 58, justifyContent: 'center', alignItems: 'center', marginRight: 14 },
+    roomCoverImg: { width: 44, height: 44 },
+    roomInfo: { flex: 1 },
+    roomTop: { flexDirection: 'row', alignItems: 'center', marginBottom: 10 },
+    roomName: { flex: 1, color: '#fff', fontSize: 14, textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
+    roomTypeLabel: { color: '#2E0000', fontSize: 12 },
+    roomBottom: { flexDirection: 'row', alignItems: 'center' },
+    userInfo: { flexDirection: 'row', alignItems: 'center', width: '45%' },
+    userAvatar: { width: 24, height: 24, borderRadius: 2, backgroundColor: '#FFDD00', borderWidth: 1.5, borderColor: '#000', marginRight: 5 },
+    userName: { color: '#2E0000', fontSize: 12, fontWeight: 'bold', maxWidth: 60 },
+    goodsNum: { color: '#2E0000', fontSize: 10, width: '35%' },
+    participantBox: { flexDirection: 'row', alignItems: 'center', width: '20%' },
+    participantIcon: { width: 14, height: 14 },
+    participantNum: { color: '#2E0000', fontSize: 12, marginLeft: 3 },
+    empty: { alignItems: 'center', marginTop: 100 },
+    emptyText: { color: '#999', fontSize: 14 },
+});

+ 104 - 66
app/weal/room.tsx

@@ -1,22 +1,23 @@
 import { Image } from 'expo-image';
 import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
-    ActivityIndicator,
-    ImageBackground,
-    RefreshControl,
-    ScrollView,
-    StatusBar,
-    StyleSheet,
-    Text,
-    TextInput,
-    TouchableOpacity,
-    View,
+  ActivityIndicator,
+  ImageBackground,
+  RefreshControl,
+  ScrollView,
+  StatusBar,
+  StyleSheet,
+  Text,
+  TextInput,
+  TouchableOpacity,
+  View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
 import { Images } from '@/constants/images';
-import { get } from '@/services/http';
+import { getRoomTypePermission, getWealList } from '@/services/weal';
+import { RuleModal, RuleModalRef } from './components/RuleModal';
 
 interface RoomItem {
   id: string;
@@ -29,25 +30,6 @@ interface RoomItem {
   participatingList: any[];
 }
 
-const roomTypes = [
-  { name: '全部', value: '' },
-  { name: '福利营', value: 'COMMON' },
-  { name: '欧气营', value: 'EUROPEAN_GAS' },
-  { name: '口令营', value: 'PASSWORD' },
-  { name: '成就营', value: 'ACHIEVEMENT' },
-  { name: '荣耀榜', value: 'GLORY' },
-];
-
-const getTypeLabel = (type: string) => {
-  const map: Record<string, string> = {
-    COMMON: '福利营',
-    PASSWORD: '口令营',
-    EUROPEAN_GAS: '欧气营',
-    ACHIEVEMENT: '成就营',
-  };
-  return map[type] || type;
-};
-
 export default function RoomScreen() {
   const router = useRouter();
   const insets = useSafeAreaInsets();
@@ -55,44 +37,78 @@ export default function RoomScreen() {
   const [refreshing, setRefreshing] = useState(false);
   const [list, setList] = useState<RoomItem[]>([]);
   const [searchVal, setSearchVal] = useState('');
+  const [type, setType] = useState('');
   const [typeIndex, setTypeIndex] = useState(0);
   const [scrollTop, setScrollTop] = useState(0);
+  const [roomTab, setRoomTab] = useState([
+    { name: '全部', value: '' }
+  ]);
+  const ruleRef = useRef<RuleModalRef>(null);
+
+  const loadPermission = async () => {
+    try {
+      const permission = await getRoomTypePermission();
+      let tabs = [{ name: '全部', value: '' }];
+      if (permission && permission.roomConfig !== 0) {
+        tabs.push({ name: '福利房', value: 'COMMON' });
+        tabs.push({ name: '口令房', value: 'PASSWORD' });
+      }
+      if (permission && permission.roomGAS !== 0) {
+        tabs.push({ name: '欧气房', value: 'EUROPEAN_GAS' });
+      }
+      if (permission && permission.roomCjf !== 0) {
+        tabs.push({ name: '成就房', value: 'ACHIEVEMENT' });
+      }
+      setRoomTab(tabs);
+    } catch (error) {
+      console.error('获取房间类型权限失败:', error);
+    }
+  };
 
   const loadData = useCallback(async (isRefresh = false) => {
     if (isRefresh) setRefreshing(true);
     else setLoading(true);
     try {
-      const type = roomTypes[typeIndex].value;
-      const res = await get('/api/luckRoom/list', {
-        pageNum: 1,
-        pageSize: 20,
-        keyword: searchVal,
-        type: type || undefined,
-      });
-      setList(res.data?.records || res.data || []);
+      const data = await getWealList(1, 20, searchVal, type);
+      setList(data || []);
     } catch (error) {
       console.error('加载房间列表失败:', error);
     }
     setLoading(false);
     setRefreshing(false);
-  }, [typeIndex, searchVal]);
+  }, [type, searchVal]);
+
+  useEffect(() => {
+    loadPermission();
+  }, []);
 
   useEffect(() => {
     loadData();
-  }, [typeIndex]);
+  }, [type]);
 
   const handleSearch = () => {
     loadData();
   };
 
-  const handleTypeChange = (index: number) => {
+  const handleTypeChange = (item: any, index: number) => {
     setTypeIndex(index);
+    setType(item.value);
   };
 
   const handleRoomPress = (item: RoomItem) => {
     router.push({ pathname: '/weal/detail', params: { id: item.id } } as any);
   };
 
+  const isItemType = (type: string) => {
+    const map: Record<string, string> = {
+      COMMON: '福利房',
+      PASSWORD: '口令房',
+      EUROPEAN_GAS: '欧气房',
+      ACHIEVEMENT: '成就房',
+    };
+    return map[type] || type;
+  };
+
   const headerBg = scrollTop > 0 ? '#333' : 'transparent';
 
   return (
@@ -104,7 +120,7 @@ export default function RoomScreen() {
           <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
             <Text style={styles.backText}>←</Text>
           </TouchableOpacity>
-          <Text style={styles.title}>福利</Text>
+          <Text style={styles.title}>房间</Text>
           <View style={styles.placeholder} />
         </View>
 
@@ -135,15 +151,15 @@ export default function RoomScreen() {
               />
             </ImageBackground>
             <View style={styles.funcBtns}>
-              <TouchableOpacity style={styles.funcItem}>
+              <TouchableOpacity style={styles.funcItem} onPress={() => router.push('/weal/create' as any)}>
                 <Image source={{ uri: Images.welfare.roomIcon0 }} style={styles.funcIcon} contentFit="contain" />
                 <Text style={styles.funcText}>创建</Text>
               </TouchableOpacity>
-              <TouchableOpacity style={styles.funcItem}>
+              <TouchableOpacity style={styles.funcItem} onPress={() => router.push('/weal/record' as any)}>
                 <Image source={{ uri: Images.welfare.roomIcon1 }} style={styles.funcIcon} contentFit="contain" />
                 <Text style={styles.funcText}>我的</Text>
               </TouchableOpacity>
-              <TouchableOpacity style={styles.funcItem}>
+              <TouchableOpacity style={styles.funcItem} onPress={() => ruleRef.current?.show()}>
                 <Image source={{ uri: Images.welfare.roomIcon2 }} style={styles.funcIcon} contentFit="contain" />
                 <Text style={styles.funcText}>玩法</Text>
               </TouchableOpacity>
@@ -151,17 +167,19 @@ export default function RoomScreen() {
           </View>
 
           {/* 类型切换 */}
-          <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.typeSection}>
-            {roomTypes.map((item, index) => (
-              <TouchableOpacity
-                key={index}
-                style={styles.typeItem}
-                onPress={() => handleTypeChange(index)}
-              >
-                <Text style={[styles.typeText, typeIndex === index && styles.typeTextActive]}>{item.name}</Text>
-              </TouchableOpacity>
-            ))}
-          </ScrollView>
+          <View style={styles.typeContainer}>
+            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.typeSection}>
+              {roomTab.map((item, index) => (
+                <TouchableOpacity
+                  key={index}
+                  style={styles.typeItem}
+                  onPress={() => handleTypeChange(item, index)}
+                >
+                  <Text style={[styles.typeText, typeIndex === index && styles.typeTextActive]}>{item.name}</Text>
+                </TouchableOpacity>
+              ))}
+            </ScrollView>
+          </View>
 
           {/* 房间列表 */}
           {loading ? (
@@ -197,7 +215,7 @@ export default function RoomScreen() {
                     <View style={styles.roomInfo}>
                       <View style={styles.roomTop}>
                         <Text style={styles.roomName} numberOfLines={1}>{item.name}</Text>
-                        <Text style={styles.roomType}>{getTypeLabel(item.type)}</Text>
+                        <Text style={styles.roomTypeLabel}>{isItemType(item.type)}</Text>
                       </View>
                       <View style={styles.roomBottom}>
                         <View style={styles.userInfo}>
@@ -221,6 +239,7 @@ export default function RoomScreen() {
 
           <View style={{ height: 100 }} />
         </ScrollView>
+        <RuleModal ref={ruleRef} />
       </ImageBackground>
     </View>
   );
@@ -233,13 +252,14 @@ const styles = StyleSheet.create({
     flexDirection: 'row',
     alignItems: 'center',
     justifyContent: 'space-between',
-    paddingHorizontal: 10,
+    paddingHorizontal: 15,
     paddingBottom: 10,
     position: 'absolute',
     top: 0,
     left: 0,
     right: 0,
     zIndex: 100,
+    height: 90,
   },
   backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
   backText: { color: '#fff', fontSize: 20 },
@@ -249,7 +269,7 @@ const styles = StyleSheet.create({
   scrollView: { flex: 1 },
 
   // 顶部搜索和功能区
-  topSection: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, marginBottom: 15 },
+  topSection: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 15, marginTop: 10, marginBottom: 15 },
   searchBox: { flex: 1, height: 37, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 15 },
   searchIcon: { width: 19, height: 19, marginRight: 8 },
   searchInput: { flex: 1, fontSize: 12, color: '#000', padding: 0 },
@@ -259,13 +279,21 @@ const styles = StyleSheet.create({
   funcText: { fontSize: 10, color: '#DFDFDF', marginTop: 2 },
 
   // 类型切换
-  typeSection: { paddingHorizontal: 10, marginBottom: 40 },
-  typeItem: { marginRight: 15, paddingVertical: 8 },
+  typeContainer: { paddingHorizontal: 15, marginBottom: 40 },
+  typeSection: {},
+  typeItem: { marginRight: 20, paddingVertical: 8 },
   typeText: { fontSize: 12, color: '#DFDFDF' },
-  typeTextActive: { color: '#e79018', fontWeight: 'bold', fontSize: 15, textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
+  typeTextActive: {
+    color: '#e79018',
+    fontWeight: 'bold',
+    fontSize: 15,
+    textShadowColor: '#000',
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 1
+  },
 
   // 房间列表
-  roomList: { paddingHorizontal: 10 },
+  roomList: { paddingHorizontal: 15 },
   roomItem: { width: '100%', height: 84, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, marginBottom: 6, position: 'relative' },
   officialBadge: { position: 'absolute', right: 0, top: 0, width: 48, height: 22 },
   officialImg: { width: '100%', height: '100%' },
@@ -275,8 +303,17 @@ const styles = StyleSheet.create({
   roomCoverImg: { width: 44, height: 44 },
   roomInfo: { flex: 1 },
   roomTop: { flexDirection: 'row', alignItems: 'center', marginBottom: 10 },
-  roomName: { flex: 1, color: '#fff', fontSize: 14, fontWeight: '400', marginRight: 10, textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
-  roomType: { color: '#2E0000', fontSize: 12 },
+  roomName: {
+    flex: 1,
+    color: '#fff',
+    fontSize: 14,
+    fontWeight: '400',
+    marginRight: 10,
+    textShadowColor: '#000',
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 1
+  },
+  roomTypeLabel: { color: '#2E0000', fontSize: 12 },
   roomBottom: { flexDirection: 'row', alignItems: 'center' },
   userInfo: { flexDirection: 'row', alignItems: 'center', width: '45%' },
   userAvatar: { width: 24, height: 24, borderRadius: 2, backgroundColor: '#FFDD00', borderWidth: 1.5, borderColor: '#000', marginRight: 5 },
@@ -289,3 +326,4 @@ const styles = StyleSheet.create({
   emptyBox: { alignItems: 'center', paddingTop: 50 },
   emptyText: { color: '#999', fontSize: 14 },
 });
+

+ 328 - 0
app/weal/store_choose.tsx

@@ -0,0 +1,328 @@
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+    ActivityIndicator,
+    FlatList,
+    Image,
+    ImageBackground,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getStore } from '@/services/award';
+import event from '@/utils/event';
+
+const StoreChooseScreen = () => {
+    const router = useRouter();
+    const insets = useSafeAreaInsets();
+    const [list, setList] = useState<any[]>([]);
+    const [selectedIds, setSelectedIds] = useState<string[]>([]);
+    const [loading, setLoading] = useState(false);
+    const [refreshing, setRefreshing] = useState(false);
+    const [page, setPage] = useState(1);
+    const [hasMore, setHasMore] = useState(true);
+    const [tabIndex, setTabIndex] = useState(0);
+
+    const tabs = [
+        { title: '全部', value: '' },
+        { title: '普通', value: 'D' },
+        { title: '隐藏', value: 'C' },
+        { title: '欧皇', value: 'B' },
+        { title: '超神', value: 'A' },
+    ];
+
+    const loadData = useCallback(async (isRefresh = false) => {
+        if (loading) return;
+        const curPage = isRefresh ? 1 : page;
+        setLoading(true);
+        console.log('Fetching store data:', curPage, tabs[tabIndex].value);
+        try {
+            const data = await getStore(curPage, 20, 0, tabs[tabIndex].value);
+            console.log('Store data received:', data);
+            let records = [];
+            if (Array.isArray(data)) {
+                records = data;
+            } else if (data && data.records) {
+                records = data.records;
+            }
+
+            console.log('Parsed records length:', records.length);
+
+            if (records.length > 0) {
+                setList(prev => (isRefresh ? records : [...prev, ...records]));
+                setHasMore(records.length === 20); // Assuming page size is 20
+                setPage(curPage + 1);
+            } else {
+                setList(prev => (isRefresh ? [] : prev));
+                setHasMore(false);
+            }
+        } catch (err) {
+            console.error('Fetch store error:', err);
+            // Alert.alert('Error', '加载数据失败'); // Optional: show alert
+        } finally {
+            setLoading(false);
+            setRefreshing(false);
+        }
+    }, [loading, page, tabIndex]);
+
+    useEffect(() => {
+        loadData(true);
+    }, [tabIndex]);
+
+    const onRefresh = () => {
+        setRefreshing(true);
+        loadData(true);
+    };
+
+    const onEndReached = () => {
+        if (hasMore && !loading) {
+            loadData();
+        }
+    };
+
+    const toggleSelect = (id: string) => {
+        setSelectedIds(prev =>
+            prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
+        );
+    };
+
+    const handleConfirm = () => {
+        if (selectedIds.length === 0) {
+            router.back();
+            return;
+        }
+        const selectedGoods = list.filter(item => selectedIds.includes(item.id));
+        event.emit(event.keys.STORE_CHOOSE, selectedGoods);
+        router.back();
+    };
+
+    const renderItem = ({ item }: { item: any }) => {
+        const isSelected = selectedIds.includes(item.id);
+        return (
+            <TouchableOpacity
+                style={styles.card}
+                onPress={() => toggleSelect(item.id)}
+            >
+                <Image source={{ uri: item.spu.cover }} style={styles.goodsImg} resizeMode="contain" />
+                <View style={styles.info}>
+                    <Text style={styles.goodsName} numberOfLines={2}>{item.spu.name}</Text>
+                    <View style={styles.tagRow}>
+                        <Text style={styles.magicText}>{item.magicAmount} 果实</Text>
+                    </View>
+                </View>
+                <View style={[styles.checkbox, isSelected && styles.checkboxSelected]}>
+                    {isSelected && <Ionicons name="checkmark" size={12} color="#000" />}
+                </View>
+            </TouchableOpacity>
+        );
+    };
+
+    return (
+        <View style={styles.container}>
+            <ImageBackground
+                source={{ uri: Images.mine.kaixinMineBg }}
+                style={styles.background}
+                resizeMode="cover"
+            >
+                <View style={[styles.header, { paddingTop: insets.top }]}>
+                    <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
+                        <Ionicons name="chevron-back" size={24} color="#fff" />
+                    </TouchableOpacity>
+                    <Text style={styles.title}>选择商品</Text>
+                    <View style={styles.placeholder} />
+                </View>
+
+                <View style={styles.tabBar}>
+                    {tabs.map((tab, index) => (
+                        <TouchableOpacity
+                            key={tab.value}
+                            style={[styles.tabItem, tabIndex === index && styles.tabItemActive]}
+                            onPress={() => setTabIndex(index)}
+                        >
+                            <Text style={[styles.tabText, tabIndex === index && styles.tabTextActive]}>
+                                {tab.title}
+                            </Text>
+                        </TouchableOpacity>
+                    ))}
+                </View>
+
+                <FlatList
+                    data={list}
+                    renderItem={renderItem}
+                    keyExtractor={(item) => item.id.toString()}
+                    contentContainerStyle={styles.listContent}
+                    onRefresh={onRefresh}
+                    refreshing={refreshing}
+                    onEndReached={onEndReached}
+                    onEndReachedThreshold={0.5}
+                    ListEmptyComponent={
+                        !loading ? (
+                            <View style={styles.emptyContainer as any}>
+                                <Text style={styles.emptyText as any}>暂无商品</Text>
+                            </View>
+                        ) : null
+                    }
+                    ListFooterComponent={() => loading && (
+                        <ActivityIndicator size="small" color="#fff" style={{ marginVertical: 20 }} />
+                    )}
+                />
+
+                <View style={[styles.footer as any, { paddingBottom: Math.max(insets.bottom, 20) }]}>
+                    <TouchableOpacity style={styles.submitBtn as any} onPress={handleConfirm}>
+                        <ImageBackground
+                            source={{ uri: Images.common.butBgL }}
+                            style={styles.submitBtnBg as any}
+                            resizeMode="stretch"
+                        >
+                            <Text style={styles.submitBtnText}>确认选择放入赠品池</Text>
+                        </ImageBackground>
+                    </TouchableOpacity>
+                </View>
+
+            </ImageBackground>
+        </View>
+    );
+};
+
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+    },
+    background: {
+        flex: 1,
+    },
+    header: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        justifyContent: 'space-between',
+        paddingHorizontal: 15,
+        height: 90,
+    },
+    backBtn: {
+        width: 40,
+        height: 40,
+        justifyContent: 'center',
+    },
+    title: {
+        fontSize: 18,
+        fontWeight: 'bold',
+        color: '#fff',
+    },
+    placeholder: {
+        width: 40,
+    },
+    tabBar: {
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        paddingVertical: 10,
+        backgroundColor: 'rgba(0,0,0,0.3)',
+    },
+    tabItem: {
+        paddingVertical: 6,
+        paddingHorizontal: 12,
+        borderRadius: 15,
+    },
+    tabItemActive: {
+        backgroundColor: 'rgba(248, 214, 104, 0.2)',
+    },
+    tabText: {
+        color: '#ccc',
+        fontSize: 14,
+    },
+    tabTextActive: {
+        color: '#F8D668',
+        fontWeight: 'bold',
+    },
+    listContent: {
+        padding: 15,
+        paddingBottom: 100,
+    },
+    card: {
+        flexDirection: 'row',
+        backgroundColor: '#fff',
+        borderRadius: 8,
+        padding: 10,
+        marginBottom: 12,
+        alignItems: 'center',
+        position: 'relative',
+    },
+    goodsImg: {
+        width: 80,
+        height: 80,
+        borderRadius: 4,
+    },
+    info: {
+        flex: 1,
+        marginLeft: 12,
+        height: 80,
+        justifyContent: 'space-around',
+    },
+    goodsName: {
+        fontSize: 14,
+        color: '#333',
+        fontWeight: 'bold',
+    },
+    tagRow: {
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    magicText: {
+        color: '#666',
+        fontSize: 12,
+    },
+    checkbox: {
+        width: 20,
+        height: 20,
+        borderRadius: 10,
+        borderWidth: 1,
+        borderColor: '#ddd',
+        justifyContent: 'center',
+        alignItems: 'center',
+        position: 'absolute',
+        right: 10,
+        top: 10,
+    },
+    checkboxSelected: {
+        backgroundColor: '#F8D668',
+        borderColor: '#F8D668',
+    },
+    footer: {
+        position: 'absolute',
+        bottom: 0,
+        left: 0,
+        right: 0,
+        alignItems: 'center',
+        paddingTop: 10,
+        backgroundColor: 'rgba(0,0,0,0.5)',
+    },
+    submitBtn: {
+        width: 240,
+        height: 60,
+    },
+    submitBtnBg: {
+        width: '100%',
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    submitBtnText: {
+        fontSize: 15,
+        fontWeight: '800',
+        color: '#fff',
+    },
+    emptyContainer: {
+        alignItems: 'center',
+        paddingTop: 100,
+    },
+    emptyText: {
+        color: '#999',
+        fontSize: 14,
+    },
+});
+
+export default StoreChooseScreen;

+ 12 - 0
constants/images.ts

@@ -142,6 +142,17 @@ export const Images = {
     participationIcon: `${CDN_BASE}/welfare/participationIcon.png`,
     official: `${CDN_BASE}/welfare/official.png`,
     mustBe: `${CDN_BASE}/welfare/mustBe.png`,
+    detail: {
+      record: `${CDN_BASE}/welfare/detail/record.png`,
+      recordIcon: `${CDN_BASE}/welfare/detail/recordIcon.png`,
+      numBg: `${CDN_BASE}/welfare/detail/numBg.png`,
+      cardItemBg: `${CDN_BASE}/welfare/detail/cardItemBg.png`,
+      time: `${CDN_BASE}/welfare/detail/time.png`,
+      transverseItemBg: `${CDN_SUPERMART}/supermart/welfare/detail/transverseItemBg.png`,
+      transverseItemBgOn: `${CDN_SUPERMART}/supermart/welfare/detail/transverseItemBgOn.png`,
+      transverseItemRight: `${CDN_SUPERMART}/supermart/welfare/detail/transverseItemRight.png`,
+      transverseItemRightOn: `${CDN_SUPERMART}/supermart/welfare/detail/transverseItemRightOn.png`,
+    },
   },
   mine: {
     kaixinMineBg: `${CDN_BASE}/mine/kaixinMineBg.png`,
@@ -157,6 +168,7 @@ export const Images = {
     kaixinorder: `${CDN_BASE}/mine/kaixinorder.png`,
     order1: `${CDN_BASE}/mine/order1.png`,
     order2: `${CDN_BASE}/mine/order2.png`,
+    dialogContentBg: `${CDN_BASE}/mine/dialogContentBg.png`,
   },
   // 地址相关
   address: {

+ 14 - 0
services/wallet.ts

@@ -0,0 +1,14 @@
+import { post } from './http';
+
+const apis = {
+    PRE_ONE_KEY: '/api/substituteOrder/preOneKeySubmit',
+};
+
+export const preOneKeySubmit = async () => {
+    const res = await post(apis.PRE_ONE_KEY);
+    return res;
+};
+
+export default {
+    preOneKeySubmit,
+};

+ 96 - 0
services/weal.ts

@@ -0,0 +1,96 @@
+import { get, getL, post } from './http';
+
+const apis = {
+    LIST: '/api/luckRoom',
+    DETAIL: '/api/luckRoom/detail',
+    JOIN: '/api/luckRoom/participate',
+    RECORD: '/api/luckRoom/participateRecordMy',
+    LIST_CREATE: '/api/luckRoom/myCreate',
+    CREATE: '/api/luckRoom/create',
+    ROOM_AUDIT_RECORD: '/api/luckRoom/roomParticipateRecord',
+    ROOM_AUDIT_PASS: '/api/luckRoom/auditPass',
+    ROOM_AUDIT_UNPASS: '/api/luckRoom/auditUnpass',
+    WINNINGRECORD: '/api/luckRoom/winningTheLotteryRecord',
+    LUCKNUMLIST: '/api/luckRoom/mySign',
+    ROOM_TYPE: '/api/luckRoom/roomType',
+    DATE_TIME_SCOPE: '/api/wallet/dateTimeScope', // 用于首页活动时间判断
+};
+
+export const getWealList = async (current: number, size: number, keyword?: string, type?: string, loading = false) => {
+    const res = await post(apis.LIST, { current, size, keyword, type }, { loading });
+    return res.data;
+};
+
+export const getWealDetail = async (roomId: string, loading = false) => {
+    const res = await get(apis.DETAIL, { roomId }, { loading });
+    return res.data;
+};
+
+export const joinWealRoom = async (roomId: string, password = '', shareId?: string) => {
+    const res = await getL(apis.JOIN, { roomId, password, shareId });
+    return res;
+};
+
+export const getWealRecord = async (current: number, size: number, loading = false) => {
+    const res = await post(apis.RECORD, { current, size }, { loading });
+    return res.data;
+};
+
+export const getWinningRecord = async (roomId: string) => {
+    const res = await get(apis.WINNINGRECORD, { roomId });
+    return res.data;
+};
+
+export const getLuckNumList = async (roomId: string) => {
+    const res = await getL(`${apis.LUCKNUMLIST}?roomId=${roomId}`);
+    return res.data;
+};
+
+export const getRoomTypePermission = async () => {
+    const res = await getL(apis.ROOM_TYPE);
+    return res.data;
+};
+
+export const getDateTimeScope = async () => {
+    const res = await get(apis.DATE_TIME_SCOPE);
+    return res.data;
+};
+
+// 后续如有创建相关需求可继续添加
+export const getMyCreateList = async (current: number, size: number, loading = false) => {
+    const res = await post(apis.LIST_CREATE, { current, size }, { loading });
+    return res.data;
+};
+
+export const createRoom = async (params: {
+    description: string;
+    inventoryIds: string[];
+    luckTime: string;
+    name: string;
+    password?: string;
+    participateMode?: number;
+}) => {
+    console.log('Service: createRoom called', params);
+    try {
+        const res = await post(apis.CREATE, params, { loading: true });
+        console.log('Service: createRoom response', res);
+        return res;
+    } catch (error) {
+        console.error('Service: createRoom error', error);
+        return { success: false, msg: '网络或未知错误' };
+    }
+};
+
+export default {
+    getWealList,
+    getWealDetail,
+    joinWealRoom,
+    getWealRecord,
+    getWinningRecord,
+    getLuckNumList,
+    getRoomTypePermission,
+    getDateTimeScope,
+    getMyCreateList,
+    createRoom,
+};
+

+ 20 - 0
utils/event.ts

@@ -0,0 +1,20 @@
+import { DeviceEventEmitter } from 'react-native';
+
+export const EventKeys = {
+    STORE_CHOOSE: 'STORE_CHOOSE',
+    WEAL_REFRESH: 'WEAL_REFRESH',
+};
+
+export const emit = (event: string, data?: any) => {
+    DeviceEventEmitter.emit(event, data);
+};
+
+export const on = (event: string, callback: (data?: any) => void) => {
+    return DeviceEventEmitter.addListener(event, callback);
+};
+
+export default {
+    emit,
+    on,
+    keys: EventKeys,
+};