فهرست منبع

feat: 新人优惠券弹窗+新人活动检测 build 33

zbb 1 ماه پیش
والد
کامیت
d599195560
4فایلهای تغییر یافته به همراه282 افزوده شده و 1 حذف شده
  1. 1 1
      app.json
  2. 51 0
      app/(tabs)/box.tsx
  3. 196 0
      components/CouponModal.tsx
  4. 34 0
      services/activity.ts

+ 1 - 1
app.json

@@ -12,7 +12,7 @@
       "supportsTablet": false,
       "bundleIdentifier": "com.asios",
       "appleTeamId": "Y9ZVX3FRX6",
-      "buildNumber": "32",
+      "buildNumber": "33",
       "infoPlist": {
         "CFBundleDisplayName": "艾斯潮盒",
         "ITSAppUsesNonExemptEncryption": false,

+ 51 - 0
app/(tabs)/box.tsx

@@ -1,7 +1,10 @@
 import { Barrage } from "@/components/Barrage";
+import { CouponModal } from "@/components/CouponModal";
 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 { Image } from "expo-image";
 import { useRouter } from "expo-router";
 import React, { useCallback, useEffect, useMemo, useState } from "react";
@@ -140,6 +143,10 @@ export default function BoxScreen() {
   const [current, setCurrent] = useState(1);
   const [hasMore, setHasMore] = useState(true);
   const [barrageList, setBarrageList] = useState<BarrageItem[]>([]);
+  // 新人优惠券弹窗
+  const [couponVisible, setCouponVisible] = useState(false);
+  const [couponList, setCouponList] = useState<CouponItem[]>([]);
+  const [newPeopleChecked, setNewPeopleChecked] = useState(false);
 
   // 加载弹幕
   const loadBarrage = useCallback(async () => {
@@ -153,6 +160,37 @@ export default function BoxScreen() {
     }
   }, []);
 
+  // 新人大礼包检测(Vue: initNewPeople)
+  const initNewPeople = useCallback(async () => {
+    if (newPeopleChecked) return;
+    const token = getToken();
+    if (!token) return; // 未登录不检测
+    try {
+      const data = await getNewbieGiftBagInfo();
+      if (data) {
+        setNewPeopleChecked(true);
+        // Vue 原项目只是展示一张海报图片,这里跳过(无具体交互)
+      }
+    } catch (e) {
+      // 静默失败
+    }
+  }, [newPeopleChecked]);
+
+  // 优惠券检测(Vue: initCoupon)
+  const initCoupon = useCallback(async () => {
+    const token = getToken();
+    if (!token) return;
+    try {
+      const coupons = await listValidCoupon("LUCK");
+      if (coupons && coupons.length > 0) {
+        setCouponList(coupons);
+        setCouponVisible(true);
+      }
+    } catch (e) {
+      // 静默失败
+    }
+  }, []);
+
   const loadData = useCallback(
     async (isRefresh = false) => {
       if (loading) return;
@@ -190,6 +228,8 @@ export default function BoxScreen() {
   useEffect(() => {
     loadData(true);
     loadBarrage();
+    initNewPeople();
+    initCoupon();
   }, [typeIndex, priceSort]);
 
   // 执行搜索
@@ -366,6 +406,17 @@ export default function BoxScreen() {
           onEndReached={handleLoadMore}
           onEndReachedThreshold={0.3}
         />
+
+        {/* 新人优惠券弹窗 */}
+        <CouponModal
+          visible={couponVisible}
+          coupons={couponList}
+          onClose={() => setCouponVisible(false)}
+          onSuccess={() => {
+            setCouponList([]);
+            loadData(true);
+          }}
+        />
       </View>
     </View>
   );

+ 196 - 0
components/CouponModal.tsx

@@ -0,0 +1,196 @@
+import React from "react";
+import {
+  Alert,
+  Modal,
+  ScrollView,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  View,
+} from "react-native";
+
+import { Colors } from "@/constants/Colors";
+import { batchReceiveCoupon, CouponItem } from "@/services/activity";
+
+interface Props {
+  visible: boolean;
+  coupons: CouponItem[];
+  onClose: () => void;
+  onSuccess: () => void;
+}
+
+export function CouponModal({ visible, coupons, onClose, onSuccess }: Props) {
+  const handleReceive = async () => {
+    try {
+      const ids = coupons.map((c) => c.id);
+      const ok = await batchReceiveCoupon(ids);
+      if (ok) {
+        Alert.alert("领取成功", "优惠券已发放到您的账户");
+        onSuccess();
+      }
+      onClose();
+    } catch (e) {
+      console.error("领取优惠券失败:", e);
+      onClose();
+    }
+  };
+
+  const formatTime = (val: string) => (val ? val.slice(0, 10) : "");
+
+  if (!coupons || coupons.length === 0) return null;
+
+  return (
+    <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
+      <View style={styles.overlay}>
+        <View style={styles.dialog}>
+          {/* 标题 */}
+          <View style={styles.titleRow}>
+            <Text style={styles.title}>新人大礼包</Text>
+            <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
+              <Text style={styles.closeBtnText}>✕</Text>
+            </TouchableOpacity>
+          </View>
+
+          {/* 优惠券列表 */}
+          <ScrollView style={styles.scrollArea} showsVerticalScrollIndicator={false}>
+            {coupons.map((item, index) => (
+              <View key={item.id || index} style={styles.couponCard}>
+                <View style={styles.couponLeft}>
+                  <Text style={styles.couponLabel}>福利优惠劵</Text>
+                </View>
+                <View style={styles.couponCenter}>
+                  <Text style={styles.couponAmount}>
+                    ¥ <Text style={styles.couponAmountNum}>{item.amount}</Text>
+                  </Text>
+                </View>
+                <View style={styles.couponRight}>
+                  <Text style={styles.couponCondition}>
+                    {item.fullAmount > 0 ? `满${item.fullAmount}可用` : "无门槛"}
+                  </Text>
+                  <Text style={styles.couponExpiry}>
+                    {formatTime(item.endTime)}过期
+                  </Text>
+                </View>
+              </View>
+            ))}
+          </ScrollView>
+
+          {/* 一键领取按钮 */}
+          <TouchableOpacity style={styles.receiveBtn} onPress={handleReceive} activeOpacity={0.8}>
+            <Text style={styles.receiveBtnText}>一键领取</Text>
+          </TouchableOpacity>
+        </View>
+      </View>
+    </Modal>
+  );
+}
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    backgroundColor: "rgba(0,0,0,0.6)",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  dialog: {
+    width: "85%",
+    maxHeight: "70%",
+    backgroundColor: Colors.darkCard,
+    borderRadius: 16,
+    borderWidth: 1,
+    borderColor: "rgba(0, 243, 255, 0.4)",
+    overflow: "hidden",
+  },
+  titleRow: {
+    flexDirection: "row",
+    justifyContent: "center",
+    alignItems: "center",
+    paddingVertical: 16,
+    paddingHorizontal: 16,
+    position: "relative",
+  },
+  title: {
+    fontSize: 22,
+    fontWeight: "bold",
+    color: "#fff",
+    textShadowColor: Colors.neonBlue,
+    textShadowRadius: 8,
+  },
+  closeBtn: {
+    position: "absolute",
+    right: 16,
+    top: 12,
+    width: 28,
+    height: 28,
+    borderRadius: 14,
+    backgroundColor: "rgba(255,255,255,0.1)",
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  closeBtnText: {
+    color: "#fff",
+    fontSize: 14,
+  },
+  scrollArea: {
+    maxHeight: 250,
+    paddingHorizontal: 16,
+  },
+  couponCard: {
+    flexDirection: "row",
+    alignItems: "center",
+    backgroundColor: "rgba(255,255,255,0.05)",
+    borderRadius: 10,
+    marginBottom: 10,
+    padding: 14,
+    borderWidth: 1,
+    borderColor: "rgba(0, 243, 255, 0.15)",
+  },
+  couponLeft: {
+    marginRight: 12,
+  },
+  couponLabel: {
+    color: Colors.neonBlue,
+    fontSize: 11,
+    fontWeight: "bold",
+    writingDirection: "ltr",
+  },
+  couponCenter: {
+    marginRight: 12,
+  },
+  couponAmount: {
+    color: "#fff",
+    fontSize: 14,
+    fontWeight: "bold",
+  },
+  couponAmountNum: {
+    fontSize: 32,
+    fontWeight: "800",
+    color: "#fff",
+  },
+  couponRight: {
+    flex: 1,
+  },
+  couponCondition: {
+    color: "#fff",
+    fontSize: 13,
+    fontWeight: "500",
+  },
+  couponExpiry: {
+    color: Colors.textTertiary,
+    fontSize: 11,
+    marginTop: 3,
+  },
+  receiveBtn: {
+    marginHorizontal: 16,
+    marginVertical: 16,
+    backgroundColor: Colors.neonBlue,
+    borderRadius: 24,
+    paddingVertical: 14,
+    alignItems: "center",
+  },
+  receiveBtnText: {
+    color: "#000",
+    fontSize: 16,
+    fontWeight: "bold",
+  },
+});

+ 34 - 0
services/activity.ts

@@ -0,0 +1,34 @@
+// 活动服务 - 新人大礼包、优惠券等
+import { get, post, postL } from './http';
+
+const apis = {
+  NEWBIE_GIFT_BAG: '/api/activity/newbieGiftBag/info',
+  COUPON_LIST_VALID: '/api/coupon/listValidCoupon',
+  COUPON_BATCH_RECEIVE: '/api/coupon/batchReceive',
+};
+
+// 新人大礼包信息
+export const getNewbieGiftBagInfo = async () => {
+  const res = await get<any>(apis.NEWBIE_GIFT_BAG);
+  return res.success && res.data ? res.data : null;
+};
+
+// 获取可领优惠券列表
+export interface CouponItem {
+  id: string;
+  amount: number;
+  fullAmount: number;
+  endTime: string;
+  name?: string;
+}
+
+export const listValidCoupon = async (scene: string, targetObj?: any) => {
+  const res = await post<CouponItem[]>(apis.COUPON_LIST_VALID, { scene, targetObj });
+  return res.success && res.data ? res.data : [];
+};
+
+// 一键领取优惠券
+export const batchReceiveCoupon = async (ids: string[]) => {
+  const res = await postL<any>(apis.COUPON_BATCH_RECEIVE, { ids });
+  return res.success;
+};