|
|
@@ -1,8 +1,11 @@
|
|
|
-import { Image } from 'expo-image';
|
|
|
+import { Images } from '@/constants/images';
|
|
|
+import Service from '@/services/weal';
|
|
|
import { useRouter } from 'expo-router';
|
|
|
-import React, { useCallback, useEffect, useState } from 'react';
|
|
|
+import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
import {
|
|
|
+ Alert,
|
|
|
Dimensions,
|
|
|
+ Image,
|
|
|
ImageBackground,
|
|
|
ScrollView,
|
|
|
StatusBar,
|
|
|
@@ -12,9 +15,9 @@ import {
|
|
|
View,
|
|
|
} from 'react-native';
|
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
-
|
|
|
-import { Images } from '@/constants/images';
|
|
|
-import { get } from '@/services/http';
|
|
|
+import { WishMaterialModal, WishMaterialModalRef } from './components/WishMaterialModal';
|
|
|
+import { WishRecordModal, WishRecordModalRef } from './components/WishRecordModal';
|
|
|
+import { WishRuleModal, WishRuleModalRef } from './components/WishRuleModal';
|
|
|
|
|
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
|
const CDN_BASE = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart';
|
|
|
@@ -44,6 +47,7 @@ interface WishItem {
|
|
|
interface GoodsItem {
|
|
|
id: string;
|
|
|
spu: { cover: string };
|
|
|
+ magicAmount: number;
|
|
|
}
|
|
|
|
|
|
export default function WishScreen() {
|
|
|
@@ -53,12 +57,21 @@ export default function WishScreen() {
|
|
|
const [active, setActive] = useState(0);
|
|
|
const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
|
|
|
const [progress, setProgress] = useState(0);
|
|
|
+ const [magicAmount, setMagicAmount] = useState(0);
|
|
|
+
|
|
|
+ // Modals
|
|
|
+ const ruleRef = useRef<WishRuleModalRef>(null);
|
|
|
+ const recordRef = useRef<WishRecordModalRef>(null);
|
|
|
+ const materialRef = useRef<WishMaterialModalRef>(null);
|
|
|
|
|
|
const loadData = useCallback(async () => {
|
|
|
try {
|
|
|
- const res = await get('/api/wish/list');
|
|
|
- if (res.data && res.data.length > 0) {
|
|
|
- setTableData(res.data);
|
|
|
+ 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);
|
|
|
@@ -69,21 +82,129 @@ export default function WishScreen() {
|
|
|
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);
|
|
|
+ if (active > 0) {
|
|
|
+ setActive(active - 1);
|
|
|
+ setGoodsList([]);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const handleNext = () => {
|
|
|
- if (active < tableData.length - 1) setActive(active + 1);
|
|
|
+ if (active < tableData.length - 1) {
|
|
|
+ setActive(active + 1);
|
|
|
+ setGoodsList([]);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const currentItem = tableData[active];
|
|
|
- const remaining = currentItem ? currentItem.quantity - currentItem.completeQuantity : 0;
|
|
|
+ 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));
|
|
|
};
|
|
|
|
|
|
+ const openMaterialModal = () => {
|
|
|
+ if (!currentItem) return;
|
|
|
+ materialRef.current?.show((selected) => {
|
|
|
+ // Add selected items to goodsList
|
|
|
+ setGoodsList(prev => {
|
|
|
+ const newIds = new Set(prev.map(i => i.id));
|
|
|
+ const toAdd = selected.filter(i => !newIds.has(i.id));
|
|
|
+ return [...prev, ...toAdd];
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ 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" />
|
|
|
@@ -98,8 +219,8 @@ export default function WishScreen() {
|
|
|
</View>
|
|
|
|
|
|
{/* 规则按钮 */}
|
|
|
- <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 160 }]}>
|
|
|
- <Image source={{ uri: wishImages.rule }} style={styles.ruleBtnImg} contentFit="contain" />
|
|
|
+ <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}>
|
|
|
@@ -107,7 +228,7 @@ export default function WishScreen() {
|
|
|
|
|
|
{/* 标题图片 */}
|
|
|
<View style={styles.titleBox}>
|
|
|
- <Image source={{ uri: wishImages.title }} style={styles.titleImg} contentFit="contain" />
|
|
|
+ <Image source={{ uri: wishImages.title }} style={styles.titleImg} resizeMode="contain" />
|
|
|
</View>
|
|
|
|
|
|
{/* 卡片轮播区域 */}
|
|
|
@@ -115,7 +236,7 @@ export default function WishScreen() {
|
|
|
{/* 左箭头 */}
|
|
|
{active > 0 && (
|
|
|
<TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
|
|
|
- <Image source={{ uri: wishImages.left }} style={styles.arrowImg} contentFit="contain" />
|
|
|
+ <Image source={{ uri: wishImages.left }} style={styles.arrowImg} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
|
)}
|
|
|
|
|
|
@@ -124,7 +245,7 @@ export default function WishScreen() {
|
|
|
{currentItem ? (
|
|
|
<View style={styles.card}>
|
|
|
<View style={styles.imgBox}>
|
|
|
- <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} contentFit="contain" />
|
|
|
+ <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} resizeMode="contain" />
|
|
|
{/* 仅剩标签 */}
|
|
|
<View style={styles.remainingBox}>
|
|
|
<Text style={styles.remainingText}>仅剩:{remaining}</Text>
|
|
|
@@ -146,7 +267,7 @@ export default function WishScreen() {
|
|
|
{/* 右箭头 */}
|
|
|
{active < tableData.length - 1 && (
|
|
|
<TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
|
|
|
- <Image source={{ uri: wishImages.right }} style={styles.arrowImg} contentFit="contain" />
|
|
|
+ <Image source={{ uri: wishImages.right }} style={styles.arrowImg} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
|
)}
|
|
|
</View>
|
|
|
@@ -162,7 +283,17 @@ export default function WishScreen() {
|
|
|
|
|
|
{/* 进度条区域 */}
|
|
|
<View style={styles.progressSection}>
|
|
|
- <Text style={styles.progressLabel}>进度:</Text>
|
|
|
+ <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>
|
|
|
@@ -174,9 +305,9 @@ export default function WishScreen() {
|
|
|
<Text style={styles.addTitle}>材料添加</Text>
|
|
|
<View style={styles.addMain}>
|
|
|
{/* 添加按钮 */}
|
|
|
- <TouchableOpacity style={styles.addBtn}>
|
|
|
+ <TouchableOpacity style={styles.addBtn} onPress={openMaterialModal}>
|
|
|
<ImageBackground source={{ uri: wishImages.addBg }} style={styles.addBtnBg} resizeMode="contain">
|
|
|
- <Image source={{ uri: wishImages.add }} style={styles.addIcon} contentFit="contain" />
|
|
|
+ <Image source={{ uri: wishImages.add }} style={styles.addIcon} resizeMode="contain" />
|
|
|
<Text style={styles.addBtnText}>添加</Text>
|
|
|
</ImageBackground>
|
|
|
</TouchableOpacity>
|
|
|
@@ -187,10 +318,10 @@ export default function WishScreen() {
|
|
|
{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} contentFit="cover" />
|
|
|
+ <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} contentFit="contain" />
|
|
|
+ <Image source={{ uri: wishImages.addClose }} style={styles.closeIcon} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
|
</View>
|
|
|
))}
|
|
|
@@ -200,15 +331,29 @@ export default function WishScreen() {
|
|
|
</ImageBackground>
|
|
|
|
|
|
{/* 底部按钮 */}
|
|
|
- <TouchableOpacity style={styles.bottomBtn}>
|
|
|
- <ImageBackground source={{ uri: Images.common.butBgHui }} style={styles.bottomBtnBg} resizeMode="contain">
|
|
|
- <Text style={styles.bottomBtnText}>{progress >= 100 ? '点亮心愿' : '等待参与'}</Text>
|
|
|
- </ImageBackground>
|
|
|
- </TouchableOpacity>
|
|
|
+ <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} />
|
|
|
+ <WishMaterialModal ref={materialRef} />
|
|
|
</ImageBackground>
|
|
|
</View>
|
|
|
);
|
|
|
@@ -301,8 +446,22 @@ const styles = StyleSheet.create({
|
|
|
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,
|
|
|
},
|
|
|
- progressLabel: { color: '#fff', fontSize: 12, width: 40 },
|
|
|
+ magicText: { color: '#fff', fontSize: 10 },
|
|
|
progressBar: {
|
|
|
flex: 1,
|
|
|
height: 14,
|
|
|
@@ -368,7 +527,8 @@ const styles = StyleSheet.create({
|
|
|
closeIcon: { width: '100%', height: '100%' },
|
|
|
|
|
|
// 底部按钮
|
|
|
- bottomBtn: { alignItems: 'center', marginTop: -10 },
|
|
|
+ bottomContainer: { alignItems: 'center', marginTop: -10 },
|
|
|
+ bottomBtn: { alignItems: 'center' },
|
|
|
bottomBtnBg: {
|
|
|
width: 160,
|
|
|
height: 60,
|