wish.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. add: `${CDN_BASE}/welfare/add.png`,
  28. left: `${CDN_BASE}/box/detail/left.png`,
  29. right: `${CDN_BASE}/box/detail/right.png`,
  30. progressBar: `${CDN_BASE}/welfare/toys/wishProgressBar.png`,
  31. };
  32. interface WishItem {
  33. id: string;
  34. name: string;
  35. spu: { cover: string };
  36. quantity: number;
  37. completeQuantity: number;
  38. }
  39. interface GoodsItem {
  40. id: string;
  41. spu: { cover: string };
  42. }
  43. export default function WishScreen() {
  44. const router = useRouter();
  45. const insets = useSafeAreaInsets();
  46. const [tableData, setTableData] = useState<WishItem[]>([]);
  47. const [active, setActive] = useState(0);
  48. const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
  49. const [progress, setProgress] = useState(0);
  50. const loadData = useCallback(async () => {
  51. try {
  52. const res = await get('/api/wish/list');
  53. if (res.data && res.data.length > 0) {
  54. setTableData(res.data);
  55. }
  56. } catch (error) {
  57. console.error('加载祈愿数据失败:', error);
  58. }
  59. }, []);
  60. useEffect(() => {
  61. loadData();
  62. }, [loadData]);
  63. const handlePrev = () => {
  64. if (active > 0) setActive(active - 1);
  65. };
  66. const handleNext = () => {
  67. if (active < tableData.length - 1) setActive(active + 1);
  68. };
  69. const currentItem = tableData[active];
  70. const remaining = currentItem ? currentItem.quantity - currentItem.completeQuantity : 0;
  71. const removeGoods = (item: GoodsItem) => {
  72. setGoodsList(goodsList.filter((g) => g.id !== item.id));
  73. };
  74. return (
  75. <View style={styles.container}>
  76. <StatusBar barStyle="light-content" />
  77. <ImageBackground source={{ uri: wishImages.bg }} style={styles.background} resizeMode="cover">
  78. {/* 头部导航 */}
  79. <View style={[styles.header, { paddingTop: insets.top }]}>
  80. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  81. <Text style={styles.backText}>←</Text>
  82. </TouchableOpacity>
  83. <Text style={styles.title}>祈愿</Text>
  84. <View style={styles.placeholder} />
  85. </View>
  86. {/* 规则按钮 */}
  87. <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 160 }]}>
  88. <Image source={{ uri: wishImages.rule }} style={styles.ruleBtnImg} contentFit="contain" />
  89. </TouchableOpacity>
  90. <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
  91. <View style={{ height: insets.top + 50 }} />
  92. {/* 标题图片 */}
  93. <View style={styles.titleBox}>
  94. <Image source={{ uri: wishImages.title }} style={styles.titleImg} contentFit="contain" />
  95. </View>
  96. {/* 卡片轮播区域 */}
  97. <View style={styles.swiperBox}>
  98. {/* 左箭头 */}
  99. {active > 0 && (
  100. <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
  101. <Image source={{ uri: wishImages.left }} style={styles.arrowImg} contentFit="contain" />
  102. </TouchableOpacity>
  103. )}
  104. {/* 卡片 */}
  105. <View style={styles.cardBox}>
  106. {currentItem ? (
  107. <View style={styles.card}>
  108. <View style={styles.imgBox}>
  109. <Image source={{ uri: currentItem.spu?.cover }} style={styles.spuImage} contentFit="contain" />
  110. {/* 仅剩标签 */}
  111. <View style={styles.remainingBox}>
  112. <Text style={styles.remainingText}>仅剩:{remaining}</Text>
  113. </View>
  114. </View>
  115. <Text style={styles.cardName} numberOfLines={1}>
  116. {currentItem.name}
  117. </Text>
  118. </View>
  119. ) : (
  120. <View style={styles.card}>
  121. <View style={styles.imgBox}>
  122. <Text style={styles.emptyText}>暂无祈愿商品</Text>
  123. </View>
  124. </View>
  125. )}
  126. </View>
  127. {/* 右箭头 */}
  128. {active < tableData.length - 1 && (
  129. <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
  130. <Image source={{ uri: wishImages.right }} style={styles.arrowImg} contentFit="contain" />
  131. </TouchableOpacity>
  132. )}
  133. </View>
  134. {/* 指示点 */}
  135. {tableData.length > 1 && (
  136. <View style={styles.dotsBox}>
  137. {tableData.map((_, i) => (
  138. <View key={i} style={[styles.dot, i === active && styles.dotActive]} />
  139. ))}
  140. </View>
  141. )}
  142. {/* 进度条区域 */}
  143. <View style={styles.progressSection}>
  144. <Text style={styles.progressLabel}>进度:</Text>
  145. <View style={styles.progressBar}>
  146. <View style={[styles.progressFill, { width: `${progress}%` }]} />
  147. </View>
  148. <Text style={styles.progressText}>{progress > 0 ? progress.toFixed(1) : progress}%</Text>
  149. </View>
  150. {/* 材料添加区域 */}
  151. <ImageBackground source={{ uri: wishImages.addSectionBg }} style={styles.addSection} resizeMode="stretch">
  152. <Text style={styles.addTitle}>材料添加</Text>
  153. <View style={styles.addMain}>
  154. {/* 添加按钮 */}
  155. <TouchableOpacity style={styles.addBtn}>
  156. <ImageBackground source={{ uri: wishImages.addBg }} style={styles.addBtnBg} resizeMode="contain">
  157. <Image source={{ uri: wishImages.add }} style={styles.addIcon} contentFit="contain" />
  158. <Text style={styles.addBtnText}>添加</Text>
  159. </ImageBackground>
  160. </TouchableOpacity>
  161. {/* 已添加的材料列表 */}
  162. <View style={styles.addCenter}>
  163. <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.addScrollContent}>
  164. {goodsList.map((item, index) => (
  165. <View key={item.id || index} style={styles.addItem}>
  166. <ImageBackground source={{ uri: wishImages.addLiBg }} style={styles.addItemBg} resizeMode="contain">
  167. <Image source={{ uri: item.spu?.cover }} style={styles.addItemImg} contentFit="cover" />
  168. </ImageBackground>
  169. <TouchableOpacity style={styles.closeBtn} onPress={() => removeGoods(item)}>
  170. <Image source={{ uri: wishImages.addClose }} style={styles.closeIcon} contentFit="contain" />
  171. </TouchableOpacity>
  172. </View>
  173. ))}
  174. </ScrollView>
  175. </View>
  176. </View>
  177. </ImageBackground>
  178. {/* 底部按钮 */}
  179. <TouchableOpacity style={styles.bottomBtn}>
  180. <ImageBackground source={{ uri: Images.common.butBgHui }} style={styles.bottomBtnBg} resizeMode="contain">
  181. <Text style={styles.bottomBtnText}>{progress >= 100 ? '点亮心愿' : '等待参与'}</Text>
  182. </ImageBackground>
  183. </TouchableOpacity>
  184. <Text style={styles.bottomTip}>*本活动最终解释权归本平台所有</Text>
  185. <View style={{ height: 100 }} />
  186. </ScrollView>
  187. </ImageBackground>
  188. </View>
  189. );
  190. }
  191. const styles = StyleSheet.create({
  192. container: { flex: 1, backgroundColor: '#1a1a2e' },
  193. background: { flex: 1 },
  194. header: {
  195. flexDirection: 'row',
  196. alignItems: 'center',
  197. justifyContent: 'space-between',
  198. paddingHorizontal: 10,
  199. paddingBottom: 10,
  200. position: 'absolute',
  201. top: 0,
  202. left: 0,
  203. right: 0,
  204. zIndex: 100,
  205. },
  206. backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
  207. backText: { color: '#fff', fontSize: 20 },
  208. title: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
  209. placeholder: { width: 40 },
  210. scrollView: { flex: 1 },
  211. // 规则按钮
  212. ruleBtn: { position: 'absolute', left: 0, zIndex: 99 },
  213. ruleBtnImg: { width: 39, height: 20 },
  214. // 标题
  215. titleBox: { alignItems: 'center', marginBottom: 10 },
  216. titleImg: { width: 288, height: 86 },
  217. // 轮播区域
  218. swiperBox: {
  219. flexDirection: 'row',
  220. alignItems: 'center',
  221. justifyContent: 'center',
  222. paddingHorizontal: 20,
  223. minHeight: 350,
  224. position: 'relative',
  225. },
  226. prevBtn: { position: 'absolute', left: 15, zIndex: 10 },
  227. nextBtn: { position: 'absolute', right: 15, zIndex: 10 },
  228. arrowImg: { width: 51, height: 49 },
  229. // 卡片
  230. cardBox: { alignItems: 'center' },
  231. card: { alignItems: 'center' },
  232. imgBox: {
  233. width: 215,
  234. height: 284,
  235. backgroundColor: '#FFF7D8',
  236. borderWidth: 4.5,
  237. borderColor: '#000',
  238. justifyContent: 'center',
  239. alignItems: 'center',
  240. position: 'relative',
  241. },
  242. spuImage: { width: 200, height: 260 },
  243. emptyText: { color: '#999', fontSize: 14 },
  244. remainingBox: {
  245. position: 'absolute',
  246. bottom: '15%',
  247. backgroundColor: '#fff',
  248. borderWidth: 3,
  249. borderColor: '#000',
  250. paddingHorizontal: 15,
  251. paddingVertical: 5,
  252. },
  253. remainingText: { fontSize: 13, fontWeight: 'bold', color: '#000' },
  254. cardName: {
  255. color: '#D0D0D0',
  256. fontSize: 12,
  257. marginTop: 13,
  258. textAlign: 'center',
  259. maxWidth: 200,
  260. },
  261. // 指示点
  262. dotsBox: { flexDirection: 'row', justifyContent: 'center', marginTop: 10 },
  263. dot: { width: 8, height: 8, borderRadius: 4, backgroundColor: '#666', marginHorizontal: 3 },
  264. dotActive: { backgroundColor: '#fff' },
  265. // 进度条
  266. progressSection: {
  267. flexDirection: 'row',
  268. alignItems: 'center',
  269. justifyContent: 'center',
  270. marginTop: 5,
  271. paddingHorizontal: 20,
  272. },
  273. progressLabel: { color: '#fff', fontSize: 12, width: 40 },
  274. progressBar: {
  275. flex: 1,
  276. height: 14,
  277. backgroundColor: '#FFEABE',
  278. borderWidth: 2,
  279. borderColor: '#000',
  280. marginHorizontal: 5,
  281. position: 'relative',
  282. },
  283. progressFill: {
  284. height: '100%',
  285. backgroundColor: '#FFAD00',
  286. borderRightWidth: 2,
  287. borderRightColor: '#000',
  288. },
  289. progressText: { color: '#fff', fontSize: 12, width: 40, textAlign: 'right' },
  290. // 材料添加区域
  291. addSection: {
  292. width: SCREEN_WIDTH - 20,
  293. height: 124,
  294. marginHorizontal: 10,
  295. marginTop: 10,
  296. paddingTop: 10,
  297. },
  298. addTitle: {
  299. color: '#fff',
  300. fontSize: 14,
  301. textAlign: 'center',
  302. fontWeight: '400',
  303. textShadowColor: '#000',
  304. textShadowOffset: { width: 1, height: 1 },
  305. textShadowRadius: 1,
  306. marginBottom: 7,
  307. },
  308. addMain: {
  309. flexDirection: 'row',
  310. alignItems: 'center',
  311. paddingHorizontal: 25,
  312. },
  313. addBtn: { marginRight: 10 },
  314. addBtnBg: {
  315. width: 42,
  316. height: 42,
  317. justifyContent: 'center',
  318. alignItems: 'center',
  319. paddingTop: 5,
  320. },
  321. addIcon: { width: 16, height: 16 },
  322. addBtnText: { fontSize: 12, color: '#000', fontWeight: '500' },
  323. addCenter: {
  324. flex: 1,
  325. height: 60,
  326. backgroundColor: '#FFFBEA',
  327. paddingTop: 7,
  328. paddingLeft: 20,
  329. },
  330. addScrollContent: { paddingRight: 10 },
  331. addItem: { position: 'relative', marginRight: 10 },
  332. addItemBg: { width: 42, height: 42, justifyContent: 'center', alignItems: 'center' },
  333. addItemImg: { width: '80%', height: '80%' },
  334. closeBtn: { position: 'absolute', right: 0, top: 0, width: 13, height: 13 },
  335. closeIcon: { width: '100%', height: '100%' },
  336. // 底部按钮
  337. bottomBtn: { alignItems: 'center', marginTop: -10 },
  338. bottomBtnBg: {
  339. width: 160,
  340. height: 60,
  341. justifyContent: 'center',
  342. alignItems: 'center',
  343. paddingTop: 4,
  344. },
  345. bottomBtnText: {
  346. color: '#fff',
  347. fontSize: 15,
  348. fontWeight: 'bold',
  349. textShadowColor: '#000',
  350. textShadowOffset: { width: 1, height: 1 },
  351. textShadowRadius: 1,
  352. },
  353. bottomTip: {
  354. color: 'rgba(255,255,255,0.67)',
  355. fontSize: 10,
  356. textAlign: 'center',
  357. marginTop: 5,
  358. },
  359. });