| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- import { Images } from "@/constants/images";
- import Service from "@/services/dimension";
- import EventUtils from "@/utils/event";
- import { useRouter } from "expo-router";
- import React, { useCallback, useEffect, useRef, useState } from "react";
- import {
- Alert,
- Dimensions,
- Image,
- ImageBackground,
- ScrollView,
- StatusBar,
- StyleSheet,
- Text,
- TouchableOpacity,
- View,
- } from "react-native";
- import { useSafeAreaInsets } from "react-native-safe-area-context";
- import {
- WishRecordModal,
- WishRecordModalRef,
- } from "./components/WishRecordModal";
- import { WishRuleModal, WishRuleModalRef } from "./components/WishRuleModal";
- const { width: SCREEN_WIDTH } = Dimensions.get("window");
- const CDN_BASE = "https://cdn.acefig.com/kai_xin_ma_te/supermart";
- const wishImages = {
- bg: `${CDN_BASE}/common/wishBg.png`,
- title: `${CDN_BASE}/welfare/wishTitle.png`,
- rule: `${CDN_BASE}/welfare/wishRule.png`,
- addBg: `${CDN_BASE}/welfare/addBg.png`,
- addLiBg: `${CDN_BASE}/welfare/addLiBg.png`,
- addClose: `${CDN_BASE}/welfare/addClose.png`,
- addSectionBg: `${CDN_BASE}/welfare/toys/addSectionBg.png`,
- add: `${CDN_BASE}/welfare/add.png`,
- left: `${CDN_BASE}/box/detail/left.png`,
- right: `${CDN_BASE}/box/detail/right.png`,
- progressBar: `${CDN_BASE}/welfare/toys/wishProgressBar.png`,
- };
- interface WishItem {
- id: string;
- name: string;
- spu: { cover: string };
- quantity: number;
- completeQuantity: number;
- }
- interface GoodsItem {
- id: string;
- spu: { cover: string };
- magicAmount: number;
- }
- export default function WishScreen() {
- const router = useRouter();
- const insets = useSafeAreaInsets();
- const [tableData, setTableData] = useState<WishItem[]>([]);
- const [active, setActive] = useState(0);
- const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
- const [progress, setProgress] = useState(0);
- const [magicAmount, setMagicAmount] = useState(0);
- const ruleRef = useRef<WishRuleModalRef>(null);
- const recordRef = useRef<WishRecordModalRef>(null);
- const loadData = useCallback(async () => {
- try {
- const data = await Service.getWishList();
- // Ensure data is array
- const list = Array.isArray(data) ? data : data?.records || [];
- if (list && list.length > 0) {
- setTableData(list);
- }
- } catch (error) {
- console.error("加载祈愿数据失败:", error);
- }
- }, []);
- useEffect(() => {
- loadData();
- }, [loadData]);
- // Update progress when active item changes or goodsList changes
- useEffect(() => {
- const updateProgress = async () => {
- if (tableData.length > 0 && tableData[active]) {
- const current = tableData[active];
- if (goodsList.length === 0) {
- setProgress(0);
- setMagicAmount(0);
- } else {
- // Call preview to get progress
- const inventoryIds = goodsList.map((g) => g.id);
- try {
- const res = await Service.wishPreview({
- substituteGoodsId: current.id,
- inventoryIds,
- });
- if (res) {
- let p = (res.progress || 0) * 100;
- if (p > 100) p = 100;
- setProgress(p);
- setMagicAmount(res.magicAmount || 0);
- }
- } catch (e) {
- console.error(e);
- }
- }
- } else {
- setProgress(0);
- setMagicAmount(0);
- }
- };
- updateProgress();
- }, [tableData, active, goodsList]);
- const handlePrev = () => {
- if (active > 0) {
- setActive(active - 1);
- setGoodsList([]);
- }
- };
- const handleNext = () => {
- if (active < tableData.length - 1) {
- setActive(active + 1);
- setGoodsList([]);
- }
- };
- const currentItem = tableData[active];
- const serverRemaining = currentItem
- ? currentItem.quantity - currentItem.completeQuantity
- : 0;
- // Visual remaining doesn't update until submit in this flow
- const remaining = serverRemaining;
- const removeGoods = (item: GoodsItem) => {
- setGoodsList(goodsList.filter((g) => g.id !== item.id));
- };
- useEffect(() => {
- const subscription = EventUtils.on(
- EventUtils.keys.STORE_CHOOSE,
- (selectedMap: any) => {
- // SelectedMap comes as Array from store_choose
- const selectedList = Array.isArray(selectedMap)
- ? selectedMap
- : Object.values(selectedMap);
- setGoodsList((prev) => {
- const existingIds = new Set(prev.map((i) => i.id));
- const newItems = selectedList.filter(
- (i: any) => !existingIds.has(i.id),
- );
- return [...prev, ...newItems];
- });
- },
- );
- return () => {
- subscription.remove();
- };
- }, []);
- const openMaterialModal = () => {
- if (!currentItem) return;
- router.push("/dimension/store_choose");
- };
- const submitWish = async () => {
- try {
- const inventoryIds = goodsList.map((g) => g.id);
- console.log("Submitting Wish:", {
- substituteGoodsId: currentItem.id,
- inventoryIds,
- });
- const res = await Service.wishSubmit({
- substituteGoodsId: currentItem.id,
- inventoryIds,
- });
- console.log("Submit Res:", res);
- // Fix: Use loose equality since backend returns string "0"
- if (res.code == 0 || res.success) {
- Alert.alert("成功", "点亮成功!");
- setGoodsList([]);
- loadData();
- } else {
- Alert.alert("提交失败", `Code: ${res.code}, Msg: ${res.msg}`);
- }
- } catch (error) {
- console.error("LightUp Error:", error);
- Alert.alert("执行异常", String(error));
- }
- };
- const handleLightUp = async () => {
- if (!currentItem) return;
- if (progress < 100) {
- if (goodsList.length === 0) {
- Alert.alert("提示", "请先添加材料");
- return;
- }
- Alert.alert("提示", "进度未满100%,无法点亮");
- return;
- }
- Alert.alert("确认点亮", "是否确认消耗所选材料进行点亮?", [
- { text: "取消", style: "cancel" },
- {
- text: "确认",
- onPress: () => {
- submitWish();
- },
- },
- ]);
- };
- return (
- <View style={styles.container}>
- <StatusBar barStyle="light-content" />
- <ImageBackground
- source={{ uri: wishImages.bg }}
- 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>
- {/* 规则按钮 */}
- <TouchableOpacity
- style={[styles.ruleBtn, { top: insets.top + 160 }]}
- onPress={() => ruleRef.current?.show()}
- >
- <Image
- source={{ uri: wishImages.rule }}
- style={styles.ruleBtnImg}
- resizeMode="contain"
- />
- </TouchableOpacity>
- <ScrollView
- style={styles.scrollView}
- showsVerticalScrollIndicator={false}
- >
- <View style={{ height: insets.top + 50 }} />
- {/* 标题图片 */}
- <View style={styles.titleBox}>
- <Image
- source={{ uri: wishImages.title }}
- style={styles.titleImg}
- resizeMode="contain"
- />
- </View>
- {/* 卡片轮播区域 */}
- <View style={styles.swiperBox}>
- {/* 左箭头 */}
- {active > 0 && (
- <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
- <Image
- source={{ uri: wishImages.left }}
- style={styles.arrowImg}
- resizeMode="contain"
- />
- </TouchableOpacity>
- )}
- {/* 卡片 */}
- <View style={styles.cardBox}>
- {currentItem ? (
- <View style={styles.card}>
- <View style={styles.imgBox}>
- <Image
- source={{ uri: currentItem.spu?.cover }}
- style={styles.spuImage}
- resizeMode="contain"
- />
- {/* 仅剩标签 */}
- <View style={styles.remainingBox}>
- <Text style={styles.remainingText}>
- 仅剩:{remaining}
- </Text>
- </View>
- </View>
- <Text style={styles.cardName} numberOfLines={1}>
- {currentItem.name}
- </Text>
- </View>
- ) : (
- <View style={styles.card}>
- <View style={styles.imgBox}>
- <Text style={styles.emptyText}>暂无祈愿商品</Text>
- </View>
- </View>
- )}
- </View>
- {/* 右箭头 */}
- {active < tableData.length - 1 && (
- <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
- <Image
- source={{ uri: wishImages.right }}
- style={styles.arrowImg}
- resizeMode="contain"
- />
- </TouchableOpacity>
- )}
- </View>
- {/* 指示点 */}
- {tableData.length > 1 && (
- <View style={styles.dotsBox}>
- {tableData.map((_, i) => (
- <View
- key={i}
- style={[styles.dot, i === active && styles.dotActive]}
- />
- ))}
- </View>
- )}
- {/* 进度条区域 */}
- <View style={styles.progressSection}>
- <View style={styles.progressLabelBox}>
- <Text style={styles.progressLabel}>进度:</Text>
- </View>
- {/* Show Magic Return if 100% and magicAmount > 0 */}
- {progress >= 100 && magicAmount > 0 && (
- <View style={styles.magicBubble}>
- <Text style={styles.magicText}>返还: {magicAmount}果实</Text>
- </View>
- )}
- <View style={styles.progressBar}>
- <View style={[styles.progressFill, { width: `${progress}%` }]} />
- </View>
- <Text style={styles.progressText}>
- {progress > 0 ? progress.toFixed(1) : progress}%
- </Text>
- </View>
- {/* 材料添加区域 */}
- <ImageBackground
- source={{ uri: wishImages.addSectionBg }}
- style={styles.addSection}
- resizeMode="stretch"
- >
- <Text style={styles.addTitle}>材料添加</Text>
- <View style={styles.addMain}>
- {/* 添加按钮 */}
- <TouchableOpacity
- style={styles.addBtn}
- onPress={openMaterialModal}
- >
- <ImageBackground
- source={{ uri: wishImages.addBg }}
- style={styles.addBtnBg}
- resizeMode="contain"
- >
- <Image
- source={{ uri: wishImages.add }}
- style={styles.addIcon}
- resizeMode="contain"
- />
- <Text style={styles.addBtnText}>添加</Text>
- </ImageBackground>
- </TouchableOpacity>
- {/* 已添加的材料列表 */}
- <View style={styles.addCenter}>
- <ScrollView
- horizontal
- showsHorizontalScrollIndicator={false}
- contentContainerStyle={styles.addScrollContent}
- >
- {goodsList.map((item, index) => (
- <View key={item.id || index} style={styles.addItem}>
- <ImageBackground
- source={{ uri: wishImages.addLiBg }}
- style={styles.addItemBg}
- resizeMode="contain"
- >
- <Image
- source={{ uri: item.spu?.cover }}
- style={styles.addItemImg}
- resizeMode="cover"
- />
- </ImageBackground>
- <TouchableOpacity
- style={styles.closeBtn}
- onPress={() => removeGoods(item)}
- >
- <Image
- source={{ uri: wishImages.addClose }}
- style={styles.closeIcon}
- resizeMode="contain"
- />
- </TouchableOpacity>
- </View>
- ))}
- </ScrollView>
- </View>
- </View>
- </ImageBackground>
- {/* 底部按钮 */}
- <View style={styles.bottomContainer}>
- {/* Record Link (Optional, if design has it outside standard flow, adding it near bottom or top)
- Wait, original design had "Rule" on left, maybe "Record" is not prominently displayed or inside Rule?
- Let's stick to current design.
- */}
- <TouchableOpacity style={styles.bottomBtn} onPress={handleLightUp}>
- <ImageBackground
- source={{
- uri:
- progress >= 100 || goodsList.length > 0
- ? Images.common.butBgL
- : Images.common.butBgHui,
- }}
- style={styles.bottomBtnBg}
- resizeMode="contain"
- >
- <Text style={styles.bottomBtnText}>
- {progress >= 100
- ? "点亮心愿"
- : goodsList.length > 0
- ? "确认添加"
- : "等待参与"}
- </Text>
- </ImageBackground>
- </TouchableOpacity>
- </View>
- <Text style={styles.bottomTip}>*本活动最终解释权归本平台所有</Text>
- <View style={{ height: 100 }} />
- </ScrollView>
- <WishRuleModal ref={ruleRef} />
- <WishRecordModal ref={recordRef} />
- </ImageBackground>
- </View>
- );
- }
- const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: "#1a1a2e" },
- background: { flex: 1 },
- header: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
- paddingHorizontal: 10,
- paddingBottom: 10,
- position: "absolute",
- top: 0,
- left: 0,
- right: 0,
- zIndex: 100,
- },
- backBtn: {
- width: 40,
- height: 40,
- justifyContent: "center",
- alignItems: "center",
- },
- backText: { color: "#fff", fontSize: 20 },
- title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
- placeholder: { width: 40 },
- scrollView: { flex: 1 },
- // 规则按钮
- ruleBtn: { position: "absolute", left: 0, zIndex: 99 },
- ruleBtnImg: { width: 39, height: 20 },
- // 标题
- titleBox: { alignItems: "center", marginBottom: 10 },
- titleImg: { width: 288, height: 86 },
- // 轮播区域
- swiperBox: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "center",
- paddingHorizontal: 20,
- minHeight: 350,
- position: "relative",
- },
- prevBtn: { position: "absolute", left: 15, zIndex: 10 },
- nextBtn: { position: "absolute", right: 15, zIndex: 10 },
- arrowImg: { width: 51, height: 49 },
- // 卡片
- cardBox: { alignItems: "center" },
- card: { alignItems: "center" },
- imgBox: {
- width: 215,
- height: 284,
- backgroundColor: "#FFF7D8",
- borderWidth: 4.5,
- borderColor: "#000",
- justifyContent: "center",
- alignItems: "center",
- position: "relative",
- },
- spuImage: { width: 200, height: 260 },
- emptyText: { color: "#999", fontSize: 14 },
- remainingBox: {
- position: "absolute",
- bottom: "15%",
- backgroundColor: "#fff",
- borderWidth: 3,
- borderColor: "#000",
- paddingHorizontal: 15,
- paddingVertical: 5,
- },
- remainingText: { fontSize: 13, fontWeight: "bold", color: "#000" },
- cardName: {
- color: "#D0D0D0",
- fontSize: 12,
- marginTop: 13,
- textAlign: "center",
- maxWidth: 200,
- },
- // 指示点
- dotsBox: { flexDirection: "row", justifyContent: "center", marginTop: 10 },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- backgroundColor: "#666",
- marginHorizontal: 3,
- },
- dotActive: { backgroundColor: "#fff" },
- // 进度条
- progressSection: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "center",
- marginTop: 5,
- paddingHorizontal: 20,
- position: "relative",
- zIndex: 20,
- },
- progressLabelBox: { width: 40 },
- progressLabel: { color: "#fff", fontSize: 12 },
- magicBubble: {
- position: "absolute",
- right: 0,
- top: -24,
- zIndex: 10,
- backgroundColor: "#f44336",
- borderRadius: 4,
- paddingHorizontal: 8,
- paddingVertical: 3,
- },
- magicText: { color: "#fff", fontSize: 10 },
- progressBar: {
- flex: 1,
- height: 14,
- backgroundColor: "#FFEABE",
- borderWidth: 2,
- borderColor: "#000",
- marginHorizontal: 5,
- position: "relative",
- },
- progressFill: {
- height: "100%",
- backgroundColor: "#FFAD00",
- borderRightWidth: 2,
- borderRightColor: "#000",
- },
- progressText: { color: "#fff", fontSize: 12, width: 40, textAlign: "right" },
- // 材料添加区域
- addSection: {
- width: SCREEN_WIDTH - 20,
- height: 124,
- marginHorizontal: 10,
- marginTop: 10,
- paddingTop: 10,
- },
- addTitle: {
- color: "#fff",
- fontSize: 14,
- textAlign: "center",
- fontWeight: "400",
- textShadowColor: "#000",
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- marginBottom: 7,
- },
- addMain: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 25,
- },
- addBtn: { marginRight: 10 },
- addBtnBg: {
- width: 42,
- height: 42,
- justifyContent: "center",
- alignItems: "center",
- paddingTop: 5,
- },
- addIcon: { width: 16, height: 16 },
- addBtnText: { fontSize: 12, color: "#000", fontWeight: "500" },
- addCenter: {
- flex: 1,
- height: 60,
- backgroundColor: "#FFFBEA",
- paddingTop: 7,
- paddingLeft: 20,
- },
- addScrollContent: { paddingRight: 10 },
- addItem: { position: "relative", marginRight: 10 },
- addItemBg: {
- width: 42,
- height: 42,
- justifyContent: "center",
- alignItems: "center",
- },
- addItemImg: { width: "80%", height: "80%" },
- closeBtn: { position: "absolute", right: 0, top: 0, width: 13, height: 13 },
- closeIcon: { width: "100%", height: "100%" },
- // 底部按钮
- bottomContainer: { alignItems: "center", marginTop: -10 },
- bottomBtn: { alignItems: "center" },
- bottomBtnBg: {
- width: 160,
- height: 60,
- justifyContent: "center",
- alignItems: "center",
- paddingTop: 4,
- },
- bottomBtnText: {
- color: "#fff",
- fontSize: 15,
- fontWeight: "bold",
- textShadowColor: "#000",
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- },
- bottomTip: {
- color: "rgba(255,255,255,0.67)",
- fontSize: 10,
- textAlign: "center",
- marginTop: 5,
- },
- });
|