index.tsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import { Images } from '@/constants/images';
  2. import ServiceAward from '@/services/award';
  3. import { Ionicons } from '@expo/vector-icons';
  4. import { Stack, useRouter } from 'expo-router';
  5. import React, { useState } from 'react';
  6. import {
  7. ActivityIndicator,
  8. Dimensions,
  9. FlatList,
  10. Image,
  11. ImageBackground,
  12. StatusBar,
  13. StyleSheet,
  14. Text,
  15. TouchableOpacity,
  16. View,
  17. } from 'react-native';
  18. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  19. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  20. const LEVEL_MAP: any = {
  21. D: { title: '普通', color: '#666666' },
  22. C: { title: '隐藏', color: '#9745e6' },
  23. B: { title: '欧皇', color: '#ff0000' },
  24. A: { title: '超神', color: '#ffae00' },
  25. };
  26. export default function StoreScreen() {
  27. const router = useRouter();
  28. const insets = useSafeAreaInsets();
  29. const [list, setList] = useState<any[]>([]);
  30. const [loading, setLoading] = useState(false);
  31. const [tabIndex, setTabIndex] = useState(0);
  32. const [page, setPage] = useState(1);
  33. const [hasMore, setHasMore] = useState(true);
  34. const tabs = ['未使用', '保险柜', '已提货'];
  35. React.useEffect(() => {
  36. setPage(1);
  37. setList([]);
  38. setHasMore(true);
  39. loadData(1);
  40. }, [tabIndex]);
  41. const loadData = async (pageNum: number) => {
  42. if (!hasMore && pageNum > 1) return;
  43. try {
  44. if (pageNum === 1) setLoading(true);
  45. let res;
  46. if (tabIndex === 0) {
  47. // Not in safe, unused (status=0)
  48. res = await ServiceAward.getStore(pageNum, 20, 0); // Use smaller size for pagination demo
  49. } else if (tabIndex === 1) {
  50. // In safe, unused (status=0)
  51. res = await ServiceAward.getStore(pageNum, 20, 1);
  52. } else {
  53. // Picked up
  54. res = await ServiceAward.getTakeList(pageNum, 20);
  55. }
  56. const records = Array.isArray(res) ? res : (res?.records || []);
  57. if (records.length < 20) {
  58. setHasMore(false);
  59. }
  60. if (pageNum === 1) {
  61. setList(records);
  62. } else {
  63. setList(prev => [...prev, ...records]);
  64. }
  65. } catch (e) {
  66. console.error(e);
  67. } finally {
  68. setLoading(false);
  69. }
  70. };
  71. const handleLoadMore = () => {
  72. if (!loading && hasMore) {
  73. const nextPage = page + 1;
  74. setPage(nextPage);
  75. loadData(nextPage);
  76. }
  77. };
  78. const handleLock = async (item: any) => {
  79. // Implement lock/unlock logic if needed
  80. };
  81. const renderItem = ({ item }: { item: any }) => (
  82. <ImageBackground
  83. source={{ uri: Images.mine.storeItemBg }}
  84. style={styles.cell}
  85. resizeMode="stretch"
  86. >
  87. <View style={styles.cellHeader}>
  88. <View style={styles.headerLeft}>
  89. <View style={[styles.checkBox]} />
  90. <Text style={[styles.levelTitle, { color: LEVEL_MAP[item.level]?.color || '#333' }]}>
  91. {LEVEL_MAP[item.level]?.title || '未知'}
  92. </Text>
  93. </View>
  94. <TouchableOpacity style={styles.lockBox} onPress={() => handleLock(item)}>
  95. <Text style={styles.lockText}>{item.safeFlag !== 1 ? '锁定' : '解锁'}</Text>
  96. <Image
  97. source={{ uri: item.safeFlag !== 1 ? Images.mine.lock : Images.mine.unlock }}
  98. style={styles.lockIcon}
  99. />
  100. </TouchableOpacity>
  101. </View>
  102. <View style={styles.cellBody}>
  103. <ImageBackground
  104. source={{ uri: Images.mine.storeGoodsImgBg }}
  105. style={styles.goodsImgBg}
  106. >
  107. <Image source={{ uri: item.spu?.cover }} style={styles.goodsImg} resizeMode="contain" />
  108. </ImageBackground>
  109. <View style={styles.goodsInfo}>
  110. <Text style={styles.goodsName} numberOfLines={2}>{item.spu?.name}</Text>
  111. <Text style={styles.goodsSource}>
  112. 从{item.fromRelationType === 'LUCK' ? '奖池' : '其他'}获得
  113. </Text>
  114. </View>
  115. </View>
  116. </ImageBackground>
  117. );
  118. return (
  119. <View style={styles.container}>
  120. <Stack.Screen options={{ headerShown: false }} />
  121. <StatusBar barStyle="light-content" />
  122. <View style={[styles.header, { paddingTop: insets.top }]}>
  123. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  124. <Ionicons name="chevron-back" size={24} color="#fff" />
  125. </TouchableOpacity>
  126. <Text style={styles.title}>仓库</Text>
  127. </View>
  128. <ImageBackground
  129. source={{ uri: Images.mine.kaixinMineBg }}
  130. style={styles.background}
  131. resizeMode="cover"
  132. >
  133. <Image
  134. source={{ uri: Images.mine.kaixinMineHeadBg }}
  135. style={styles.headerBg}
  136. resizeMode="cover"
  137. />
  138. <View style={[styles.content, { paddingTop: insets.top + 50 }]}>
  139. {/* Tabs */}
  140. <View style={styles.tabs}>
  141. {tabs.map((tab, index) => (
  142. <TouchableOpacity
  143. key={index}
  144. style={[styles.tabItem, tabIndex === index && styles.tabItemActive]}
  145. onPress={() => setTabIndex(index)}
  146. >
  147. <Text style={[styles.tabText, tabIndex === index && styles.tabTextActive]}>{tab}</Text>
  148. {tabIndex === index && <View style={styles.tabLine} />}
  149. </TouchableOpacity>
  150. ))}
  151. </View>
  152. <FlatList
  153. data={list}
  154. renderItem={renderItem}
  155. keyExtractor={(item, index) => index.toString()}
  156. contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 100 }}
  157. onEndReached={handleLoadMore}
  158. onEndReachedThreshold={0.1}
  159. ListFooterComponent={
  160. loading && list.length > 0 ? (
  161. <ActivityIndicator color="#fff" style={{ marginVertical: 10 }} />
  162. ) : null
  163. }
  164. ListEmptyComponent={
  165. !loading ? (
  166. <View style={styles.emptyBox}>
  167. <Text style={styles.emptyText}>暂无物品</Text>
  168. </View>
  169. ) : null
  170. }
  171. />
  172. </View>
  173. </ImageBackground>
  174. </View>
  175. );
  176. }
  177. const styles = StyleSheet.create({
  178. container: {
  179. flex: 1,
  180. backgroundColor: '#1a1a2e',
  181. },
  182. header: {
  183. position: 'absolute',
  184. top: 0,
  185. left: 0,
  186. right: 0,
  187. zIndex: 100,
  188. alignItems: 'center',
  189. paddingBottom: 10,
  190. },
  191. headerBg: {
  192. position: 'absolute',
  193. top: 0,
  194. left: 0,
  195. width: '100%',
  196. height: 260,
  197. },
  198. backBtn: {
  199. position: 'absolute',
  200. left: 10,
  201. bottom: 10,
  202. zIndex: 101,
  203. },
  204. title: {
  205. color: '#fff',
  206. fontSize: 16,
  207. fontWeight: 'bold',
  208. },
  209. background: {
  210. flex: 1,
  211. width: '100%',
  212. height: '100%',
  213. },
  214. content: {
  215. flex: 1,
  216. },
  217. tabs: {
  218. flexDirection: 'row',
  219. justifyContent: 'space-around',
  220. marginBottom: 10,
  221. },
  222. tabItem: {
  223. paddingVertical: 10,
  224. paddingHorizontal: 10,
  225. alignItems: 'center',
  226. },
  227. tabItemActive: {},
  228. tabText: {
  229. color: '#aaa',
  230. fontSize: 14,
  231. },
  232. tabTextActive: {
  233. color: '#fff',
  234. fontWeight: 'bold',
  235. fontSize: 16,
  236. },
  237. tabLine: {
  238. width: 20,
  239. height: 3,
  240. backgroundColor: '#fff',
  241. marginTop: 5,
  242. borderRadius: 2,
  243. },
  244. cell: {
  245. width: '100%',
  246. height: 154,
  247. marginBottom: 10,
  248. padding: 15,
  249. },
  250. cellHeader: {
  251. flexDirection: 'row',
  252. justifyContent: 'space-between',
  253. alignItems: 'center',
  254. borderBottomWidth: 1,
  255. borderBottomColor: 'rgba(0,0,0,0.1)',
  256. paddingBottom: 10,
  257. marginBottom: 10,
  258. },
  259. headerLeft: {
  260. flexDirection: 'row',
  261. alignItems: 'center',
  262. },
  263. checkBox: {
  264. width: 16,
  265. height: 16,
  266. borderWidth: 1,
  267. borderColor: '#999',
  268. marginRight: 10,
  269. backgroundColor: '#fff',
  270. },
  271. levelTitle: {
  272. fontSize: 16,
  273. fontWeight: 'bold',
  274. },
  275. lockBox: {
  276. flexDirection: 'row',
  277. alignItems: 'center',
  278. },
  279. lockText: {
  280. fontSize: 12,
  281. color: '#666',
  282. },
  283. lockIcon: {
  284. width: 16,
  285. height: 16,
  286. marginLeft: 5,
  287. },
  288. cellBody: {
  289. flexDirection: 'row',
  290. },
  291. goodsImgBg: {
  292. width: 65,
  293. height: 65,
  294. justifyContent: 'center',
  295. alignItems: 'center',
  296. marginRight: 10,
  297. },
  298. goodsImg: {
  299. width: 60,
  300. height: 60,
  301. },
  302. goodsInfo: {
  303. flex: 1,
  304. justifyContent: 'space-between',
  305. },
  306. goodsName: {
  307. fontSize: 14,
  308. color: '#333',
  309. fontWeight: 'bold',
  310. },
  311. goodsSource: {
  312. fontSize: 12,
  313. color: '#999',
  314. },
  315. emptyBox: {
  316. marginTop: 100,
  317. alignItems: 'center',
  318. },
  319. emptyText: {
  320. color: '#999',
  321. },
  322. });