2
0

3 Commits d4357a7f0c ... d4f863997f

Autor SHA1 Mensagem Data
  zbb d4f863997f fix: hide redemption code entry, bump build number to 7 há 1 mês atrás
  zbb 984a822062 chore: bump version to 1.0.1 há 2 meses atrás
  zbb ce84778cfa fix: remove wechat pay, fix welfare room password, disable invite gift, add alipay callback há 2 meses atrás

+ 2 - 2
app.json

@@ -2,7 +2,7 @@
   "expo": {
     "name": "asios",
     "slug": "asios",
-    "version": "1.0.0",
+    "version": "1.0.1",
     "orientation": "portrait",
     "icon": "./assets/images/icon.png",
     "scheme": ["asios", "alipay2021005175632205"],
@@ -12,7 +12,7 @@
       "supportsTablet": false,
       "bundleIdentifier": "com.asios",
       "appleTeamId": "Y9ZVX3FRX6",
-      "buildNumber": "5",
+      "buildNumber": "7",
       "infoPlist": {
         "ITSAppUsesNonExemptEncryption": false,
         "NSPhotoLibraryUsageDescription": "App需要访问您的相册以便您可以上传头像或保存商品分享图片。",

+ 1 - 1
app/(tabs)/mine.tsx

@@ -314,7 +314,7 @@ export default function MineScreen() {
             {inviteShow && userInfo && (
               <TouchableOpacity
                 style={styles.shortcutItem}
-                onPress={() => handleMenuPress("/invite")}
+                onPress={() => Alert.alert("提示", "暂未开放")}
               >
                 <View style={styles.shortcutIconBox}>
                   <Image

+ 3 - 29
app/award-detail/components/CheckoutModal.tsx

@@ -116,9 +116,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
     // ... (Imports handled via separate edit or assume existing)
 
     const [payConfig, setPayConfig] = useState<any>(null);
-    const [paymentMethod, setPaymentMethod] = useState<"ALIPAY" | "WXPAY">(
-      "ALIPAY",
-    );
+    const [paymentMethod, setPaymentMethod] = useState<"ALIPAY">("ALIPAY");
 
     // ...
 
@@ -208,12 +206,9 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
         // Prioritize Wallet if checked
         if (cashChecked) {
           paymentType = "WALLET";
-        } else if (paymentMethod === "ALIPAY") {
-          // APP端固定使用 ALIPAY_APP,忽略后端配置的 CUSTOMER_SERVICE_LINK
-          paymentType = "ALIPAY_APP";
         } else {
-          // 微信支付也固定使用 WXPAY_APP
-          paymentType = "WXPAY_APP";
+          // 默认只支持支付宝
+          paymentType = "ALIPAY_APP";
         }
 
         const payNum = packFlag ? 1 : num;
@@ -532,27 +527,6 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                         </View>
                       </TouchableOpacity>
                     ) : null}
-
-                    {payConfig?.wxpay?.enabled ? (
-                      <TouchableOpacity
-                        style={styles.payOption}
-                        onPress={() => setPaymentMethod("WXPAY")}
-                      >
-                        <View style={styles.rowLeft}>
-                          <Text style={styles.payLabel}>微信支付</Text>
-                        </View>
-                        <View
-                          style={[
-                            styles.radio,
-                            paymentMethod === "WXPAY" && styles.radioChecked,
-                          ]}
-                        >
-                          {paymentMethod === "WXPAY" ? (
-                            <View style={styles.radioInner} />
-                          ) : null}
-                        </View>
-                      </TouchableOpacity>
-                    ) : null}
                   </View>
                 ) : null}
 

+ 648 - 385
app/dimension/detail.tsx

@@ -1,6 +1,6 @@
-import { Image } from 'expo-image';
-import { useLocalSearchParams, useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Image } from "expo-image";
+import { useLocalSearchParams, useRouter } from "expo-router";
+import React, { useCallback, useEffect, useRef, useState } from "react";
 import {
     ActivityIndicator,
     Alert,
@@ -14,407 +14,670 @@ import {
     TextInput,
     TouchableOpacity,
     View,
-} from 'react-native';
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
 
-import { Images } from '@/constants/images';
-import { getWealDetail, getWinningRecord, joinWealRoom } from '@/services/dimension';
+import { Images } from "@/constants/images";
+import {
+    getWealDetail,
+    getWinningRecord,
+    joinWealRoom,
+} from "@/services/dimension";
 
 const TYPE_MAP: any = {
-    COMMON: { title: '福利房' },
-    PASSWORD: { title: '口令房' },
-    ACHIEVEMENT: { title: '成就房' },
-    EUROPEAN_GAS: { title: '欧气房' },
-    HONOR_ROLL: { title: '荣耀榜' },
+  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);
+  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);
         }
