index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import { Images } from '@/constants/images';
  2. import ServiceWallet from '@/services/wallet';
  3. import { Ionicons } from '@expo/vector-icons';
  4. import { Stack, useRouter } from 'expo-router';
  5. import React, { useEffect, useState } from 'react';
  6. import {
  7. ActivityIndicator,
  8. Dimensions,
  9. ImageBackground,
  10. ScrollView,
  11. StatusBar,
  12. StyleSheet,
  13. Text,
  14. TouchableOpacity,
  15. View
  16. } from 'react-native';
  17. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  18. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  19. const TABS = [
  20. { title: '全部类型', value: '' },
  21. { title: '收入', value: 'IN' },
  22. { title: '支出', value: 'OUT' },
  23. ];
  24. export default function MagicScreen() {
  25. const router = useRouter();
  26. const insets = useSafeAreaInsets();
  27. const [data, setData] = useState<any>(null); // Balance info
  28. const [list, setList] = useState<any[]>([]);
  29. const [loading, setLoading] = useState(false);
  30. const [tabIndex, setTabIndex] = useState(0);
  31. useEffect(() => {
  32. loadInfo();
  33. loadList();
  34. }, [tabIndex]);
  35. const loadInfo = async () => {
  36. try {
  37. const res = await ServiceWallet.info('MAGIC');
  38. setData(res);
  39. } catch (e) {
  40. console.error(e);
  41. }
  42. };
  43. const loadList = async () => {
  44. try {
  45. setLoading(true);
  46. // bill(current, size, walletType, type)
  47. const res = await ServiceWallet.bill(1, 100, 'MAGIC', TABS[tabIndex].value);
  48. const records = Array.isArray(res) ? res : (res?.records || []);
  49. setList(records);
  50. } catch (e) {
  51. console.error(e);
  52. } finally {
  53. setLoading(false);
  54. }
  55. };
  56. const handleTabChange = () => {
  57. const nextIndex = (tabIndex + 1) % TABS.length;
  58. setTabIndex(nextIndex);
  59. };
  60. return (
  61. <View style={styles.container}>
  62. <Stack.Screen options={{ headerShown: false }} />
  63. <StatusBar barStyle="light-content" />
  64. <View style={[styles.header, { paddingTop: insets.top }]}>
  65. <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
  66. <Ionicons name="chevron-back" size={24} color="#fff" />
  67. </TouchableOpacity>
  68. <Text style={styles.title}>果实</Text>
  69. </View>
  70. <ImageBackground
  71. source={{ uri: Images.mine.kaixinMineBg }}
  72. style={styles.background}
  73. resizeMode="cover"
  74. >
  75. <ImageBackground
  76. source={{ uri: Images.mine.kaixinMineHeadBg }}
  77. style={styles.headerBg}
  78. resizeMode="cover"
  79. />
  80. <ScrollView
  81. style={styles.scrollView}
  82. contentContainerStyle={{ paddingTop: insets.top + 50, paddingBottom: 50 }}
  83. >
  84. {/* Stats Box - visually mimicking the legacy StoneBox */}
  85. <View style={styles.stoneBox}>
  86. <View style={styles.infoRow}>
  87. <View style={styles.infoCol}>
  88. <Text style={styles.num}>{data?.balance || '0'}</Text>
  89. <Text style={styles.label}>可用</Text>
  90. </View>
  91. <View style={styles.infoCol}>
  92. <Text style={styles.num}>{data?.frozen || '0'}</Text>
  93. <Text style={styles.label}>冻结</Text>
  94. </View>
  95. </View>
  96. </View>
  97. <View style={styles.listSection}>
  98. <ImageBackground
  99. source={{ uri: Images.mine.stoneImage }}
  100. style={styles.listTitleBg}
  101. resizeMode="stretch"
  102. >
  103. <Text style={styles.listTitleText}>果实明细</Text>
  104. <TouchableOpacity style={styles.pickerBox} onPress={handleTabChange}>
  105. <ImageBackground
  106. source={{ uri: Images.mine.magicTypeBg }}
  107. style={styles.pickerBg}
  108. resizeMode="stretch"
  109. >
  110. <Text style={styles.pickerText}>{TABS[tabIndex].title}</Text>
  111. <Ionicons name="caret-down" size={12} color="#000" style={{ marginLeft: 4 }} />
  112. </ImageBackground>
  113. </TouchableOpacity>
  114. </ImageBackground>
  115. <View style={styles.listContainer}>
  116. {loading && list.length === 0 ? (
  117. <ActivityIndicator color="#fff" style={{ marginTop: 20 }} />
  118. ) : list.length > 0 ? (
  119. list.map((item, index) => (
  120. <View key={index} style={styles.cell}>
  121. <View style={styles.cellRow}>
  122. <Text style={styles.cellDesc}>{item.itemDesc}</Text>
  123. <Text style={[styles.cellAmount, { color: item.type === 'IN' ? '#ff0000' : '#000' }]}>
  124. {item.type === 'IN' ? '+' : '-'}{item.money}
  125. </Text>
  126. </View>
  127. <Text style={styles.cellTime}>{item.createTime}</Text>
  128. </View>
  129. ))
  130. ) : (
  131. <View style={styles.emptyBox}>
  132. <Text style={styles.emptyText}>暂无记录</Text>
  133. </View>
  134. )}
  135. </View>
  136. </View>
  137. </ScrollView>
  138. </ImageBackground>
  139. </View>
  140. );
  141. }
  142. const styles = StyleSheet.create({
  143. container: {
  144. flex: 1,
  145. backgroundColor: '#1a1a2e',
  146. },
  147. header: {
  148. position: 'absolute',
  149. top: 0,
  150. left: 0,
  151. right: 0,
  152. zIndex: 100,
  153. alignItems: 'center',
  154. paddingBottom: 10,
  155. },
  156. backBtn: {
  157. position: 'absolute',
  158. left: 10,
  159. bottom: 10,
  160. zIndex: 101,
  161. },
  162. title: {
  163. color: '#fff',
  164. fontSize: 16,
  165. fontWeight: 'bold',
  166. },
  167. background: {
  168. flex: 1,
  169. width: '100%',
  170. height: '100%',
  171. },
  172. headerBg: {
  173. position: 'absolute',
  174. width: '100%',
  175. height: 240, // 480rpx / 2 = 240
  176. top: 0,
  177. left: 0,
  178. },
  179. scrollView: {
  180. flex: 1,
  181. },
  182. stoneBox: {
  183. width: 310, // 620rpx / 2
  184. height: 100, // 200rpx / 2
  185. marginHorizontal: 'auto',
  186. alignSelf: 'center',
  187. justifyContent: 'center',
  188. // No background image as per legacy analysis, relies on transparency over headerBg?
  189. // Wait, legacy publicHeaderBg height is 480rpx (240px). StoneBox is inside wrapper padding 200rpx top.
  190. // So it sits on top of headerBg.
  191. },
  192. infoRow: {
  193. flexDirection: 'row',
  194. justifyContent: 'space-around',
  195. alignItems: 'center',
  196. },
  197. infoCol: {
  198. alignItems: 'center',
  199. },
  200. num: {
  201. fontSize: 22,
  202. fontWeight: 'bold',
  203. color: '#fff',
  204. },
  205. label: {
  206. fontSize: 14,
  207. color: '#fff',
  208. marginTop: 4,
  209. },
  210. listSection: {
  211. paddingHorizontal: 16,
  212. marginTop: 10,
  213. },
  214. listTitleBg: {
  215. width: '100%',
  216. height: 63, // 126rpx / 2
  217. justifyContent: 'center',
  218. alignItems: 'center',
  219. flexDirection: 'row',
  220. position: 'relative',
  221. zIndex: 10,
  222. },
  223. listTitleText: {
  224. fontSize: 16,
  225. fontWeight: 'bold',
  226. color: '#333',
  227. },
  228. pickerBox: {
  229. position: 'absolute',
  230. right: 13,
  231. top: 20, // Adjust vertically
  232. },
  233. pickerBg: {
  234. flexDirection: 'row',
  235. alignItems: 'center',
  236. paddingHorizontal: 10,
  237. paddingVertical: 5,
  238. minWidth: 60,
  239. justifyContent: 'center',
  240. },
  241. pickerText: {
  242. fontSize: 12,
  243. color: 'rgba(0,0,0,0.85)',
  244. },
  245. listContainer: {
  246. backgroundColor: '#fff', // Legacy wrapper background is transparent, list items have no bg?
  247. // Legacy list items have border-bottom.
  248. // Wait, legacy wrapper has background-size: 100% 100%, likely kaixinMineBg.
  249. // Cells are transparent?
  250. // Legacy: .cell border-bottom: 2rpx solid #A2A2A2.
  251. // Font color #000.
  252. // So list should probably be on a white-ish background or transparent?
  253. // Looking at screenshot or legacy, if text is black, background must be light.
  254. // But kaixinMineBg is usually dark/blue.
  255. // Ah, kaixinMineBg (new images) might be different.
  256. // Let's assume transparent cell on whatever background.
  257. // But text color is #000 in legacy css!
  258. // Maybe headerBg covers the whole page? No.
  259. // I'll add a white background to list container to be safe, or check detailed legacy logic.
  260. // Legacy wrapper min-height 100vh.
  261. // I'll stick to transparency but maybe change text color if needed.
  262. // Legacy text: color: #000.
  263. marginTop: -10, // Overlap slightly or just separate
  264. paddingTop: 10,
  265. paddingHorizontal: 10,
  266. paddingBottom: 20,
  267. borderRadius: 8,
  268. backgroundColor: '#fff', // Safest bet for "black text" readability
  269. minHeight: 300,
  270. },
  271. cell: {
  272. borderBottomWidth: 1,
  273. borderBottomColor: '#eee',
  274. paddingVertical: 12,
  275. },
  276. cellRow: {
  277. flexDirection: 'row',
  278. justifyContent: 'space-between',
  279. marginBottom: 6,
  280. },
  281. cellDesc: {
  282. fontSize: 14,
  283. fontWeight: 'bold',
  284. color: '#000',
  285. flex: 1,
  286. },
  287. cellAmount: {
  288. fontSize: 14,
  289. fontWeight: 'bold',
  290. },
  291. cellTime: {
  292. fontSize: 12,
  293. color: '#666',
  294. },
  295. emptyBox: {
  296. alignItems: 'center',
  297. marginTop: 40,
  298. },
  299. emptyText: {
  300. color: '#999',
  301. },
  302. });