|
@@ -0,0 +1,315 @@
|
|
|
|
|
+import { Images } from '@/constants/images';
|
|
|
|
|
+import ServiceWallet from '@/services/wallet';
|
|
|
|
|
+import { Ionicons } from '@expo/vector-icons';
|
|
|
|
|
+import { Stack, useRouter } from 'expo-router';
|
|
|
|
|
+import React, { useEffect, useState } from 'react';
|
|
|
|
|
+import {
|
|
|
|
|
+ ActivityIndicator,
|
|
|
|
|
+ Dimensions,
|
|
|
|
|
+ ImageBackground,
|
|
|
|
|
+ ScrollView,
|
|
|
|
|
+ StatusBar,
|
|
|
|
|
+ StyleSheet,
|
|
|
|
|
+ Text,
|
|
|
|
|
+ TouchableOpacity,
|
|
|
|
|
+ View
|
|
|
|
|
+} from 'react-native';
|
|
|
|
|
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
|
+
|
|
|
|
|
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
|
|
|
+
|
|
|
|
|
+const TABS = [
|
|
|
|
|
+ { title: '全部类型', value: '' },
|
|
|
|
|
+ { title: '收入', value: 'IN' },
|
|
|
|
|
+ { title: '支出', value: 'OUT' },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+export default function MagicScreen() {
|
|
|
|
|
+ const router = useRouter();
|
|
|
|
|
+ const insets = useSafeAreaInsets();
|
|
|
|
|
+ const [data, setData] = useState<any>(null); // Balance info
|
|
|
|
|
+ const [list, setList] = useState<any[]>([]);
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+ const [tabIndex, setTabIndex] = useState(0);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ loadInfo();
|
|
|
|
|
+ loadList();
|
|
|
|
|
+ }, [tabIndex]);
|
|
|
|
|
+
|
|
|
|
|
+ const loadInfo = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await ServiceWallet.info('MAGIC');
|
|
|
|
|
+ setData(res);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const loadList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ setLoading(true);
|
|
|
|
|
+ // bill(current, size, walletType, type)
|
|
|
|
|
+ const res = await ServiceWallet.bill(1, 100, 'MAGIC', TABS[tabIndex].value);
|
|
|
|
|
+ const records = Array.isArray(res) ? res : (res?.records || []);
|
|
|
|
|
+ setList(records);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleTabChange = () => {
|
|
|
|
|
+ const nextIndex = (tabIndex + 1) % TABS.length;
|
|
|
|
|
+ setTabIndex(nextIndex);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View style={styles.container}>
|
|
|
|
|
+ <Stack.Screen options={{ headerShown: false }} />
|
|
|
|
|
+ <StatusBar barStyle="light-content" />
|
|
|
|
|
+ <View style={[styles.header, { paddingTop: insets.top }]}>
|
|
|
|
|
+ <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
|
|
|
|
|
+ <Ionicons name="chevron-back" size={24} color="#fff" />
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ <Text style={styles.title}>果实</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <ImageBackground
|
|
|
|
|
+ source={{ uri: Images.mine.kaixinMineBg }}
|
|
|
|
|
+ style={styles.background}
|
|
|
|
|
+ resizeMode="cover"
|
|
|
|
|
+ >
|
|
|
|
|
+ <ImageBackground
|
|
|
|
|
+ source={{ uri: Images.mine.kaixinMineHeadBg }}
|
|
|
|
|
+ style={styles.headerBg}
|
|
|
|
|
+ resizeMode="cover"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <ScrollView
|
|
|
|
|
+ style={styles.scrollView}
|
|
|
|
|
+ contentContainerStyle={{ paddingTop: insets.top + 50, paddingBottom: 50 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {/* Stats Box - visually mimicking the legacy StoneBox */}
|
|
|
|
|
+ <View style={styles.stoneBox}>
|
|
|
|
|
+ <View style={styles.infoRow}>
|
|
|
|
|
+ <View style={styles.infoCol}>
|
|
|
|
|
+ <Text style={styles.num}>{data?.balance || '0'}</Text>
|
|
|
|
|
+ <Text style={styles.label}>可用</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View style={styles.infoCol}>
|
|
|
|
|
+ <Text style={styles.num}>{data?.frozen || '0'}</Text>
|
|
|
|
|
+ <Text style={styles.label}>冻结</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View style={styles.listSection}>
|
|
|
|
|
+ <ImageBackground
|
|
|
|
|
+ source={{ uri: Images.mine.stoneImage }}
|
|
|
|
|
+ style={styles.listTitleBg}
|
|
|
|
|
+ resizeMode="stretch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.listTitleText}>果实明细</Text>
|
|
|
|
|
+ <TouchableOpacity style={styles.pickerBox} onPress={handleTabChange}>
|
|
|
|
|
+ <ImageBackground
|
|
|
|
|
+ source={{ uri: Images.mine.magicTypeBg }}
|
|
|
|
|
+ style={styles.pickerBg}
|
|
|
|
|
+ resizeMode="stretch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.pickerText}>{TABS[tabIndex].title}</Text>
|
|
|
|
|
+ <Ionicons name="caret-down" size={12} color="#000" style={{ marginLeft: 4 }} />
|
|
|
|
|
+ </ImageBackground>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ </ImageBackground>
|
|
|
|
|
+
|
|
|
|
|
+ <View style={styles.listContainer}>
|
|
|
|
|
+ {loading && list.length === 0 ? (
|
|
|
|
|
+ <ActivityIndicator color="#fff" style={{ marginTop: 20 }} />
|
|
|
|
|
+ ) : list.length > 0 ? (
|
|
|
|
|
+ list.map((item, index) => (
|
|
|
|
|
+ <View key={index} style={styles.cell}>
|
|
|
|
|
+ <View style={styles.cellRow}>
|
|
|
|
|
+ <Text style={styles.cellDesc}>{item.itemDesc}</Text>
|
|
|
|
|
+ <Text style={[styles.cellAmount, { color: item.type === 'IN' ? '#ff0000' : '#000' }]}>
|
|
|
|
|
+ {item.type === 'IN' ? '+' : '-'}{item.money}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <Text style={styles.cellTime}>{item.createTime}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ))
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View style={styles.emptyBox}>
|
|
|
|
|
+ <Text style={styles.emptyText}>暂无记录</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
+ </ImageBackground>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const styles = StyleSheet.create({
|
|
|
|
|
+ container: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ backgroundColor: '#1a1a2e',
|
|
|
|
|
+ },
|
|
|
|
|
+ header: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ left: 0,
|
|
|
|
|
+ right: 0,
|
|
|
|
|
+ zIndex: 100,
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ paddingBottom: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ backBtn: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ left: 10,
|
|
|
|
|
+ bottom: 10,
|
|
|
|
|
+ zIndex: 101,
|
|
|
|
|
+ },
|
|
|
|
|
+ title: {
|
|
|
|
|
+ color: '#fff',
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ background: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: '100%',
|
|
|
|
|
+ },
|
|
|
|
|
+ headerBg: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: 240, // 480rpx / 2 = 240
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ left: 0,
|
|
|
|
|
+ },
|
|
|
|
|
+ scrollView: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ },
|
|
|
|
|
+ stoneBox: {
|
|
|
|
|
+ width: 310, // 620rpx / 2
|
|
|
|
|
+ height: 100, // 200rpx / 2
|
|
|
|
|
+ marginHorizontal: 'auto',
|
|
|
|
|
+ alignSelf: 'center',
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ // No background image as per legacy analysis, relies on transparency over headerBg?
|
|
|
|
|
+ // Wait, legacy publicHeaderBg height is 480rpx (240px). StoneBox is inside wrapper padding 200rpx top.
|
|
|
|
|
+ // So it sits on top of headerBg.
|
|
|
|
|
+ },
|
|
|
|
|
+ infoRow: {
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ justifyContent: 'space-around',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ infoCol: {
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ num: {
|
|
|
|
|
+ fontSize: 22,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ color: '#fff',
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ color: '#fff',
|
|
|
|
|
+ marginTop: 4,
|
|
|
|
|
+ },
|
|
|
|
|
+ listSection: {
|
|
|
|
|
+ paddingHorizontal: 16,
|
|
|
|
|
+ marginTop: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ listTitleBg: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: 63, // 126rpx / 2
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ position: 'relative',
|
|
|
|
|
+ zIndex: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ listTitleText: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ color: '#333',
|
|
|
|
|
+ },
|
|
|
|
|
+ pickerBox: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ right: 13,
|
|
|
|
|
+ top: 20, // Adjust vertically
|
|
|
|
|
+ },
|
|
|
|
|
+ pickerBg: {
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ paddingHorizontal: 10,
|
|
|
|
|
+ paddingVertical: 5,
|
|
|
|
|
+ minWidth: 60,
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ pickerText: {
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ color: 'rgba(0,0,0,0.85)',
|
|
|
|
|
+ },
|
|
|
|
|
+ listContainer: {
|
|
|
|
|
+ backgroundColor: '#fff', // Legacy wrapper background is transparent, list items have no bg?
|
|
|
|
|
+ // Legacy list items have border-bottom.
|
|
|
|
|
+ // Wait, legacy wrapper has background-size: 100% 100%, likely kaixinMineBg.
|
|
|
|
|
+ // Cells are transparent?
|
|
|
|
|
+ // Legacy: .cell border-bottom: 2rpx solid #A2A2A2.
|
|
|
|
|
+ // Font color #000.
|
|
|
|
|
+ // So list should probably be on a white-ish background or transparent?
|
|
|
|
|
+ // Looking at screenshot or legacy, if text is black, background must be light.
|
|
|
|
|
+ // But kaixinMineBg is usually dark/blue.
|
|
|
|
|
+ // Ah, kaixinMineBg (new images) might be different.
|
|
|
|
|
+ // Let's assume transparent cell on whatever background.
|
|
|
|
|
+ // But text color is #000 in legacy css!
|
|
|
|
|
+ // Maybe headerBg covers the whole page? No.
|
|
|
|
|
+ // I'll add a white background to list container to be safe, or check detailed legacy logic.
|
|
|
|
|
+ // Legacy wrapper min-height 100vh.
|
|
|
|
|
+ // I'll stick to transparency but maybe change text color if needed.
|
|
|
|
|
+ // Legacy text: color: #000.
|
|
|
|
|
+ marginTop: -10, // Overlap slightly or just separate
|
|
|
|
|
+ paddingTop: 10,
|
|
|
|
|
+ paddingHorizontal: 10,
|
|
|
|
|
+ paddingBottom: 20,
|
|
|
|
|
+ borderRadius: 8,
|
|
|
|
|
+ backgroundColor: '#fff', // Safest bet for "black text" readability
|
|
|
|
|
+ minHeight: 300,
|
|
|
|
|
+ },
|
|
|
|
|
+ cell: {
|
|
|
|
|
+ borderBottomWidth: 1,
|
|
|
|
|
+ borderBottomColor: '#eee',
|
|
|
|
|
+ paddingVertical: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ cellRow: {
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ justifyContent: 'space-between',
|
|
|
|
|
+ marginBottom: 6,
|
|
|
|
|
+ },
|
|
|
|
|
+ cellDesc: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ color: '#000',
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ },
|
|
|
|
|
+ cellAmount: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ cellTime: {
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ color: '#666',
|
|
|
|
|
+ },
|
|
|
|
|
+ emptyBox: {
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ marginTop: 40,
|
|
|
|
|
+ },
|
|
|
|
|
+ emptyText: {
|
|
|
|
|
+ color: '#999',
|
|
|
|
|
+ },
|
|
|
|
|
+});
|