-        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;
+      } 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;
         }
-    };
-
-    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);
+        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 {
-            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 handleShare = async () => {
-        try {
-            const result = await Share.share({
-                message: `快来参与福利房:${data?.name},房间ID:${id}`,
-                title: '福利房分享',
-            });
-            if (result.action === Share.sharedAction) {
-                if (result.activityType) {
-                    // shared with activity type of result.activityType
-                } else {
-                    // shared
-                }
-            } else if (result.action === Share.dismissedAction) {
-                // dismissed
-            }
-        } catch (error: any) {
-            Alert.alert(error.message);
+          if (
+            res.msg &&
+            (res.msg.includes("口令") || res.msg.includes("密码"))
+          ) {
+            setJoinVisible(true);
+          } else {
+            Alert.alert("错误", res.msg || "加入失败");
+          }
         }
-    };
-
-    const showWinRecords = async () => {
-        try {
-            const res = await getWinningRecord(id as string);
-            setWinRecords(res || []);
-            setWinVisible(true);
-        } catch (error) {
-            console.error('获取中奖记录失败:', error);
+      } 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 handleShare = async () => {
+    try {
+      const result = await Share.share({
+        message: `快来参与福利房:${data?.name},房间ID:${id}`,
+        title: "福利房分享",
+      });
+      if (result.action === Share.sharedAction) {
+        if (result.activityType) {
+          // shared with activity type of result.activityType
+        } else {
+          // shared
         }
-    };
-
-    if (loading) {
-        return <View style={styles.loading}><ActivityIndicator color="#fff" /></View>;
+      } else if (result.action === Share.dismissedAction) {
+        // dismissed
+      }
+    } catch (error: any) {
+      Alert.alert(error.message);
     }
+  };
+
+  const showWinRecords = async () => {
+    try {
+      const res = await getWinningRecord(id as string);
+      setWinRecords(res || []);
+      setWinVisible(true);
+    } catch (error) {
+      console.error("获取中奖记录失败:", error);
+    }
+  };
 
-    const headerBg = scrollTop > 50 ? '#333' : 'transparent';
-
+  if (loading) {
     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>
-                    <TouchableOpacity onPress={handleShare} style={styles.backBtn}>
-                        <Image source={{ uri: Images.mine.invite }} style={{ width: 20, height: 20 }} contentFit="contain" />
-                    </TouchableOpacity>
-                </View>
-
-                <ScrollView
-                    style={styles.scrollView}
-                    onScroll={e => setScrollTop(e.nativeEvent.contentOffset.y)}
-                    scrollEventThrottle={16}
-                    showsVerticalScrollIndicator={false}
+      <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>
+          <TouchableOpacity onPress={handleShare} style={styles.backBtn}>
+            <Image
+              source={{ uri: Images.mine.invite }}
+              style={{ width: 20, height: 20 }}
+              contentFit="contain"
+            />
+          </TouchableOpacity>
+        </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"
                 >
-                    <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>
-                            <View style={[styles.userList, { overflow: 'hidden' }]}>
-                                {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>
-                                ))}
-                            </View>
-                        </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>
+                  <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>
-                </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>
+                  </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>
+              <View style={[styles.userList, { overflow: "hidden" }]}>
+                {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>
+                ))}
+              </View>
+            </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: 10, 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 },
+  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: 10,
+    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 },
 });

+ 55 - 0
app/safepay.tsx

