Explorar o código

feat: 新增仓库详情页、审核列表页及多处bug修复

zbb hai 1 mes
pai
achega
798b19e6d5

+ 2 - 2
app.json

@@ -2,7 +2,7 @@
   "expo": {
     "name": "艾斯",
     "slug": "asios",
-    "version": "1.0.4",
+    "version": "1.0.5",
     "orientation": "portrait",
     "icon": "./assets/images/icon.png",
     "scheme": ["asios", "alipay2021005175632205"],
@@ -12,7 +12,7 @@
       "supportsTablet": false,
       "bundleIdentifier": "com.asios",
       "appleTeamId": "Y9ZVX3FRX6",
-      "buildNumber": "23",
+      "buildNumber": "25",
       "infoPlist": {
         "CFBundleDisplayName": "艾斯潮盒",
         "ITSAppUsesNonExemptEncryption": false,

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

@@ -37,6 +37,7 @@ export default function MineScreen() {
     data: string;
   } | null>(null);
   const [showWallet, setShowWallet] = useState(false);
+  const [showExchange, setShowExchange] = useState(false);
   const kefuRef = useRef<KefuPopupRef>(null);
 
   const loadData = useCallback(async () => {
@@ -58,6 +59,9 @@ export default function MineScreen() {
 
       const walletConfig = await getParamConfig("wallet_recharge_show");
       setShowWallet(walletConfig?.state === 1);
+
+      const exchangeConfig = await getParamConfig("harry_exchange_show");
+      setShowExchange(exchangeConfig?.state === 1);
     } catch (error) {
       console.error("获取数据失败:", error);
     }
@@ -380,7 +384,7 @@ export default function MineScreen() {
 
         {/* Menu Section */}
         <View style={styles.menuSection}>
-          <MenuCell onItemPress={handleMenuItemPress} showWallet={showWallet} />
+          <MenuCell onItemPress={handleMenuItemPress} showWallet={showWallet} showExchange={showExchange} />
         </View>
 
         {/* Footer Info */}

+ 1 - 0
app/cloud-warehouse/_layout.tsx

@@ -6,6 +6,7 @@ export default function StoreLayout() {
       <Stack.Screen name="index" />
       <Stack.Screen name="checkout" />
       <Stack.Screen name="packages" />
+      <Stack.Screen name="detail" />
     </Stack>
   );
 }

+ 280 - 0
app/cloud-warehouse/detail.tsx

@@ -0,0 +1,280 @@
+import { Image } from "expo-image";
+import { useLocalSearchParams, useRouter } from "expo-router";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import {
+    ActivityIndicator,
+    Alert,
+    ImageBackground,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+import { Images } from "@/constants/images";
+import { convertApply, convertPreview, getLuckDetail } from "@/services/award";
+
+const LEVEL_MAP: Record<string, { title: string; color: string }> = {
+  A: { title: "超神", color: "#FF3B30" },
+  B: { title: "欧皇", color: "#FF9500" },
+  C: { title: "隐藏", color: "#AF52DE" },
+  D: { title: "普通", color: "#8E8E93" },
+};
+
+const FROM_TYPE_MAP: Record<string, string> = {
+  MALL: "商城",
+  DISTRIBUTION: "分销",
+  LUCK: "奖池",
+  LUCK_PICKUP: "商品提货",
+  LUCK_EXCHANGE: "商品兑换",
+  SUBSTITUTE: "商品置换",
+  TRANSFER: "商品转赠",
+  LUCK_ROOM: "福利房",
+  LUCK_WHEEL: "魔天轮",
+  DOLL_MACHINE: "扭蛋",
+  RECHARGE: "充值",
+  OFFICIAL: "官方",
+  ACTIVITY: "活动",
+  LUCK_ACTIVITY: "奖池活动",
+  REDEEM_CODE_ACTIVITY: "兑换码活动",
+};
+
+export default function StoreDetailScreen() {
+  const { id } = useLocalSearchParams<{ id: string }>();
+  const router = useRouter();
+  const insets = useSafeAreaInsets();
+
+  const [loading, setLoading] = useState(true);
+  const [item, setItem] = useState<any>(null);
+  const [converting, setConverting] = useState(false);
+
+  const loadData = useCallback(async () => {
+    if (!id) return;
+    setLoading(true);
+    try {
+      const data = await getLuckDetail(id);
+      setItem(data);
+    } catch (e) {
+      console.error("加载详情失败:", e);
+    }
+    setLoading(false);
+  }, [id]);
+
+  useEffect(() => {
+    loadData();
+  }, [loadData]);
+
+  const handleConvert = async () => {
+    if (!item || item.magicAmount <= 0) {
+      Alert.alert("提示", "不可兑换");
+      return;
+    }
+    setConverting(true);
+    try {
+      const preview = await convertPreview([item.id]);
+      if (preview?.data) {
+        Alert.alert(
+          "确认兑换",
+          `将兑换 ${item.spu?.name} 获得 ${preview.data?.totalMagicAmount || item.magicAmount} 果实`,
+          [
+            { text: "取消", style: "cancel" },
+            {
+              text: "确定",
+              onPress: async () => {
+                const success = await convertApply([item.id]);
+                if (success) {
+                  Alert.alert("提示", "兑换成功", [
+                    { text: "确定", onPress: () => router.back() },
+                  ]);
+                }
+              },
+            },
+          ],
+        );
+      }
+    } catch (e) {
+      Alert.alert("提示", "兑换失败");
+    }
+    setConverting(false);
+  };
+
+  if (loading) {
+    return (
+      <View style={styles.loadingBox}>
+        <ActivityIndicator size="large" color="#fff" />
+      </View>
+    );
+  }
+
+  if (!item) {
+    return (
+      <View style={styles.loadingBox}>
+        <Text style={{ color: "#999", fontSize: 16 }}>商品不存在</Text>
+      </View>
+    );
+  }
+
+  const levelInfo = LEVEL_MAP[item.level] || { title: "其他", color: "#999" };
+
+  return (
+    <View style={styles.container}>
+      <StatusBar barStyle="light-content" />
+      <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()}
+          >
+            <Text style={styles.backText}>←</Text>
+          </TouchableOpacity>
+          <Text style={styles.title}>仓库</Text>
+          <View style={styles.placeholder} />
+        </View>
+
+        {/* 商品信息 */}
+        <View style={styles.content}>
+          <View style={styles.detailCard}>
+            <Image
+              source={{ uri: item.spu?.cover }}
+              style={styles.coverImage}
+              contentFit="contain"
+            />
+            <Text style={[styles.levelTag, { color: levelInfo.color }]}>
+              {levelInfo.title}
+            </Text>
+            <Text style={styles.goodsName}>{item.spu?.name}</Text>
+            <View style={styles.sourceBox}>
+              <Text style={styles.sourceText}>
+                从{FROM_TYPE_MAP[item.fromRelationType] || "其他"}获得
+              </Text>
+            </View>
+          </View>
+        </View>
+
+        {/* 兑换按钮 */}
+        {item.magicAmount > 0 && (
+          <View
+            style={[
+              styles.bottomBar,
+              { paddingBottom: Math.max(insets.bottom, 20) },
+            ]}
+          >
+            <TouchableOpacity
+              style={styles.convertBtn}
+              onPress={handleConvert}
+              disabled={converting}
+              activeOpacity={0.8}
+            >
+              <ImageBackground
+                source={{ uri: Images.common.loginBtn }}
+                style={styles.convertBtnBg}
+                resizeMode="stretch"
+              >
+                <Text style={styles.convertBtnText}>兑换果实</Text>
+                <Text style={styles.convertBtnSub}>
+                  可兑换 {item.magicAmount} 果实
+                </Text>
+              </ImageBackground>
+            </TouchableOpacity>
+          </View>
+        )}
+      </ImageBackground>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: { flex: 1, backgroundColor: "#1a1a2e" },
+  background: { flex: 1 },
+  loadingBox: {
+    flex: 1,
+    backgroundColor: "#1a1a2e",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  header: {
+    flexDirection: "row",
+    alignItems: "center",
+    justifyContent: "space-between",
+    paddingHorizontal: 15,
+    paddingBottom: 10,
+  },
+  backBtn: {
+    width: 40,
+    height: 40,
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  backText: { color: "#fff", fontSize: 20 },
+  title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
+  placeholder: { width: 40 },
+
+  content: { flex: 1, paddingHorizontal: 20, paddingTop: 20 },
+  detailCard: {
+    backgroundColor: "rgba(255,255,255,0.95)",
+    borderRadius: 16,
+    padding: 24,
+    alignItems: "center",
+  },
+  coverImage: {
+    width: 180,
+    height: 180,
+    borderRadius: 10,
+    marginBottom: 16,
+  },
+  levelTag: {
+    fontSize: 16,
+    fontWeight: "bold",
+    marginBottom: 8,
+    textShadowColor: "#000",
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 0,
+  },
+  goodsName: {
+    fontSize: 16,
+    color: "#333",
+    fontWeight: "bold",
+    textAlign: "center",
+    marginBottom: 12,
+    lineHeight: 22,
+  },
+  sourceBox: {
+    backgroundColor: "rgba(0,0,0,0.05)",
+    borderRadius: 20,
+    paddingHorizontal: 16,
+    paddingVertical: 6,
+  },
+  sourceText: { fontSize: 13, color: "#666" },
+
+  bottomBar: {
+    paddingHorizontal: 30,
+    paddingTop: 10,
+    alignItems: "center",
+  },
+  convertBtn: {
+    width: "80%",
+    height: 60,
+  },
+  convertBtnBg: {
+    width: "100%",
+    height: "100%",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  convertBtnText: {
+    color: "#000",
+    fontSize: 16,
+    fontWeight: "bold",
+  },
+  convertBtnSub: {
+    color: "#735200",
+    fontSize: 11,
+  },
+});

+ 6 - 2
app/cloud-warehouse/index.tsx

@@ -331,7 +331,11 @@ export default function StoreScreen() {
               />
             </TouchableOpacity>
           </TouchableOpacity>
-          <View style={styles.cellBody}>
+          <TouchableOpacity
+            style={styles.cellBody}
+            onPress={() => router.push({ pathname: '/cloud-warehouse/detail', params: { id: item.id } } as any)}
+            activeOpacity={0.7}
+          >
             <ImageBackground
               source={{ uri: Images.mine.storeGoodsImgBg }}
               style={styles.goodsImgBg}
@@ -351,7 +355,7 @@ export default function StoreScreen() {
               </Text>
             </View>
             <Text style={styles.arrow}>{">"}</Text>
-          </View>
+          </TouchableOpacity>
         </View>
       </ImageBackground>
     );

+ 1 - 0
app/dimension/_layout.tsx

@@ -11,6 +11,7 @@ export default function WealLayout() {
       <Stack.Screen name="store_choose" />
       <Stack.Screen name="catchDoll" />
       <Stack.Screen name="wish" />
+      <Stack.Screen name="audit_list" />
     </Stack>
   );
 }

+ 342 - 0
app/dimension/audit_list.tsx

@@ -0,0 +1,342 @@
+import { Image } from "expo-image";
+import { useLocalSearchParams, useRouter } from "expo-router";
+import React, { useCallback, useEffect, useState } from "react";
+import {
+    ActivityIndicator,
+    Alert,
+    FlatList,
+    ImageBackground,
+    RefreshControl,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+import { Images } from "@/constants/images";
+import {
+    roomAuditPass,
+    roomAuditRecord,
+    roomAuditUnpass,
+} from "@/services/dimension";
+
+interface AuditItem {
+  id: string;
+  avatar: string;
+  nickname: string;
+  auditStatus: number; // 0-待审核 1-拒绝 2-通过
+}
+
+const TABS = [
+  { title: "待审核", value: 0 },
+  { title: "审核通过", value: 2 },
+  { title: "审核不通过", value: 1 },
+];
+
+export default function AuditListScreen() {
+  const { id: roomId } = useLocalSearchParams<{ id: string }>();
+  const router = useRouter();
+  const insets = useSafeAreaInsets();
+
+  const [activeTab, setActiveTab] = useState(0);
+  const [list, setList] = useState<AuditItem[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [refreshing, setRefreshing] = useState(false);
+  const [page, setPage] = useState(1);
+  const [hasMore, setHasMore] = useState(true);
+
+  const loadData = useCallback(
+    async (pageNum: number, isRefresh = false) => {
+      if (loading && !isRefresh) return;
+      try {
+        if (pageNum === 1) setLoading(true);
+        const data = await roomAuditRecord(
+          pageNum,
+          20,
+          roomId as string,
+          TABS[activeTab].value,
+        );
+        const records = Array.isArray(data)
+          ? data
+          : data?.records || [];
+        if (records.length < 20) setHasMore(false);
+        if (pageNum === 1 || isRefresh) {
+          setList(records);
+        } else {
+          setList((prev) => [...prev, ...records]);
+        }
+      } catch (e) {
+        console.error("加载审核记录失败:", e);
+      } finally {
+        setLoading(false);
+        setRefreshing(false);
+      }
+    },
+    [roomId, activeTab, loading],
+  );
+
+  useEffect(() => {
+    setPage(1);
+    setList([]);
+    setHasMore(true);
+    loadData(1, true);
+  }, [activeTab]);
+
+  const handleRefresh = () => {
+    setRefreshing(true);
+    setPage(1);
+    setHasMore(true);
+    loadData(1, true);
+  };
+
+  const handleLoadMore = () => {
+    if (!loading && hasMore) {
+      const np = page + 1;
+      setPage(np);
+      loadData(np);
+    }
+  };
+
+  const handlePass = async (item: AuditItem, index: number) => {
+    try {
+      const res = await roomAuditPass(item.id);
+      if (res) {
+        const newList = [...list];
+        newList[index] = { ...item, auditStatus: 2 };
+        setList(newList);
+      }
+    } catch (e) {
+      Alert.alert("提示", "操作失败");
+    }
+  };
+
+  const handleReject = async (item: AuditItem, index: number) => {
+    try {
+      const res = await roomAuditUnpass(item.id);
+      if (res) {
+        const newList = [...list];
+        newList[index] = { ...item, auditStatus: 1 };
+        setList(newList);
+      }
+    } catch (e) {
+      Alert.alert("提示", "操作失败");
+    }
+  };
+
+  const renderItem = ({
+    item,
+    index,
+  }: {
+    item: AuditItem;
+    index: number;
+  }) => (
+    <View style={styles.cell}>
+      <Image
+        source={{ uri: item.avatar }}
+        style={styles.avatar}
+        contentFit="cover"
+      />
+      <Text style={styles.nickname} numberOfLines={1}>
+        {item.nickname}
+      </Text>
+      <View style={styles.actionArea}>
+        {item.auditStatus === 0 ? (
+          <>
+            <TouchableOpacity
+              style={[styles.btn, styles.rejectBtn]}
+              onPress={() => handleReject(item, index)}
+            >
+              <Text style={styles.btnText}>拒绝</Text>
+            </TouchableOpacity>
+            <TouchableOpacity
+              style={[styles.btn, styles.passBtn]}
+              onPress={() => handlePass(item, index)}
+            >
+              <Text style={styles.btnText}>通过</Text>
+            </TouchableOpacity>
+          </>
+        ) : (
+          <Text style={styles.statusText}>
+            {item.auditStatus === 2 ? "通过" : "拒绝"}
+          </Text>
+        )}
+      </View>
+    </View>
+  );
+
+  return (
+    <View style={styles.container}>
+      <StatusBar barStyle="light-content" />
+      <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()}
+          >
+            <Text style={styles.backText}>←</Text>
+          </TouchableOpacity>
+          <Text style={styles.title}>审核</Text>
+          <View style={styles.placeholder} />
+        </View>
+
+        {/* Tab 切换 */}
+        <View style={styles.tabs}>
+          {TABS.map((tab, index) => {
+            const isActive = activeTab === index;
+            return (
+              <TouchableOpacity
+                key={index}
+                style={[styles.tabItem, isActive && styles.tabItemActive]}
+                onPress={() => setActiveTab(index)}
+              >
+                <Text
+                  style={[
+                    styles.tabText,
+                    isActive && styles.tabTextActive,
+                  ]}
+                >
+                  {tab.title}
+                </Text>
+                {isActive && <View style={styles.tabLine} />}
+              </TouchableOpacity>
+            );
+          })}
+        </View>
+
+        {/* 列表 */}
+        <FlatList
+          data={list}
+          renderItem={renderItem}
+          keyExtractor={(item, index) => item.id || index.toString()}
+          contentContainerStyle={styles.listContent}
+          refreshControl={
+            <RefreshControl
+              refreshing={refreshing}
+              onRefresh={handleRefresh}
+              tintColor="#fff"
+            />
+          }
+          onEndReached={handleLoadMore}
+          onEndReachedThreshold={0.3}
+          ListFooterComponent={
+            loading && list.length > 0 ? (
+              <ActivityIndicator
+                color="#fff"
+                style={{ marginVertical: 10 }}
+              />
+            ) : null
+          }
+          ListEmptyComponent={
+            !loading ? (
+              <View style={styles.emptyBox}>
+                <Text style={styles.emptyText}>暂无记录</Text>
+              </View>
+            ) : null
+          }
+        />
+      </ImageBackground>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: { flex: 1, backgroundColor: "#1a1a2e" },
+  background: { flex: 1 },
+  header: {
+    flexDirection: "row",
+    alignItems: "center",
+    justifyContent: "space-between",
+    paddingHorizontal: 15,
+    paddingBottom: 10,
+  },
+  backBtn: {
+    width: 40,
+    height: 40,
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  backText: { color: "#fff", fontSize: 20 },
+  title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
+  placeholder: { width: 40 },
+
+  // Tabs
+  tabs: {
+    flexDirection: "row",
+    paddingHorizontal: 10,
+    borderBottomWidth: 1,
+    borderBottomColor: "rgba(255,255,255,0.15)",
+  },
+  tabItem: {
+    flex: 1,
+    height: 44,
+    justifyContent: "center",
+    alignItems: "center",
+    position: "relative",
+  },
+  tabItemActive: {},
+  tabText: { color: "#999", fontSize: 14 },
+  tabTextActive: { color: "#fff", fontWeight: "bold" },
+  tabLine: {
+    position: "absolute",
+    bottom: 0,
+    width: "60%",
+    height: 2,
+    backgroundColor: "#e79018",
+  },
+
+  // List
+  listContent: { padding: 15, paddingBottom: 100 },
+  cell: {
+    flexDirection: "row",
+    alignItems: "center",
+    backgroundColor: "rgba(255,255,255,0.9)",
+    borderRadius: 8,
+    paddingHorizontal: 15,
+    paddingVertical: 12,
+    marginBottom: 10,
+  },
+  avatar: {
+    width: 40,
+    height: 40,
+    borderRadius: 4,
+    borderWidth: 2,
+    borderColor: "#000",
+    marginRight: 12,
+  },
+  nickname: {
+    flex: 1,
+    fontSize: 14,
+    color: "#333",
+    fontWeight: "500",
+  },
+  actionArea: {
+    flexDirection: "row",
+    alignItems: "center",
+  },
+  btn: {
+    width: 68,
+    height: 32,
+    borderRadius: 4,
+    justifyContent: "center",
+    alignItems: "center",
+    marginLeft: 8,
+  },
+  rejectBtn: { backgroundColor: "#ff4d4f" },
+  passBtn: { backgroundColor: "#52c41a" },
+  btnText: { color: "#fff", fontSize: 13, fontWeight: "bold" },
+  statusText: {
+    fontSize: 13,
+    color: "#666",
+    fontWeight: "500",
+  },
+
+  emptyBox: { alignItems: "center", paddingTop: 80 },
+  emptyText: { color: "#999", fontSize: 14 },
+});

+ 5 - 1
app/dimension/create_list.tsx

@@ -103,7 +103,11 @@ const CreateListScreen = () => {
                         <TouchableOpacity
                             style={styles.auditBtn}
                             onPress={() => {
-                                // router.push({ pathname: '/dimension/audit_list', params: { id: item.id } })
+                                if (item.status === 1 && item.participateMode === 1) {
+                                    router.push({ pathname: '/dimension/audit_list', params: { id: item.id } } as any);
+                                } else {
+                                    router.push({ pathname: '/dimension/detail', params: { id: item.id } } as any);
+                                }
                             }}
                         >
                             <Text style={styles.auditBtnText}>

+ 1 - 1
app/dimension/room.tsx

@@ -170,7 +170,7 @@ export default function RoomScreen() {
               />
             </ImageBackground>
             <View style={styles.funcBtns}>
-              <TouchableOpacity style={styles.funcItem} onPress={() => router.push('/dimension/create' as any)}>
+              <TouchableOpacity style={styles.funcItem} onPress={() => router.push('/dimension/create_list' as any)}>
                 <Image source={{ uri: Images.welfare.roomIcon0 }} style={styles.funcIcon} contentFit="contain" />
                 <Text style={styles.funcText}>创建</Text>
               </TouchableOpacity>

+ 1 - 1
app/feedback/index.tsx

@@ -98,7 +98,7 @@ export default function FeedbackScreen() {
             {/* 投诉提示 */}
             <View style={styles.complaintTip}>
               <Text style={styles.complaintTipText}>
-                此投诉为本小程序自有投诉渠道,非微信官方投诉渠道
+                此投诉为本平台自有投诉渠道
               </Text>
             </View>
 

+ 6 - 11
components/RegionPicker.tsx

@@ -51,19 +51,14 @@ export const RegionPicker: React.FC<RegionPickerProps> = ({
   const loadProvinces = async () => {
     setLoading(true);
     try {
-      // Assuming pid=0 or 1 for top level. service says pid=1 is default
-      const list = await getArea(0); // Try 0 first, typically 0 is root
-      if (list && list.length > 0) {
-          setProvinces(list);
-      } else {
-        // Fallback or retry with 1 if 0 returns empty/null?
-        // Service code: pid ? { pid } : { pid: 1 }. So if I pass 0, it passes {pid:0}.
-        // Let's assume typical tree structure. If fail, maybe try different PID or log error.
-        // Actually, let's try 0.
-         setProvinces(list || []);
+      // 省级节点的 pid 通常为 1(中国节点),先尝试 1,空则回退 0
+      let list = await getArea(1);
+      if (!list || list.length === 0) {
+        list = await getArea(0);
       }
+      setProvinces(list || []);
     } catch (e) {
-      console.error(e);
+      console.error('加载省份数据失败:', e);
     }
     setLoading(false);
   };

+ 12 - 7
components/mine/MenuCell.tsx

@@ -14,9 +14,10 @@ interface MenuItem {
 interface MenuCellProps {
   onItemPress: (type: string) => void;
   showWallet?: boolean;
+  showExchange?: boolean;
 }
 
-export function MenuCell({ onItemPress, showWallet = false }: MenuCellProps) {
+export function MenuCell({ onItemPress, showWallet = false, showExchange = false }: MenuCellProps) {
   const menuList: MenuItem[] = [
     ...(showWallet
       ? [
@@ -32,12 +33,16 @@ export function MenuCell({ onItemPress, showWallet = false }: MenuCellProps) {
       title: "全部订单",
       type: "2_0",
     },
-    // {
-    //   icon: Images.mine.exchangeIcon || "",
-    //   title: "兑换码",
-    //   tip: "10:00 ~ 18:00",
-    //   type: "6_1",
-    // },
+    ...(showExchange
+      ? [
+          {
+            icon: Images.mine.exchangeIcon || "",
+            title: "兑换码",
+            tip: "10:00 ~ 18:00",
+            type: "6_1",
+          },
+        ]
+      : []),
     {
       icon: Images.mine.customerService || "",
       title: "联系客服",

+ 1 - 1
services/award.ts

@@ -50,7 +50,7 @@ const apis = {
   FEEDBACK_SUBMIT: '/api/app/feedback/submit',
   MY_BOXES: '/api/luck/treasure-box/my-boxes',
   OPEN_BOX: '/api/luck/treasure-box/open',
-  HARRY_EXCHANGE: '/api/redeemCode/exchange',
+  HARRY_EXCHANGE: '/api/redemptionCode/redeem',
   WIN_RECORDS: '/api/luck/treasure-box/win-records',
   COUNT_RECORDS: '/api/luckPool/countRecordsAfterLastLevel',
 };

+ 31 - 9
services/base.ts

@@ -1,5 +1,6 @@
 // 基础服务 - 对应 supermart-mini/service/base.js
-import { get, post } from "./http";
+import { Platform } from "react-native";
+import { get, getToken, post } from "./http";
 
 const apis = {
   PAGE_CONFIG: "/api/app/page/getByPageId",
@@ -83,14 +84,20 @@ export const uploadFile = async (
   folder = "avatar",
 ): Promise<string | null> => {
   try {
+    // iOS ImagePicker 可能返回带空格的路径,需要处理
+    let processedUri = fileUri;
+    if (Platform.OS === "ios" && !processedUri.startsWith("file://")) {
+      processedUri = `file://${processedUri}`;
+    }
+
     const formData = new FormData();
 
     // 获取文件名
-    const fileName = fileUri.split("/").pop() || "image.jpg";
+    const fileName = processedUri.split("/").pop() || `avatar_${Date.now()}.jpg`;
 
     // 创建文件对象 (React Native 格式)
     const fileObj = {
-      uri: fileUri,
+      uri: processedUri,
       type: "image/jpeg",
       name: fileName,
     } as any;
@@ -99,41 +106,56 @@ export const uploadFile = async (
     formData.append("appId", "supermart-acetoys");
     formData.append("folder", folder);
 
-    // 使用http.ts中的getToken获取token
-    const { getToken } = require("./http");
+    // 获取 token
     const token = getToken();
+    console.log("[uploadFile] token:", token ? "有" : "无", "uri:", processedUri.substring(0, 50));
+
+    // 构建 track header(与 Vue 原项目 http.js 一致)
+    const track = JSON.stringify({
+      os: Platform.OS === "ios" ? 5 : 6,
+      version: "1.0.5",
+      deviceCode: "",
+      bundleId: "com.wonderful.mart",
+      channel: "wonderful",
+    });
 
     const response = await fetch("https://mm.acefig.com/api/oss/file/upload", {
       method: "POST",
       headers: {
         // 不设置 Content-Type,让 fetch 自动处理 multipart/form-data 边界
         Authentication: token || "",
+        track: track,
       },
       body: formData,
     });
 
+    console.log("[uploadFile] 响应状态:", response.status);
+
     // 检查响应状态
     if (!response.ok) {
       const errorText = await response.text();
-      console.error("上传响应状态:", response.status, "响应内容:", errorText);
+      console.error("[uploadFile] 上传失败 状态:", response.status, "内容:", errorText);
       return null;
     }
 
     const text = await response.text();
+    console.log("[uploadFile] 响应内容:", text?.substring(0, 200));
+
     if (!text) {
-      console.error("上传响应为空");
+      console.error("[uploadFile] 上传响应为空");
       return null;
     }
 
     const result = JSON.parse(text);
     // code 可能是数字或字符串
     if ((result.code === 0 || result.code === "0") && result.data?.url) {
+      console.log("[uploadFile] 成功:", result.data.url);
       return result.data.url;
     }
-    console.error("上传返回错误:", result);
+    console.error("[uploadFile] 上传返回错误:", JSON.stringify(result));
     return null;
   } catch (error) {
-    console.error("上传文件失败:", error);
+    console.error("[uploadFile] 上传文件异常:", error);
     return null;
   }
 };

+ 32 - 1
services/dimension.ts

@@ -1,4 +1,4 @@
-import { get, getL, post } from "./http";
+import { get, getL, post, postL } from "./http";
 
 const apis = {
   LIST: "/api/luckRoom",
@@ -109,6 +109,34 @@ export const createRoom = async (params: {
   }
 };
 
+// 获取审核记录列表
+export const roomAuditRecord = async (
+  current: number,
+  size: number,
+  roomId: string,
+  auditStatus: number,
+) => {
+  const res = await post(apis.ROOM_AUDIT_RECORD, {
+    current,
+    size,
+    roomId,
+    auditStatus,
+  });
+  return res.data;
+};
+
+// 审核通过
+export const roomAuditPass = async (id: string) => {
+  const res = await postL(apis.ROOM_AUDIT_PASS, { id });
+  return res.success;
+};
+
+// 审核拒绝
+export const roomAuditUnpass = async (id: string) => {
+  const res = await postL(apis.ROOM_AUDIT_UNPASS, { id });
+  return res.success;
+};
+
 export default {
   getWealList,
   getWealDetail,
@@ -120,6 +148,9 @@ export default {
   getDateTimeScope,
   getMyCreateList,
   createRoom,
+  roomAuditRecord,
+  roomAuditPass,
+  roomAuditUnpass,
 
   // 扭蛋机相关 API
   catchDollDetail: async () => {