Просмотр исходного кода

fix: 新人优惠券弹窗触发时机 + 新增新人大礼包海报弹窗

- box tab 新人弹窗改用 useFocusEffect 触发,对齐 Vue onShow(之前仅在 typeIndex/priceSort 变化时检测,token 首次未就绪导致从不显示)
- 新增 NewbieActivityModal 展示新人大礼包海报,AsyncStorage NEW_PEOPLE 标记一台设备只弹一次
zbb 2 дней назад
Родитель
Сommit
3eb0842693
2 измененных файлов с 111 добавлено и 5 удалено
  1. 27 5
      app/(tabs)/box.tsx
  2. 84 0
      components/NewbieActivityModal.tsx

+ 27 - 5
app/(tabs)/box.tsx

@@ -1,12 +1,14 @@
 import { Barrage } from "@/components/Barrage";
 import { CouponModal } from "@/components/CouponModal";
+import { NewbieActivityModal } from "@/components/NewbieActivityModal";
 import { Colors } from "@/constants/Colors";
 import { Images } from "@/constants/images";
 import { CouponItem, getNewbieGiftBagInfo, listValidCoupon } from "@/services/activity";
 import { getFeedbackList, getPoolList, PoolItem } from "@/services/award";
 import { getToken } from "@/services/http";
+import AsyncStorage from "@react-native-async-storage/async-storage";
 import { Image } from "expo-image";
-import { useRouter } from "expo-router";
+import { useFocusEffect, useRouter } from "expo-router";
 import React, { useCallback, useEffect, useMemo, useState } from "react";
 import {
     ActivityIndicator,
@@ -147,6 +149,8 @@ export default function BoxScreen() {
   const [couponVisible, setCouponVisible] = useState(false);
   const [couponList, setCouponList] = useState<CouponItem[]>([]);
   const [newPeopleChecked, setNewPeopleChecked] = useState(false);
+  // 新人大礼包海报弹窗
+  const [activityVisible, setActivityVisible] = useState(false);
 
   // 加载弹幕
   const loadBarrage = useCallback(async () => {
@@ -160,16 +164,22 @@ export default function BoxScreen() {
     }
   }, []);
 
-  // 新人大礼包检测(Vue: initNewPeople)
+  // 新人大礼包检测(Vue: initNewPeople)—— 一个设备只弹一次
   const initNewPeople = useCallback(async () => {
     if (newPeopleChecked) return;
     const token = getToken();
     if (!token) return; // 未登录不检测
     try {
+      const flag = await AsyncStorage.getItem("NEW_PEOPLE");
+      if (flag) {
+        setNewPeopleChecked(true);
+        return;
+      }
       const data = await getNewbieGiftBagInfo();
       if (data) {
         setNewPeopleChecked(true);
-        // Vue 原项目只是展示一张海报图片,这里跳过(无具体交互)
+        setActivityVisible(true);
+        await AsyncStorage.setItem("NEW_PEOPLE", "1");
       }
     } catch (e) {
       // 静默失败
@@ -228,10 +238,16 @@ export default function BoxScreen() {
   useEffect(() => {
     loadData(true);
     loadBarrage();
-    initNewPeople();
-    initCoupon();
   }, [typeIndex, priceSort]);
 
+  // 对齐 Vue onShow:每次页面获得焦点检查新人弹窗/优惠券
+  useFocusEffect(
+    useCallback(() => {
+      initNewPeople();
+      initCoupon();
+    }, [initNewPeople, initCoupon]),
+  );
+
   // 执行搜索
   const handleSearch = () => {
     setList([]);
@@ -417,6 +433,12 @@ export default function BoxScreen() {
             loadData(true);
           }}
         />
+
+        {/* 新人大礼包海报弹窗 */}
+        <NewbieActivityModal
+          visible={activityVisible}
+          onClose={() => setActivityVisible(false)}
+        />
       </View>
     </View>
   );

+ 84 - 0
components/NewbieActivityModal.tsx

@@ -0,0 +1,84 @@
+import { Image } from "expo-image";
+import React from "react";
+import {
+  Modal,
+  Pressable,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from "react-native";
+
+const POSTER_URI =
+  "https://cdn.acetoys.cn/kai_xin_ma_te/resource/magic/award/activity_new.webp";
+
+interface Props {
+  visible: boolean;
+  onClose: () => void;
+}
+
+export function NewbieActivityModal({ visible, onClose }: Props) {
+  return (
+    <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
+      <Pressable style={styles.overlay} onPress={onClose}>
+        <View style={styles.wrapper}>
+          <Image
+            source={{ uri: POSTER_URI }}
+            style={styles.poster}
+            contentFit="contain"
+          />
+          <TouchableOpacity
+            style={styles.closeBtn}
+            onPress={onClose}
+            activeOpacity={0.8}
+            hitSlop={16}
+          >
+            <View style={styles.closeCircle}>
+              <View style={[styles.closeLine, styles.closeLineA]} />
+              <View style={[styles.closeLine, styles.closeLineB]} />
+            </View>
+          </TouchableOpacity>
+        </View>
+      </Pressable>
+    </Modal>
+  );
+}
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    backgroundColor: "rgba(0,0,0,0.6)",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  wrapper: {
+    alignItems: "center",
+  },
+  poster: {
+    width: 275,
+    height: 300,
+  },
+  closeBtn: {
+    marginTop: 16,
+  },
+  closeCircle: {
+    width: 30,
+    height: 30,
+    borderRadius: 15,
+    borderWidth: 1.5,
+    borderColor: "#fff",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  closeLine: {
+    position: "absolute",
+    width: 14,
+    height: 1.5,
+    backgroundColor: "#fff",
+  },
+  closeLineA: {
+    transform: [{ rotate: "45deg" }],
+  },
+  closeLineB: {
+    transform: [{ rotate: "-45deg" }],
+  },
+});