@@ -0,0 +1,55 @@
+import { Colors } from "@/constants/Colors";
+import { useLocalSearchParams, useRouter } from "expo-router";
+import { useEffect } from "react";
+import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
+
+/**
+ * Alipay Callback Handler
+ * Handles the "asios://safepay" deep link from Alipay.
+ * Automatically goes back to invoke the native SDK's callback handling.
+ */
+export default function AlipayResult() {
+  const router = useRouter();
+  const params = useLocalSearchParams();
+
+  useEffect(() => {
+    console.log("Alipay callback params:", params);
+
+    // The native SDK (expo-native-alipay) listens for the app verify event
+    // and resolves the Promise in the calling component (Recharge/Checkout).
+    // This route mainly exists to prevent "Unmatched Route" errors.
+    // We simply dismiss it to reveal the underlying screen.
+    const timer = setTimeout(() => {
+      // Use router.dismiss() if available in stack, or back()
+      if (router.canGoBack()) {
+        router.back();
+      } else {
+        // Fallback if launched directly (unlikely for payment callback)
+        router.replace("/");
+      }
+    }, 500); // Short delay to allow smooth transition
+
+    return () => clearTimeout(timer);
+  }, []);
+
+  return (
+    <View style={styles.container}>
+      <ActivityIndicator size="large" color={Colors.neonBlue || "#00F3FF"} />
+      <Text style={styles.text}>正在返回应用...</Text>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    justifyContent: "center",
+    alignItems: "center",
+    backgroundColor: Colors.darkBg || "#121212",
+  },
+  text: {
+    marginTop: 20,
+    color: Colors.textSecondary || "#aaa",
+    fontSize: 14,
+  },
+});

+ 3 - 29
app/treasure-hunt/components/CheckoutModal.tsx

@@ -116,9 +116,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
     // ... (Imports handled via separate edit or assume existing)
 
     const [payConfig, setPayConfig] = useState<any>(null);
-    const [paymentMethod, setPaymentMethod] = useState<"ALIPAY" | "WXPAY">(
-      "ALIPAY",
-    );
+    const [paymentMethod, setPaymentMethod] = useState<"ALIPAY">("ALIPAY");
 
     // ...
 
@@ -208,12 +206,9 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
         // Prioritize Wallet if checked
         if (cashChecked) {
           paymentType = "WALLET";
-        } else if (paymentMethod === "ALIPAY") {
-          // APP端固定使用 ALIPAY_APP,忽略后端配置的 CUSTOMER_SERVICE_LINK
-          paymentType = "ALIPAY_APP";
         } else {
-          // 微信支付也固定使用 WXPAY_APP
-          paymentType = "WXPAY_APP";
+          // 默认只支持支付宝
+          paymentType = "ALIPAY_APP";
         }
 
         const payNum = packFlag ? 1 : num;
@@ -532,27 +527,6 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                         </View>
                       </TouchableOpacity>
                     ) : null}
-
-                    {payConfig?.wxpay?.enabled ? (
-                      <TouchableOpacity
-                        style={styles.payOption}
-                        onPress={() => setPaymentMethod("WXPAY")}
-                      >
-                        <View style={styles.rowLeft}>
-                          <Text style={styles.payLabel}>微信支付</Text>
-                        </View>
-                        <View
-                          style={[
-                            styles.radio,
-                            paymentMethod === "WXPAY" && styles.radioChecked,
-                          ]}
-                        >
-                          {paymentMethod === "WXPAY" ? (
-                            <View style={styles.radioInner} />
-                          ) : null}
-                        </View>
-                      </TouchableOpacity>
-                    ) : null}
                   </View>
                 ) : null}
 

+ 6 - 6
components/mine/MenuCell.tsx

@@ -32,12 +32,12 @@ export function MenuCell({ onItemPress, showWallet = false }: MenuCellProps) {
       title: "全部订单",
       type: "2_0",
     },
-    {
-      icon: Images.mine.exchangeIcon || "",
-      title: "兑换码",
-      tip: "10:00 ~ 18:00",
-      type: "6_1",
-    },
+    // {
+    //   icon: Images.mine.exchangeIcon || "",
+    //   title: "兑换码",
+    //   tip: "10:00 ~ 18:00",
+    //   type: "6_1",
+    // },
     {
       icon: Images.mine.customerService || "",
       title: "联系客服",