store_choose.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import { Ionicons } from '@expo/vector-icons';
  2. import { useRouter } from 'expo-router';
  3. import React, { useCallback, useEffect, useState } from 'react';
  4. import {
  5. ActivityIndicator,
  6. FlatList,
  7. Image,
  8. ImageBackground,
  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 { getStore } from '@/services/award';
  17. import event from '@/utils/event';
  18. const StoreChooseScreen = () => {
  19. const router = useRouter();
  20. const insets = useSafeAreaInsets();
  21. const [list, setList] = useState<any[]>([]);
  22. const [selectedIds, setSelectedIds] = useState<string[]>([]);
  23. const [loading, setLoading] = useState(false);
  24. const [refreshing, setRefreshing] = useState(false);
  25. const [page, setPage] = useState(1);
  26. const [hasMore, setHasMore] = useState(true);
  27. const [tabIndex, setTabIndex] = useState(0);
  28. const tabs = [
  29. { title: '全部', value: '' },
  30. { title: '普通', value: 'D' },
  31. { title: '隐藏', value: 'C' },
  32. { title: '欧皇', value: 'B' },
  33. { title: '超神', value: 'A' },
  34. ];
  35. const loadData = useCallback(async (isRefresh = false) => {
  36. if (loading) return;
  37. const curPage = isRefresh ? 1 : page;
  38. setLoading(true);
  39. console.log('Fetching store data:', curPage, tabs[tabIndex].value);
  40. try {
  41. const data = await getStore(curPage, 20, 0, tabs[tabIndex].value);
  42. console.log('Store data received:', data);
  43. let records = [];
  44. if (Array.isArray(data)) {
  45. records = data;
  46. } else if (data && data.records) {
  47. records = data.records;
  48. }
  49. console.log('Parsed records length:', records.length);
  50. if (records.length > 0) {
  51. setList(prev => (isRefresh ? records : [...prev, ...records]));
  52. setHasMore(records.length === 20); // Assuming page size is 20
  53. setPage(curPage + 1);
  54. } else {
  55. setList(prev => (isRefresh ? [] : prev));
  56. setHasMore(false);
  57. }
  58. } catch (err) {
  59. console.error('Fetch store error:', err);
  60. // Alert.alert('Error', '加载数据失败'); // Optional: show alert
  61. } finally {
  62. setLoading(false);
  63. setRefreshing(false);
  64. }
  65. }, [loading, page, tabIndex]);
  66. useEffect(() => {
  67. loadData(true);
  68. }, [tabIndex]);
  69. const onRefresh = () => {
  70. setRefreshing(true);
  71. loadData(true);
  72. };
  73. const onEndReached = () => {
  74. if (hasMore && !loading) {
  75. loadData();
  76. }
  77. };
  78. const toggleSelect = (id: string) => {
  79. setSelectedIds(prev =>
  80. prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
  81. );
  82. };
  83. const handleConfirm = () => {
  84. if (selectedIds.length === 0) {
  85. router.back();
  86. return;
  87. }
  88. const selectedGoods = list.filter(item => selectedIds.includes(item.id));
  89. event.emit(event.keys.STORE_CHOOSE, selectedGoods);
  90. router.back();
  91. };
  92. const handleSelectAll = () => {
  93. if (list.length === 0) return;
  94. const allSelected = list.every(item => selectedIds.includes(item.id));
  95. if (allSelected) {
  96. setSelectedIds([]);
  97. } else {
  98. const allIds = list.map(item => item.id);
  99. setSelectedIds(allIds);
  100. }
  101. };
  102. const renderItem = ({ item }: { item: any }) => {
  103. const isSelected = selectedIds.includes(item.id);
  104. return (
  105. <TouchableOpacity
  106. style={styles.card}
  107. onPress={() => toggleSelect(item.id)}
  108. >
  109. <Image source={{ uri: item.spu.cover }} style={styles.goodsImg} resizeMode="contain" />
  110. <View style={styles.info}>
  111. <Text style={styles.goodsName} numberOfLines={2}>{item.spu.name}</Text>
  112. <View style={styles.tagRow}>
  113. <Text style={styles.magicText}>{item.magicAmount} 果实</Text>
  114. </View>
  115. </View>
  116. <View style={[styles.checkbox, isSelected && styles.checkboxSelected]}>
  117. {isSelected && <Ionicons name="checkmark" size={12} color="#000" />}
  118. </View>
  119. </TouchableOpacity>
  120. );
  121. };
  122. return (
  123. <View style={styles.container}>
  124. <ImageBackground
  125. source={{ uri: Images.mine.kaixinMineBg }}
  126. style={styles.background}
  127. resizeMode="cover"
  128. >
  129. <View style={[styles.header, { paddingTop: insets.top }]}>
  130. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  131. <Ionicons name="chevron-back" size={24} color="#fff" />
  132. </TouchableOpacity>
  133. <Text style={styles.title}>选择商品</Text>
  134. <View style={styles.placeholder} />
  135. </View>
  136. <View style={styles.tabBar}>
  137. {tabs.map((tab, index) => (
  138. <TouchableOpacity
  139. key={tab.value}
  140. style={[styles.tabItem, tabIndex === index && styles.tabItemActive]}
  141. onPress={() => setTabIndex(index)}
  142. >
  143. <Text style={[styles.tabText, tabIndex === index && styles.tabTextActive]}>
  144. {tab.title}
  145. </Text>
  146. </TouchableOpacity>
  147. ))}
  148. </View>
  149. <FlatList
  150. data={list}
  151. renderItem={renderItem}
  152. keyExtractor={(item) => item.id.toString()}
  153. contentContainerStyle={styles.listContent}
  154. onRefresh={onRefresh}
  155. refreshing={refreshing}
  156. onEndReached={onEndReached}
  157. onEndReachedThreshold={0.5}
  158. ListEmptyComponent={
  159. !loading ? (
  160. <View style={styles.emptyContainer as any}>
  161. <Text style={styles.emptyText as any}>暂无商品</Text>
  162. </View>
  163. ) : null
  164. }
  165. ListFooterComponent={() => loading && (
  166. <ActivityIndicator size="small" color="#fff" style={{ marginVertical: 20 }} />
  167. )}
  168. />
  169. <View style={[styles.footer as any, { paddingBottom: Math.max(insets.bottom, 20) }]}>
  170. <TouchableOpacity style={styles.selectAllBtn} onPress={handleSelectAll}>
  171. <Image
  172. source={{ uri: list.length > 0 && selectedIds.length === list.length ? Images.mine.checkAll : Images.mine.checkAll }}
  173. style={[styles.selectAllIcon, { opacity: list.length > 0 && selectedIds.length === list.length ? 1 : 0.5 }]}
  174. resizeMode="contain"
  175. />
  176. </TouchableOpacity>
  177. <TouchableOpacity style={styles.submitBtn as any} onPress={handleConfirm}>
  178. <ImageBackground
  179. source={{ uri: Images.common.butBgL }}
  180. style={styles.submitBtnBg as any}
  181. resizeMode="stretch"
  182. >
  183. <Text style={styles.submitBtnText}>确认选择 ({selectedIds.length})</Text>
  184. </ImageBackground>
  185. </TouchableOpacity>
  186. </View>
  187. </ImageBackground>
  188. </View>
  189. );
  190. };
  191. const styles = StyleSheet.create({
  192. container: {
  193. flex: 1,
  194. },
  195. background: {
  196. flex: 1,
  197. },
  198. header: {
  199. flexDirection: 'row',
  200. alignItems: 'center',
  201. justifyContent: 'space-between',
  202. paddingHorizontal: 15,
  203. height: 90,
  204. },
  205. backBtn: {
  206. width: 40,
  207. height: 40,
  208. justifyContent: 'center',
  209. },
  210. title: {
  211. fontSize: 18,
  212. fontWeight: 'bold',
  213. color: '#fff',
  214. },
  215. placeholder: {
  216. width: 40,
  217. },
  218. tabBar: {
  219. flexDirection: 'row',
  220. justifyContent: 'space-around',
  221. paddingVertical: 10,
  222. backgroundColor: 'rgba(0,0,0,0.3)',
  223. },
  224. tabItem: {
  225. paddingVertical: 6,
  226. paddingHorizontal: 12,
  227. borderRadius: 15,
  228. },
  229. tabItemActive: {
  230. backgroundColor: 'rgba(248, 214, 104, 0.2)',
  231. },
  232. tabText: {
  233. color: '#ccc',
  234. fontSize: 14,
  235. },
  236. tabTextActive: {
  237. color: '#F8D668',
  238. fontWeight: 'bold',
  239. },
  240. listContent: {
  241. padding: 15,
  242. paddingBottom: 100,
  243. },
  244. card: {
  245. flexDirection: 'row',
  246. backgroundColor: '#fff',
  247. borderRadius: 8,
  248. padding: 10,
  249. marginBottom: 12,
  250. alignItems: 'center',
  251. position: 'relative',
  252. },
  253. goodsImg: {
  254. width: 80,
  255. height: 80,
  256. borderRadius: 4,
  257. },
  258. info: {
  259. flex: 1,
  260. marginLeft: 12,
  261. height: 80,
  262. justifyContent: 'space-around',
  263. },
  264. goodsName: {
  265. fontSize: 14,
  266. color: '#333',
  267. fontWeight: 'bold',
  268. },
  269. tagRow: {
  270. flexDirection: 'row',
  271. alignItems: 'center',
  272. },
  273. magicText: {
  274. color: '#666',
  275. fontSize: 12,
  276. },
  277. checkbox: {
  278. width: 20,
  279. height: 20,
  280. borderRadius: 10,
  281. borderWidth: 1,
  282. borderColor: '#ddd',
  283. justifyContent: 'center',
  284. alignItems: 'center',
  285. position: 'absolute',
  286. right: 10,
  287. top: 10,
  288. },
  289. checkboxSelected: {
  290. backgroundColor: '#F8D668',
  291. borderColor: '#F8D668',
  292. },
  293. footer: {
  294. position: 'absolute',
  295. bottom: 0,
  296. left: 0,
  297. right: 0,
  298. alignItems: 'center',
  299. paddingTop: 10,
  300. backgroundColor: 'rgba(0,0,0,0.5)',
  301. },
  302. submitBtn: {
  303. width: 240,
  304. height: 60,
  305. },
  306. submitBtnBg: {
  307. width: '100%',
  308. height: '100%',
  309. justifyContent: 'center',
  310. alignItems: 'center',
  311. },
  312. submitBtnText: {
  313. fontSize: 15,
  314. fontWeight: '800',
  315. color: '#fff',
  316. },
  317. emptyContainer: {
  318. alignItems: 'center',
  319. paddingTop: 100,
  320. },
  321. emptyText: {
  322. color: '#999',
  323. fontSize: 14,
  324. },
  325. selectAllBtn: {
  326. position: 'absolute',
  327. top: -50,
  328. right: 20,
  329. flexDirection: 'row',
  330. alignItems: 'center',
  331. },
  332. selectAllIcon: {
  333. width: 30,
  334. height: 30,
  335. marginRight: 5,
  336. },
  337. selectAllText: {
  338. color: '#fff',
  339. fontSize: 14,
  340. fontWeight: 'bold',
  341. },
  342. });
  343. export default StoreChooseScreen;