index.tsx 8.1 KB

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