index.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { Stack, useRouter } from 'expo-router';
  2. import React, { useEffect, useState } from 'react';
  3. import { FlatList, ImageBackground, RefreshControl, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
  4. import services from '../../services/api';
  5. const TABS = [
  6. { title: '全部', value: '' },
  7. { title: '收入', value: 'IN' },
  8. { title: '支出', value: 'OUT' },
  9. ];
  10. // Define types
  11. interface TransactionItem {
  12. itemId: string;
  13. itemDesc: string;
  14. createTime: string;
  15. type: string;
  16. money: number;
  17. }
  18. export default function WalletScreen() {
  19. const router = useRouter();
  20. const [balance, setBalance] = useState('0.00');
  21. const [activeTab, setActiveTab] = useState(TABS[0]);
  22. const [transactions, setTransactions] = useState<TransactionItem[]>([]);
  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 fetchWalletInfo = async () => {
  28. try {
  29. const res = await services.wallet.info('CASH');
  30. if (res && res.balance) {
  31. setBalance(res.balance);
  32. }
  33. } catch (error) {
  34. console.error('Failed to fetch wallet info', error);
  35. }
  36. };
  37. const fetchTransactions = async (pageNum = 1, isRefresh = false) => {
  38. if (loading) return;
  39. setLoading(true);
  40. try {
  41. const res = await services.wallet.bill(pageNum, 20, 'CASH', activeTab.value);
  42. if (res && res.records) {
  43. if (isRefresh) {
  44. setTransactions(res.records);
  45. } else {
  46. setTransactions(prev => [...prev, ...res.records]);
  47. }
  48. setHasMore(res.records.length >= 20);
  49. setPage(pageNum);
  50. } else {
  51. if (isRefresh) setTransactions([]);
  52. setHasMore(false);
  53. }
  54. } catch (error) {
  55. console.error('Failed to fetch transactions', error);
  56. } finally {
  57. setLoading(false);
  58. setRefreshing(false);
  59. }
  60. };
  61. useEffect(() => {
  62. fetchWalletInfo();
  63. fetchTransactions(1, true);
  64. }, [activeTab]);
  65. const onRefresh = () => {
  66. setRefreshing(true);
  67. fetchWalletInfo();
  68. fetchTransactions(1, true);
  69. };
  70. const loadMore = () => {
  71. if (hasMore && !loading) {
  72. fetchTransactions(page + 1);
  73. }
  74. };
  75. const renderItem = ({ item }: { item: TransactionItem }) => (
  76. <View style={styles.cell}>
  77. <View>
  78. <Text style={styles.itemDesc}>{item.itemDesc}</Text>
  79. <Text style={styles.itemTime}>{item.createTime}</Text>
  80. </View>
  81. <Text style={[styles.itemAmount, item.type === 'IN' ? styles.amountIn : styles.amountOut]}>
  82. {item.type === 'IN' ? '+' : '-'}{item.money}
  83. </Text>
  84. </View>
  85. );
  86. return (
  87. <ImageBackground
  88. source={{ uri: 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart/mine/kaixinMineBg.png' }}
  89. style={styles.container}
  90. resizeMode="cover"
  91. >
  92. <Stack.Screen options={{
  93. title: '钱包',
  94. headerTitleStyle: { color: '#fff' },
  95. headerStyle: { backgroundColor: 'transparent' },
  96. headerTransparent: true,
  97. headerTintColor: '#fff',
  98. headerBackTitleVisible: false,
  99. headerBackTitle: '',
  100. }} />
  101. <SafeAreaView style={{ flex: 1, marginTop: 100 }}>
  102. {/* Info Card */}
  103. <View style={styles.infoCard}>
  104. <View style={styles.balanceContainer}>
  105. <Text style={styles.balanceAmount}>{balance}</Text>
  106. <Text style={styles.balanceLabel}>余额</Text>
  107. </View>
  108. <View style={styles.actionButtons}>
  109. <TouchableOpacity
  110. style={styles.btnWithdraw}
  111. onPress={() => router.push('/wallet/withdraw')}
  112. >
  113. <Text style={styles.btnWithdrawText}>我要提现</Text>
  114. </TouchableOpacity>
  115. <TouchableOpacity
  116. style={styles.btnRecharge}
  117. onPress={() => router.push('/wallet/recharge')}
  118. >
  119. <Text style={styles.btnRechargeText}>充值</Text>
  120. </TouchableOpacity>
  121. </View>
  122. </View>
  123. {/* Tabs */}
  124. <View style={styles.tabsContainer}>
  125. {TABS.map((tab, index) => (
  126. <TouchableOpacity
  127. key={index}
  128. style={styles.tabItem}
  129. onPress={() => setActiveTab(tab)}
  130. >
  131. <Text style={[styles.tabText, activeTab.value === tab.value && styles.activeTabText]}>
  132. {tab.title}
  133. </Text>
  134. </TouchableOpacity>
  135. ))}
  136. </View>
  137. {/* Transaction List */}
  138. <View style={styles.listContainer}>
  139. <FlatList
  140. data={transactions}
  141. renderItem={renderItem}
  142. keyExtractor={(item) => item.itemId || Math.random().toString()}
  143. contentContainerStyle={styles.listContent}
  144. refreshControl={
  145. <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#333" />
  146. }
  147. onEndReached={loadMore}
  148. onEndReachedThreshold={0.5}
  149. // ListFooterComponent={loading && !refreshing ? <ActivityIndicator style={{marginTop: 10}} /> : null}
  150. ListEmptyComponent={!loading ? <View style={styles.emptyContainer}><Text style={styles.emptyText}>暂无记录</Text></View> : null}
  151. />
  152. </View>
  153. </SafeAreaView>
  154. </ImageBackground>
  155. );
  156. }
  157. const styles = StyleSheet.create({
  158. container: {
  159. flex: 1,
  160. backgroundColor: '#f5f5f5',
  161. },
  162. infoCard: {
  163. marginHorizontal: 14,
  164. marginTop: 12,
  165. marginBottom: 15,
  166. borderRadius: 10,
  167. height: 139,
  168. backgroundColor: '#3d3f41',
  169. alignItems: 'center',
  170. justifyContent: 'center',
  171. },
  172. balanceContainer: {
  173. alignItems: 'center',
  174. marginTop: 10,
  175. },
  176. balanceAmount: {
  177. fontSize: 30,
  178. fontWeight: 'bold',
  179. color: '#fff',
  180. },
  181. balanceLabel: {
  182. fontSize: 12,
  183. color: 'rgba(255,255,255,0.8)',
  184. marginTop: 2,
  185. marginBottom: 15,
  186. },
  187. actionButtons: {
  188. flexDirection: 'row',
  189. justifyContent: 'space-around',
  190. width: '75%',
  191. },
  192. btnWithdraw: {
  193. width: 90,
  194. height: 32,
  195. borderRadius: 16,
  196. borderWidth: 1,
  197. borderColor: '#fff',
  198. justifyContent: 'center',
  199. alignItems: 'center',
  200. },
  201. btnWithdrawText: {
  202. color: '#fff',
  203. fontSize: 14,
  204. },
  205. btnRecharge: {
  206. width: 90,
  207. height: 32,
  208. borderRadius: 16,
  209. backgroundColor: '#fff',
  210. justifyContent: 'center',
  211. alignItems: 'center',
  212. },
  213. btnRechargeText: {
  214. color: '#3d3f41',
  215. fontSize: 14,
  216. },
  217. tabsContainer: {
  218. flexDirection: 'row',
  219. justifyContent: 'flex-start',
  220. paddingHorizontal: 15,
  221. marginBottom: 10,
  222. },
  223. tabItem: {
  224. width: '32%',
  225. paddingVertical: 10,
  226. alignItems: 'center',
  227. justifyContent: 'center',
  228. },
  229. tabText: {
  230. color: '#3d3f41',
  231. fontSize: 14,
  232. },
  233. activeTabText: {
  234. fontWeight: 'bold',
  235. fontSize: 15,
  236. // Add underline or color change if needed, logic in Uniapp was mostly bold
  237. },
  238. listContainer: {
  239. flex: 1,
  240. marginHorizontal: 14,
  241. marginBottom: 14,
  242. borderRadius: 10,
  243. backgroundColor: '#fff',
  244. overflow: 'hidden',
  245. },
  246. listContent: {
  247. paddingBottom: 20,
  248. },
  249. cell: {
  250. flexDirection: 'row',
  251. justifyContent: 'space-between',
  252. alignItems: 'center',
  253. paddingVertical: 18,
  254. paddingHorizontal: 15,
  255. borderBottomWidth: 1,
  256. borderBottomColor: '#eee',
  257. },
  258. itemDesc: {
  259. fontSize: 14,
  260. fontWeight: 'bold',
  261. color: '#666666',
  262. },
  263. itemTime: {
  264. fontSize: 10,
  265. color: '#999',
  266. marginTop: 4,
  267. },
  268. itemAmount: {
  269. fontSize: 16,
  270. },
  271. amountIn: {
  272. color: '#ff9600', // Uniapp color-theme
  273. },
  274. amountOut: {
  275. color: '#666666', // Uniapp color-1
  276. },
  277. emptyContainer: {
  278. padding: 60,
  279. alignItems: 'center',
  280. },
  281. emptyText: {
  282. color: '#999',
  283. },
  284. });