recharge.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { Stack, useRouter } from 'expo-router';
  2. import React, { useState } from 'react';
  3. import { ActivityIndicator, Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
  4. import services from '../../services/api';
  5. const MAX_AMOUNT = 100000;
  6. export default function RechargeScreen() {
  7. const router = useRouter();
  8. const [money, setMoney] = useState('');
  9. const [loading, setLoading] = useState(false);
  10. const validateInput = (text: string) => {
  11. let value = text.replace(/[^\d.]/g, '')
  12. .replace(/^\./g, '')
  13. .replace(/\.{2,}/g, '.');
  14. value = value.replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
  15. if (parseFloat(value) > MAX_AMOUNT) {
  16. Alert.alert('提示', `最大充值金额为${MAX_AMOUNT}元`);
  17. value = MAX_AMOUNT.toString();
  18. }
  19. setMoney(value);
  20. };
  21. const handleRecharge = async () => {
  22. const amount = parseFloat(money);
  23. if (!amount || amount <= 0) {
  24. Alert.alert('提示', '请输入有效的充值金额');
  25. return;
  26. }
  27. setLoading(true);
  28. try {
  29. // Uniapp logic: generatePaymentLink -> tradeNo
  30. const res = await services.wallet.generatePaymentLink(amount, 'ALIPAY_H5', 'CASH');
  31. if (res && res.data && res.data.tradeNo) {
  32. const tradeNo = res.data.tradeNo;
  33. Alert.alert(
  34. '提示',
  35. '已生成充值订单,请完成支付',
  36. [
  37. {
  38. text: '已支付',
  39. onPress: async () => {
  40. const checkRes = await services.wallet.checkPaymentStatus({ tradeNo: tradeNo });
  41. if (checkRes && checkRes.code === 0 && checkRes.data.paySuccess) {
  42. Alert.alert('提示', '支付成功');
  43. router.replace('/wallet/recharge_record');
  44. } else {
  45. Alert.alert('提示', checkRes.msg || '支付未完成或查询失败');
  46. }
  47. }
  48. },
  49. { text: '稍后支付' }
  50. ]
  51. );
  52. } else {
  53. Alert.alert('失败', '生成充值订单失败');
  54. }
  55. } catch (error) {
  56. console.error('Recharge failed', error);
  57. Alert.alert('错误', '充值失败,请重试');
  58. } finally {
  59. setLoading(false);
  60. }
  61. };
  62. return (
  63. <View style={styles.container}>
  64. <Stack.Screen options={{ title: '充值', headerStyle: { backgroundColor: '#fff' }, headerShadowVisible: false }} />
  65. <View style={styles.content}>
  66. <View style={styles.inputWrapper}>
  67. <Text style={styles.label}>充值金额:</Text>
  68. <TextInput
  69. style={styles.input}
  70. value={money}
  71. onChangeText={validateInput}
  72. placeholder="请输入金额"
  73. keyboardType="decimal-pad"
  74. />
  75. </View>
  76. <Text style={styles.tip}>充值金额可以用于购买手办,奖池</Text>
  77. <Text style={styles.tipSub}>
  78. 充值金额不可提现,<Text style={styles.highlight}>线下充值额外返10%</Text>
  79. </Text>
  80. <TouchableOpacity
  81. style={[styles.btn, loading && styles.disabledBtn]}
  82. onPress={handleRecharge}
  83. disabled={loading}
  84. >
  85. {loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.btnText}>充值</Text>}
  86. </TouchableOpacity>
  87. <TouchableOpacity style={styles.recordLink} onPress={() => router.push('/wallet/recharge_record')}>
  88. <Text style={styles.recordLinkText}>充值记录 {'>'}{'>'}</Text>
  89. </TouchableOpacity>
  90. </View>
  91. </View>
  92. );
  93. }
  94. const styles = StyleSheet.create({
  95. container: {
  96. flex: 1,
  97. backgroundColor: '#fff',
  98. },
  99. content: {
  100. paddingHorizontal: 24,
  101. paddingTop: 32,
  102. },
  103. inputWrapper: {
  104. flexDirection: 'row',
  105. alignItems: 'center',
  106. borderWidth: 1,
  107. borderColor: '#ddd',
  108. borderRadius: 8,
  109. paddingHorizontal: 16,
  110. height: 52, // 104rpx
  111. marginTop: 15,
  112. },
  113. label: {
  114. fontSize: 16, // font5
  115. color: '#333',
  116. marginRight: 10,
  117. },
  118. input: {
  119. flex: 1,
  120. fontSize: 16, // font5
  121. height: '100%',
  122. },
  123. tip: {
  124. textAlign: 'center',
  125. color: '#666', // color-2
  126. fontSize: 14,
  127. marginTop: 36,
  128. },
  129. tipSub: {
  130. textAlign: 'center',
  131. color: '#666', // color-2
  132. fontSize: 14,
  133. marginTop: 5,
  134. marginBottom: 20,
  135. },
  136. highlight: {
  137. color: '#07C160',
  138. textDecorationLine: 'underline',
  139. },
  140. btn: {
  141. backgroundColor: '#0081ff', // bg-blue
  142. height: 44,
  143. borderRadius: 4,
  144. justifyContent: 'center',
  145. alignItems: 'center',
  146. width: '70%',
  147. alignSelf: 'center',
  148. },
  149. disabledBtn: {
  150. opacity: 0.7,
  151. },
  152. btnText: {
  153. color: '#fff',
  154. fontSize: 14,
  155. fontWeight: 'bold',
  156. },
  157. recordLink: {
  158. marginTop: 20,
  159. alignItems: 'center',
  160. },
  161. recordLinkText: {
  162. color: '#8b3dff', // color-theme
  163. fontSize: 14,
  164. },
  165. });