wish.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import { Image } from 'expo-image';
  2. import { useRouter } from 'expo-router';
  3. import React, { useCallback, useEffect, useState } from 'react';
  4. import {
  5. Dimensions,
  6. ImageBackground,
  7. ScrollView,
  8. StatusBar,
  9. StyleSheet,
  10. Text,
  11. TouchableOpacity,
  12. View,
  13. } from 'react-native';
  14. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  15. import { Images } from '@/constants/images';
  16. import { get } from '@/services/http';
  17. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  18. const CDN_BASE = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart';
  19. const wishImages = {
  20. bg: `${CDN_BASE}/common/wishBg.png`,
  21. title: `${CDN_BASE}/welfare/wishTitle.png`,
  22. rule: `${CDN_BASE}/welfare/wishRule.png`,
  23. addBg: `${CDN_BASE}/welfare/addBg.png`,
  24. addLiBg: `${CDN_BASE}/welfare/addLiBg.png`,
  25. addClose: `${CDN_BASE}/welfare/addClose.png`,
  26. addSectionBg: `${CDN_BASE}/welfare/toys/addSectionBg.png`,
  27. left: `${CDN_BASE}/box/detail/left.png`,
  28. right: `${CDN_BASE}/box/detail/right.png`,
  29. };
  30. interface WishItem {
  31. id: string;
  32. name: string;
  33. spu: { cover: string };
  34. quantity: number;
  35. completeQuantity: number;
  36. }
  37. interface GoodsItem {
  38. id: string;
  39. spu: { cover: string };
  40. }
  41. export default function WishScreen() {
  42. const router = useRouter();
  43. const insets = useSafeAreaInsets();
  44. const [tableData, setTableData] = useState<WishItem[]>([]);
  45. const [active, setActive] = useState(0);
  46. const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
  47. const [progress, setProgress] = useState(0);
  48. const loadData = useCallback(async () => {
  49. try {
  50. const res = await get('/api/wish/list');
  51. if (res.data && res.data.length > 0) {
  52. setTableData(res.data);
  53. }
  54. } catch (error) {
  55. console.error('加载祈愿数据失败:', error);
  56. }
  57. }, []);
  58. useEffect(() => {
  59. loadData();
  60. }, [loadData]);
  61. const handlePrev = () => {
  62. if (active > 0) setActive(active - 1);
  63. };
  64. const handleNext = () => {
  65. if (active < tableData.length - 1) setActive(active + 1);
  66. };
  67. const currentItem = tableData[active];
  68. const remaining = currentItem ? currentItem.quantity - currentItem.completeQuantity : 0;
  69. return (
  70. <View style={styles.container}>
  71. <StatusBar barStyle="light-content" />
  72. <ImageBackground source={{ uri: wishImages.bg }} style={styles.background} resizeMode="cover">
  73. {/* 顶部导航 */}
  74. <View style={[styles.header, { paddingTop: insets.top }]}>
  75. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  76. <Text style={styles.backText}>←</Text>
  77. </TouchableOpacity>
  78. <Text style={styles.title}>艾斯祈福</Text>
  79. <View style={styles.placeholder} />
  80. </View>
  81. <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
  82. <View style={{ height: insets.top + 50 }} />
  83. {/* 标题 */}
  84. <View style={styles.titleBox}>
  85. <Image source={{ uri: wishImages.title }} style={styles.titleImg} contentFit="contain" />
  86. </View>
  87. {/* 规则按钮 */}
  88. <TouchableOpacity style={styles.ruleBtn}>
  89. <Image source={{ uri: wishImages.rule }} style={styles.ruleBtnImg} contentFit="contain" />
  90. </TouchableOpacity>
  91. {/* 商品轮播 */}
  92. <View style={styles.swiperBox}>
  93. {active > 0 && (
  94. <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
  95. <Image source={{ uri: wishImages.left }} style={styles.arrowImg} contentFit="contain" />
  96. </TouchableOpacity>
  97. )}
  98. <View style={styles.cardBox}>
  99. {currentItem && (
  100. <View style={styles.card}>
  101. <View style={styles.imgBox}>
  102. <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} contentFit="contain" />
  103. <View style={styles.remainingBox}>
  104. <Text style={styles.remainingText}>仅剩:{remaining}</Text>
  105. </View>
  106. </View>
  107. <Text style={styles.cardName} numberOfLines={1}>{currentItem.name}</Text>
  108. </View>
  109. )}
  110. </View>
  111. {active < tableData.length - 1 && (
  112. <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
  113. <Image source={{ uri: wishImages.right }} style={styles.arrowImg} contentFit="contain" />
  114. </TouchableOpacity>
  115. )}
  116. </View>
  117. {/* 进度条 */}
  118. <View style={styles.progressSection}>
  119. <Text style={styles.progressLabel}>进度:</Text>
  120. <View style={styles.progressBar}>
  121. <View style={[styles.progressFill, { width: `${progress}%` }]} />
  122. </View>
  123. <Text style={styles.progressText}>{progress}%</Text>
  124. </View>
  125. {/* 材料添加区域 */}
  126. <ImageBackground source={{ uri: wishImages.addSectionBg }} style={styles.addSection} resizeMode="stretch">
  127. <Text style={styles.addTitle}>材料添加</Text>
  128. <View style={styles.addMain}>
  129. <TouchableOpacity style={styles.addBtn}>
  130. <ImageBackground source={{ uri: wishImages.addBg }} style={styles.addBtnBg} resizeMode="contain">
  131. <Image source={{ uri: `${CDN_BASE}/welfare/add.png` }} style={styles.addIcon} contentFit="contain" />
  132. <Text style={styles.addBtnText}>添加</Text>
  133. </ImageBackground>
  134. </TouchableOpacity>
  135. <View style={styles.addCenter}>
  136. <ScrollView horizontal showsHorizontalScrollIndicator={false}>
  137. {goodsList.map((item, index) => (
  138. <View key={item.id || index} style={styles.addItem}>
  139. <ImageBackground source={{ uri: wishImages.addLiBg }} style={styles.addItemBg} resizeMode="contain">
  140. <Image source={{ uri: item.spu?.cover }} style={styles.addItemImg} contentFit="cover" />
  141. </ImageBackground>
  142. <TouchableOpacity style={styles.closeBtn}>
  143. <Image source={{ uri: wishImages.addClose }} style={styles.closeIcon} contentFit="contain" />
  144. </TouchableOpacity>
  145. </View>
  146. ))}
  147. {/* 空位占位 */}
  148. {Array.from({ length: Math.max(0, 6 - goodsList.length) }).map((_, i) => (
  149. <View key={`empty-${i}`} style={styles.emptySlot} />
  150. ))}
  151. </ScrollView>
  152. </View>
  153. </View>
  154. </ImageBackground>
  155. {/* 底部按钮 */}
  156. <TouchableOpacity style={styles.bottomBtn}>
  157. <ImageBackground source={{ uri: Images.common.butBgHui }} style={styles.bottomBtnBg} resizeMode="contain">
  158. <Text style={styles.bottomBtnText}>还差一点点,去收集</Text>
  159. </ImageBackground>
  160. </TouchableOpacity>
  161. <Text style={styles.bottomTip}>*本活动最终解释权归本平台所有</Text>
  162. <View style={{ height: 100 }} />
  163. </ScrollView>
  164. </ImageBackground>
  165. </View>
  166. );
  167. }
  168. const styles = StyleSheet.create({
  169. container: { flex: 1, backgroundColor: '#1a1a2e' },
  170. background: { flex: 1 },
  171. header: {
  172. flexDirection: 'row',
  173. alignItems: 'center',
  174. justifyContent: 'space-between',
  175. paddingHorizontal: 10,
  176. paddingBottom: 10,
  177. position: 'absolute',
  178. top: 0,
  179. left: 0,
  180. right: 0,
  181. zIndex: 100,
  182. },
  183. backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
  184. backText: { color: '#fff', fontSize: 20 },
  185. title: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
  186. placeholder: { width: 40 },
  187. scrollView: { flex: 1 },
  188. titleBox: { alignItems: 'center', marginBottom: 10 },
  189. titleImg: { width: 288, height: 86 },
  190. ruleBtn: { position: 'absolute', left: 0, top: 180, zIndex: 10 },
  191. ruleBtnImg: { width: 39, height: 20 },
  192. swiperBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: 20 },
  193. prevBtn: { position: 'absolute', left: 15, zIndex: 10 },
  194. nextBtn: { position: 'absolute', right: 15, zIndex: 10 },
  195. arrowImg: { width: 51, height: 49 },
  196. cardBox: { alignItems: 'center' },
  197. card: { alignItems: 'center' },
  198. imgBox: {
  199. width: 215,
  200. height: 284,
  201. backgroundColor: '#FFF7D8',
  202. borderWidth: 4.5,
  203. borderColor: '#000',
  204. justifyContent: 'center',
  205. alignItems: 'center',
  206. position: 'relative',
  207. },
  208. spuImage: { width: 200, height: 275 },
  209. remainingBox: {
  210. position: 'absolute',
  211. bottom: '15%',
  212. backgroundColor: '#fff',
  213. borderWidth: 3,
  214. borderColor: '#000',
  215. paddingHorizontal: 15,
  216. paddingVertical: 5,
  217. },
  218. remainingText: { fontSize: 13, fontWeight: 'bold', color: '#000' },
  219. cardName: { color: '#D0D0D0', fontSize: 12, marginTop: 13, textAlign: 'center', maxWidth: 200 },
  220. progressSection: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginTop: 5, paddingHorizontal: 20 },
  221. progressLabel: { color: '#fff', fontSize: 12, width: 40 },
  222. progressBar: { flex: 1, height: 14, backgroundColor: '#FFEABE', borderWidth: 2, borderColor: '#000', marginHorizontal: 5 },
  223. progressFill: { height: '100%', backgroundColor: '#FFAD00', borderRightWidth: 2, borderRightColor: '#000' },
  224. progressText: { color: '#fff', fontSize: 12, width: 40, textAlign: 'right' },
  225. addSection: { width: SCREEN_WIDTH - 20, height: 124, marginHorizontal: 10, marginTop: 10, paddingTop: 10 },
  226. addTitle: { color: '#fff', fontSize: 14, textAlign: 'center', fontWeight: '400', textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1, marginBottom: 7 },
  227. addMain: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 25 },
  228. addBtn: { marginRight: 10 },
  229. addBtnBg: { width: 42, height: 42, justifyContent: 'center', alignItems: 'center', paddingTop: 5 },
  230. addIcon: { width: 16, height: 16 },
  231. addBtnText: { fontSize: 12, color: '#000', fontWeight: '500' },
  232. addCenter: { flex: 1, height: 60, backgroundColor: '#FFFBEA', paddingLeft: 20, paddingTop: 7 },
  233. addItem: { position: 'relative', marginRight: 10 },
  234. addItemBg: { width: 42, height: 42, justifyContent: 'center', alignItems: 'center' },
  235. addItemImg: { width: '80%', height: '80%' },
  236. closeBtn: { position: 'absolute', right: 0, top: 0, width: 13, height: 13 },
  237. closeIcon: { width: '100%', height: '100%' },
  238. emptySlot: { width: 42, height: 42, backgroundColor: '#f0f0f0', borderWidth: 1, borderColor: '#ddd', marginRight: 10 },
  239. bottomBtn: { alignItems: 'center', marginTop: -10 },
  240. bottomBtnBg: { width: 160, height: 60, justifyContent: 'center', alignItems: 'center', paddingTop: 4 },
  241. bottomBtnText: { color: '#fff', fontSize: 15, fontWeight: 'bold', textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
  242. bottomTip: { color: 'rgba(255,255,255,0.67)', fontSize: 10, textAlign: 'center', marginTop: 5 },
  243. });