withdraw.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { Ionicons } from "@expo/vector-icons";
  2. import { Stack, useLocalSearchParams, useRouter } from "expo-router";
  3. import React, { useEffect, useState } from "react";
  4. import {
  5. ActivityIndicator,
  6. Alert,
  7. ScrollView,
  8. StyleSheet,
  9. Text,
  10. TextInput,
  11. TouchableOpacity,
  12. View,
  13. } from "react-native";
  14. import services from "../../services/api";
  15. export default function WithdrawScreen() {
  16. const router = useRouter();
  17. const params = useLocalSearchParams();
  18. const [balance, setBalance] = useState("0.00");
  19. const [money, setMoney] = useState("");
  20. const [bankInfo, setBankInfo] = useState<any>(null);
  21. const [rate, setRate] = useState(0);
  22. const [agree, setAgree] = useState(false);
  23. const [loading, setLoading] = useState(false);
  24. // Uniapp had type='ALIPAY' default
  25. const type = (params.type as string) || "ALIPAY";
  26. useEffect(() => {
  27. fetchData();
  28. fetchRate();
  29. fetchWithdrawInfo();
  30. }, []);
  31. const fetchData = async () => {
  32. try {
  33. const res = await services.purse.withdrawPre(type, "MAGIC_PROMOTION");
  34. if (res && res.avaliableWithdraw) {
  35. setBalance(res.avaliableWithdraw.amount);
  36. }
  37. } catch (error) {
  38. console.error("Fetch withdraw pre failed", error);
  39. }
  40. };
  41. const fetchRate = async () => {
  42. try {
  43. await services.user
  44. .getParamConfig("wallet_withdraw_rate")
  45. .then((res: any) => {
  46. setRate(res.data ? Number(res.data) : 0);
  47. });
  48. } catch (error) {
  49. console.error("Fetch rate failed", error);
  50. }
  51. };
  52. const fetchWithdrawInfo = async () => {
  53. try {
  54. const res = await services.purse.getWithdraw();
  55. setBankInfo(res);
  56. } catch (error) {
  57. console.error("Fetch bank info failed", error);
  58. }
  59. };
  60. const handleWithdraw = async () => {
  61. if (!agree) {
  62. Alert.alert("提示", "请您先阅读并同意提现协议");
  63. return;
  64. }
  65. if (!bankInfo || !bankInfo.bankNum) {
  66. Alert.alert("提示", "请新增银行卡信息");
  67. return;
  68. }
  69. const amount = parseFloat(money);
  70. if (
  71. !money ||
  72. isNaN(amount) ||
  73. amount <= 0 ||
  74. amount > parseFloat(balance)
  75. ) {
  76. Alert.alert("提示", "请输入正确的金额");
  77. return;
  78. }
  79. setLoading(true);
  80. try {
  81. const success = await services.purse.withdraw({
  82. money,
  83. ...bankInfo,
  84. walletType: "MAGIC_PROMOTION",
  85. accountType: "3",
  86. });
  87. if (success) {
  88. Alert.alert("成功", "提现申请已提交", [
  89. {
  90. text: "确定",
  91. onPress: () => router.push("/wallet/withdraw_record"),
  92. },
  93. ]);
  94. setMoney("");
  95. }
  96. } catch (error) {
  97. console.error("Withdraw failed", error);
  98. } finally {
  99. setLoading(false);
  100. }
  101. };
  102. const realMoney = money
  103. ? (parseFloat(money) * (1 - rate)).toFixed(2)
  104. : "0.00";
  105. return (
  106. <ScrollView style={styles.container}>
  107. <Stack.Screen
  108. options={{
  109. title: "提现",
  110. headerStyle: { backgroundColor: "#fff" },
  111. headerShadowVisible: false,
  112. }}
  113. />
  114. <View style={styles.groupContainer}>
  115. {/* Bank Card Section */}
  116. <TouchableOpacity
  117. style={styles.formGroup}
  118. onPress={() => router.push("/cardInfo")}
  119. >
  120. <Text style={styles.label}>提现至</Text>
  121. <View style={styles.rightContent}>
  122. <Text style={styles.valueText}>
  123. {bankInfo && bankInfo.bankName
  124. ? `${bankInfo.bankName}尾号(${bankInfo.bankNum.slice(-4)})`
  125. : "暂无银行卡信息"}
  126. </Text>
  127. <Ionicons name="chevron-forward" size={16} color="#8799a3" />
  128. </View>
  129. </TouchableOpacity>
  130. </View>
  131. <View style={styles.groupContainer}>
  132. <View style={styles.formGroup}>
  133. <Text style={styles.label}>可提现金额:{balance}元</Text>
  134. <TouchableOpacity style={styles.ruleBtn}>
  135. <Ionicons name="help-circle" size={16} color="#5b5b5b" />
  136. <Text style={styles.ruleText}>提现规则</Text>
  137. </TouchableOpacity>
  138. </View>
  139. <View style={styles.formGroupInput}>
  140. <Text style={styles.currencySymbol}>¥</Text>
  141. <TextInput
  142. style={styles.input}
  143. value={money}
  144. onChangeText={setMoney}
  145. keyboardType="decimal-pad"
  146. placeholder="请输入提现金额"
  147. />
  148. <TouchableOpacity onPress={() => setMoney(balance)}>
  149. <Text style={styles.withdrawAll}>全部提现</Text>
  150. </TouchableOpacity>
  151. </View>
  152. <View style={[styles.formGroup, { backgroundColor: "#f8f8f8" }]}>
  153. <Text style={styles.label}>税率:</Text>
  154. <Text style={styles.valueText}>{(rate * 100).toFixed(0)}%</Text>
  155. </View>
  156. <View style={[styles.formGroup, { backgroundColor: "#f8f8f8" }]}>
  157. <Text style={styles.label}>实际到账金额:</Text>
  158. <Text style={styles.valueText}>{realMoney}</Text>
  159. </View>
  160. </View>
  161. <TouchableOpacity
  162. style={styles.recordLink}
  163. onPress={() => router.push("/wallet/withdraw_record")}
  164. >
  165. <Text style={styles.recordLinkText}>提现记录</Text>
  166. </TouchableOpacity>
  167. <TouchableOpacity
  168. style={styles.agreement}
  169. onPress={() => setAgree(!agree)}
  170. >
  171. <Ionicons
  172. name={agree ? "radio-button-on" : "radio-button-off"}
  173. size={20}
  174. color={agree ? "#8b3dff" : "#8799a3"}
  175. />
  176. <Text style={styles.agreementText}>
  177. 我已阅读并同意 <Text style={styles.linkText}>《提现协议》</Text>
  178. </Text>
  179. </TouchableOpacity>
  180. <View style={styles.footer}>
  181. <TouchableOpacity
  182. style={[styles.confirmBtn, loading && styles.disabledBtn]}
  183. onPress={handleWithdraw}
  184. disabled={loading}
  185. >
  186. {loading ? (
  187. <ActivityIndicator color="#fff" />
  188. ) : (
  189. <Text style={styles.confirmBtnText}>确认提现</Text>
  190. )}
  191. </TouchableOpacity>
  192. </View>
  193. </ScrollView>
  194. );
  195. }
  196. const styles = StyleSheet.create({
  197. container: {
  198. flex: 1,
  199. backgroundColor: "#fff",
  200. },
  201. groupContainer: {
  202. paddingHorizontal: 12,
  203. paddingTop: 18,
  204. },
  205. formGroup: {
  206. flexDirection: "row",
  207. justifyContent: "space-between",
  208. alignItems: "center",
  209. paddingVertical: 15,
  210. backgroundColor: "#fff",
  211. // borderBottomWidth: 1,
  212. // borderBottomColor: '#eee',
  213. },
  214. formGroupInput: {
  215. flexDirection: "row",
  216. alignItems: "center",
  217. paddingVertical: 15,
  218. borderBottomWidth: 1,
  219. borderBottomColor: "#eee",
  220. },
  221. label: {
  222. fontSize: 15,
  223. color: "#333",
  224. },
  225. rightContent: {
  226. flexDirection: "row",
  227. alignItems: "center",
  228. },
  229. valueText: {
  230. fontSize: 15,
  231. color: "#666",
  232. marginRight: 5,
  233. },
  234. ruleBtn: {
  235. flexDirection: "row",
  236. alignItems: "center",
  237. },
  238. ruleText: {
  239. fontSize: 14,
  240. color: "#5b5b5b",
  241. marginLeft: 4,
  242. },
  243. currencySymbol: {
  244. fontSize: 24,
  245. fontWeight: "bold",
  246. color: "#333",
  247. width: 30,
  248. },
  249. input: {
  250. flex: 1,
  251. fontSize: 20,
  252. paddingHorizontal: 10,
  253. },
  254. withdrawAll: {
  255. color: "#ff9600",
  256. fontSize: 14,
  257. },
  258. recordLink: {
  259. alignItems: "center",
  260. marginTop: 18,
  261. },
  262. recordLinkText: {
  263. fontSize: 12,
  264. color: "#999",
  265. },
  266. agreement: {
  267. flexDirection: "row",
  268. justifyContent: "center",
  269. alignItems: "center",
  270. marginTop: 20,
  271. },
  272. agreementText: {
  273. fontSize: 12,
  274. color: "#333",
  275. marginLeft: 4,
  276. },
  277. linkText: {
  278. color: "#8b3dff",
  279. },
  280. footer: {
  281. paddingHorizontal: 26,
  282. paddingTop: 6,
  283. marginTop: 20,
  284. backgroundColor: "#f6f6f6", // Optional visual separation if needed, or transparent
  285. paddingBottom: 40,
  286. },
  287. confirmBtn: {
  288. backgroundColor: "#8b3dff", // Theme color
  289. height: 50,
  290. borderRadius: 25,
  291. justifyContent: "center",
  292. alignItems: "center",
  293. },
  294. disabledBtn: {
  295. opacity: 0.7,
  296. },
  297. confirmBtnText: {
  298. color: "#fff",
  299. fontSize: 16,
  300. fontWeight: "bold",
  301. },
  302. });