mine.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. import { KefuPopup, KefuPopupRef } from "@/components/mine/KefuPopup";
  2. import { MenuCell } from "@/components/mine/MenuCell";
  3. import { Colors } from "@/constants/Colors";
  4. import { Images } from "@/constants/images";
  5. import { getMagicIndex } from "@/services/award";
  6. import { getParamConfig, getUserInfo, UserInfo } from "@/services/user";
  7. import * as Clipboard from "expo-clipboard";
  8. import { Image } from "expo-image";
  9. import { useFocusEffect, useRouter } from "expo-router";
  10. import React, { useCallback, useRef, useState } from "react";
  11. import {
  12. Alert,
  13. ScrollView,
  14. StatusBar,
  15. StyleSheet,
  16. Text,
  17. TouchableOpacity,
  18. View,
  19. } from "react-native";
  20. import { useSafeAreaInsets } from "react-native-safe-area-context";
  21. interface IndexData {
  22. couponCount?: number;
  23. inventoryCount?: number;
  24. magicBalance?: number;
  25. treasureBoxCount?: number;
  26. }
  27. export default function MineScreen() {
  28. const router = useRouter();
  29. const insets = useSafeAreaInsets();
  30. const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
  31. const [indexData, setIndexData] = useState<IndexData | null>(null);
  32. const [inviteShow, setInviteShow] = useState(false);
  33. const [filingInfo, setFilingInfo] = useState<{
  34. state: number;
  35. data: string;
  36. } | null>(null);
  37. const [showWallet, setShowWallet] = useState(false);
  38. const [showExchange, setShowExchange] = useState(false);
  39. const kefuRef = useRef<KefuPopupRef>(null);
  40. const loadData = useCallback(async () => {
  41. try {
  42. const [info, magicData] = await Promise.all([
  43. getUserInfo(),
  44. getMagicIndex(),
  45. ]);
  46. setUserInfo(info);
  47. setIndexData(magicData);
  48. const inviteConfig = await getParamConfig("invite_config");
  49. setInviteShow(inviteConfig?.state !== 0);
  50. const filingConfig = await getParamConfig("beian_icp");
  51. if (filingConfig) {
  52. setFilingInfo({ state: filingConfig.state, data: filingConfig.data });
  53. }
  54. const walletConfig = await getParamConfig("wallet_recharge_show");
  55. setShowWallet(walletConfig?.state === 1);
  56. const exchangeConfig = await getParamConfig("harry_exchange_show");
  57. setShowExchange(exchangeConfig?.state === 1);
  58. } catch (error) {
  59. console.error("获取数据失败:", error);
  60. }
  61. }, []);
  62. useFocusEffect(
  63. useCallback(() => {
  64. loadData();
  65. }, [loadData]),
  66. );
  67. const handleLogin = () => {
  68. if (!userInfo) {
  69. router.push("/login" as any);
  70. }
  71. };
  72. const handleCopy = async (text: string) => {
  73. await Clipboard.setStringAsync(text);
  74. Alert.alert("提示", "复制成功");
  75. };
  76. const handleMenuPress = (route: string) => {
  77. if (!userInfo) {
  78. router.push("/login" as any);
  79. return;
  80. }
  81. if (route) {
  82. router.push(route as any);
  83. }
  84. };
  85. const handleMenuItemPress = (type: string) => {
  86. if (!userInfo && type !== "4_4") {
  87. router.push("/login" as any);
  88. return;
  89. }
  90. switch (type) {
  91. case "1_1": // 钱包
  92. router.push("/wallet" as any);
  93. break;
  94. case "2_0": // 全部订单
  95. router.push("/orders" as any);
  96. break;
  97. case "6_1": // 兑换码
  98. router.push("/exchange" as any);
  99. break;
  100. case "4_4": // 联系客服
  101. kefuRef.current?.open();
  102. break;
  103. case "4_3": // 地址
  104. router.push("/address" as any);
  105. break;
  106. case "4_9": // 意见反馈
  107. router.push("/feedback" as any);
  108. break;
  109. case "4_5": // 设置
  110. router.push("/setting" as any);
  111. break;
  112. default:
  113. break;
  114. }
  115. };
  116. const showNumber = (key: keyof IndexData) => {
  117. if (!indexData) return "-";
  118. if (typeof indexData[key] === "undefined") return "-";
  119. return bigNumberTransform(indexData[key]!);
  120. };
  121. const bigNumberTransform = (value: number) => {
  122. const newValue = ["", "", ""];
  123. let fr = 1000;
  124. let num = 3;
  125. let text1 = "";
  126. let fm = 1;
  127. let tempValue = value;
  128. while (tempValue / fr >= 1) {
  129. fr *= 10;
  130. num += 1;
  131. }
  132. if (num <= 4) {
  133. newValue[0] = parseInt(String(value / 1000)) + "";
  134. newValue[1] = "千";
  135. } else if (num <= 8) {
  136. text1 = (num - 4) / 3 > 1 ? "千万" : "万";
  137. fm = text1 === "万" ? 10000 : 10000000;
  138. if (value % fm === 0) {
  139. newValue[0] = parseInt(String(value / fm)) + "";
  140. } else {
  141. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  142. }
  143. newValue[1] = text1;
  144. } else if (num <= 16) {
  145. text1 = (num - 8) / 3 > 1 ? "千亿" : "亿";
  146. text1 = (num - 8) / 4 > 1 ? "万亿" : text1;
  147. text1 = (num - 8) / 7 > 1 ? "千万亿" : text1;
  148. fm = 1;
  149. if (text1 === "亿") {
  150. fm = 100000000;
  151. } else if (text1 === "千亿") {
  152. fm = 100000000000;
  153. } else if (text1 === "万亿") {
  154. fm = 1000000000000;
  155. } else if (text1 === "千万亿") {
  156. fm = 1000000000000000;
  157. }
  158. if (value % fm === 0) {
  159. newValue[0] = parseInt(String(value / fm)) + "";
  160. } else {
  161. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  162. }
  163. newValue[1] = text1;
  164. }
  165. if (value < 1000) {
  166. newValue[0] = String(value);
  167. newValue[1] = "";
  168. }
  169. return newValue.join("");
  170. };
  171. // Stat Card Component
  172. const StatCard = ({ num, label, onPress }: any) => (
  173. <TouchableOpacity style={styles.dataItem} onPress={onPress}>
  174. <Text style={styles.dataNum}>{num}</Text>
  175. <Text style={styles.dataLabel}>{label}</Text>
  176. <View style={styles.dataLine} />
  177. </TouchableOpacity>
  178. );
  179. return (
  180. <View style={styles.container}>
  181. <StatusBar barStyle="light-content" />
  182. {/* Scroll View */}
  183. <ScrollView
  184. style={styles.scrollView}
  185. contentContainerStyle={{
  186. paddingTop: insets.top + 20,
  187. paddingBottom: 100,
  188. }}
  189. showsVerticalScrollIndicator={false}
  190. >
  191. {/* Header Title */}
  192. <View style={styles.header}>
  193. <Text style={styles.title}>DATA CENTER</Text>
  194. <Text style={styles.subTitle}>个人档案</Text>
  195. </View>
  196. {/* User Info Card */}
  197. <View style={styles.userCard}>
  198. <View style={styles.userContent}>
  199. <TouchableOpacity
  200. style={styles.avatarContainer}
  201. onPress={handleLogin}
  202. activeOpacity={0.8}
  203. >
  204. <Image
  205. source={{
  206. uri: userInfo?.avatar || Images.common.defaultAvatar,
  207. }}
  208. style={styles.avatar}
  209. contentFit="cover"
  210. />
  211. <View style={styles.avatarBorder} />
  212. </TouchableOpacity>
  213. <View style={styles.userInfo}>
  214. <View style={styles.nicknameRow}>
  215. <Text style={styles.nickname}>
  216. {userInfo?.nickname || "未授权访问"}
  217. </Text>
  218. {userInfo && (
  219. <TouchableOpacity
  220. onPress={() => handleMenuPress("/profile")}
  221. style={styles.editBtn}
  222. >
  223. <Text style={styles.editBtnText}>EDIT</Text>
  224. </TouchableOpacity>
  225. )}
  226. </View>
  227. {userInfo ? (
  228. <View style={styles.idRow}>
  229. <TouchableOpacity
  230. style={styles.idItem}
  231. onPress={() =>
  232. handleCopy(userInfo.username || userInfo.id || "")
  233. }
  234. >
  235. <Text style={styles.idText}>
  236. ID: {userInfo.username || userInfo.id}
  237. </Text>
  238. <View style={styles.copyBadge}>
  239. <Text style={styles.copyText}>COPY</Text>
  240. </View>
  241. </TouchableOpacity>
  242. {(userInfo.mobile || userInfo.phone) && (
  243. <Text style={styles.phoneText}>
  244. Mobile: {userInfo.mobile || userInfo.phone}
  245. </Text>
  246. )}
  247. </View>
  248. ) : (
  249. <TouchableOpacity onPress={handleLogin}>
  250. <Text style={styles.loginTip}>点击获取访问权限</Text>
  251. </TouchableOpacity>
  252. )}
  253. </View>
  254. </View>
  255. {/* Tech Decoration */}
  256. <View style={styles.techDecoTR} />
  257. <View style={styles.techDecoBL} />
  258. </View>
  259. {/* Stats Grid */}
  260. <View style={styles.statsContainer}>
  261. <StatCard
  262. num={showNumber("couponCount")}
  263. label="优惠券"
  264. onPress={() => handleMenuPress("/coupon")}
  265. />
  266. <StatCard
  267. num={showNumber("inventoryCount")}
  268. label="仓库"
  269. onPress={() => handleMenuPress("/store")}
  270. />
  271. <StatCard
  272. num={showNumber("magicBalance")}
  273. label="果实"
  274. onPress={() => handleMenuPress("/magic")}
  275. />
  276. <StatCard
  277. num={showNumber("treasureBoxCount")}
  278. label="宝箱"
  279. onPress={() => handleMenuPress("/boxInBox/boxList")}
  280. />
  281. </View>
  282. {/* Shortcuts */}
  283. <View style={styles.shortcutsContainer}>
  284. <View style={styles.shortcutsHeader}>
  285. <Text style={styles.shortcutsTitle}>QUICK ACCESS</Text>
  286. </View>
  287. <View style={styles.shortcutsGrid}>
  288. {inviteShow && userInfo && (
  289. <TouchableOpacity
  290. style={styles.shortcutItem}
  291. onPress={() => Alert.alert("提示", "暂未开放")}
  292. >
  293. <View style={styles.shortcutIconBox}>
  294. <Image
  295. source={Images.mine.invite}
  296. style={styles.shortcutIcon}
  297. contentFit="contain"
  298. />
  299. </View>
  300. <Text style={styles.shortcutText}>邀新有礼</Text>
  301. </TouchableOpacity>
  302. )}
  303. <TouchableOpacity
  304. style={styles.shortcutItem}
  305. onPress={() => handleMenuPress("/message")}
  306. >
  307. <View style={styles.shortcutIconBox}>
  308. <Image
  309. source={Images.mine.message}
  310. style={styles.shortcutIcon}
  311. contentFit="contain"
  312. />
  313. </View>
  314. <Text style={styles.shortcutText}>系统消息</Text>
  315. </TouchableOpacity>
  316. <TouchableOpacity
  317. style={styles.shortcutItem}
  318. onPress={() => handleMenuPress("/orders")}
  319. >
  320. <View style={styles.shortcutIconBox}>
  321. <Image
  322. source={Images.mine.kaixinOrders}
  323. style={styles.shortcutIcon}
  324. contentFit="contain"
  325. />
  326. </View>
  327. <Text style={styles.shortcutText}>宝箱订单</Text>
  328. </TouchableOpacity>
  329. </View>
  330. </View>
  331. {/* Orders Entry */}
  332. <View style={styles.orderContainer}>
  333. <View style={styles.shortcutsHeader}>
  334. <Text style={styles.shortcutsTitle}>ORDERS</Text>
  335. </View>
  336. <View style={styles.orderGrid}>
  337. <TouchableOpacity
  338. style={styles.orderBtn}
  339. onPress={() => handleMenuPress("/orders/shop?active=1")}
  340. >
  341. <Text style={styles.orderBtnText}>待发货</Text>
  342. <View style={styles.orderBtnLine} />
  343. </TouchableOpacity>
  344. <TouchableOpacity
  345. style={styles.orderBtn}
  346. onPress={() => handleMenuPress("/orders/shop?active=4")}
  347. >
  348. <Text style={styles.orderBtnText}>已发货</Text>
  349. <View style={styles.orderBtnLine} />
  350. </TouchableOpacity>
  351. </View>
  352. </View>
  353. {/* Menu Section */}
  354. <View style={styles.menuSection}>
  355. <MenuCell onItemPress={handleMenuItemPress} showWallet={showWallet} showExchange={showExchange} />
  356. </View>
  357. {/* Footer Info */}
  358. {filingInfo && filingInfo.state !== 0 && (
  359. <View style={styles.filingBox}>
  360. <Text style={styles.filingText}>{filingInfo.data}</Text>
  361. </View>
  362. )}
  363. </ScrollView>
  364. <KefuPopup ref={kefuRef} />
  365. </View>
  366. );
  367. }
  368. const styles = StyleSheet.create({
  369. container: {
  370. flex: 1,
  371. backgroundColor: Colors.darkBg,
  372. },
  373. scrollView: {
  374. flex: 1,
  375. },
  376. header: {
  377. paddingHorizontal: 20,
  378. marginBottom: 20,
  379. },
  380. title: {
  381. color: "#fff",
  382. fontSize: 24,
  383. fontWeight: "bold",
  384. fontStyle: "italic",
  385. letterSpacing: 2,
  386. textShadowColor: Colors.neonBlue,
  387. textShadowRadius: 10,
  388. },
  389. subTitle: {
  390. color: Colors.neonBlue,
  391. fontSize: 12,
  392. letterSpacing: 4,
  393. },
  394. // User Card
  395. userCard: {
  396. marginHorizontal: 15,
  397. padding: 20,
  398. backgroundColor: "rgba(255, 255, 255, 0.05)",
  399. borderRadius: 12,
  400. borderWidth: 1,
  401. borderColor: Colors.neonBlue,
  402. position: "relative",
  403. marginBottom: 20,
  404. },
  405. userContent: {
  406. flexDirection: "row",
  407. alignItems: "center",
  408. },
  409. avatarContainer: {
  410. position: "relative",
  411. marginRight: 15,
  412. },
  413. avatar: {
  414. width: 60,
  415. height: 60,
  416. borderRadius: 30,
  417. borderWidth: 2,
  418. borderColor: "#fff",
  419. },
  420. avatarBorder: {
  421. position: "absolute",
  422. top: -4,
  423. left: -4,
  424. right: -4,
  425. bottom: -4,
  426. borderRadius: 34,
  427. borderWidth: 1,
  428. borderColor: Colors.neonPink,
  429. borderStyle: "dashed",
  430. },
  431. userInfo: {
  432. flex: 1,
  433. },
  434. nicknameRow: {
  435. flexDirection: "row",
  436. alignItems: "center",
  437. justifyContent: "space-between",
  438. marginBottom: 8,
  439. },
  440. nickname: {
  441. color: "#fff",
  442. fontSize: 18,
  443. fontWeight: "bold",
  444. },
  445. editBtn: {
  446. paddingHorizontal: 10,
  447. paddingVertical: 4,
  448. backgroundColor: Colors.neonBlue,
  449. borderRadius: 4,
  450. },
  451. editBtnText: {
  452. color: "#000",
  453. fontSize: 10,
  454. fontWeight: "bold",
  455. },
  456. idRow: {
  457. alignItems: "flex-start",
  458. },
  459. idItem: {
  460. flexDirection: "row",
  461. alignItems: "center",
  462. marginBottom: 4,
  463. },
  464. idText: {
  465. color: Colors.textSecondary,
  466. fontSize: 12,
  467. marginRight: 8,
  468. },
  469. copyBadge: {
  470. backgroundColor: "rgba(255, 255, 255, 0.1)",
  471. paddingHorizontal: 4,
  472. borderRadius: 2,
  473. },
  474. copyText: {
  475. color: "#fff",
  476. fontSize: 8,
  477. },
  478. phoneText: {
  479. color: Colors.textTertiary,
  480. fontSize: 12,
  481. },
  482. loginTip: {
  483. color: Colors.neonPink,
  484. fontSize: 14,
  485. textDecorationLine: "underline",
  486. },
  487. techDecoTR: {
  488. position: "absolute",
  489. top: 0,
  490. right: 0,
  491. width: 20,
  492. height: 20,
  493. borderTopWidth: 2,
  494. borderRightWidth: 2,
  495. borderColor: Colors.neonBlue,
  496. },
  497. techDecoBL: {
  498. position: "absolute",
  499. bottom: 0,
  500. left: 0,
  501. width: 20,
  502. height: 20,
  503. borderBottomWidth: 2,
  504. borderLeftWidth: 2,
  505. borderColor: Colors.neonBlue,
  506. },
  507. // Stats
  508. statsContainer: {
  509. flexDirection: "row",
  510. justifyContent: "space-between",
  511. marginHorizontal: 15,
  512. marginBottom: 20,
  513. backgroundColor: Colors.darkCard,
  514. borderRadius: 8,
  515. padding: 15,
  516. borderWidth: 1,
  517. borderColor: "rgba(255, 255, 255, 0.1)",
  518. },
  519. dataItem: {
  520. alignItems: "center",
  521. flex: 1,
  522. },
  523. dataNum: {
  524. color: Colors.neonBlue,
  525. fontSize: 18,
  526. fontWeight: "bold",
  527. textShadowColor: Colors.neonBlue,
  528. textShadowRadius: 5,
  529. },
  530. dataLabel: {
  531. color: Colors.textSecondary,
  532. fontSize: 12,
  533. marginTop: 4,
  534. },
  535. dataLine: {
  536. width: 20,
  537. height: 2,
  538. backgroundColor: "rgba(255, 255, 255, 0.1)",
  539. marginTop: 8,
  540. },
  541. // Shortcuts
  542. shortcutsContainer: {
  543. marginHorizontal: 15,
  544. marginBottom: 20,
  545. },
  546. shortcutsHeader: {
  547. borderLeftWidth: 3,
  548. borderLeftColor: Colors.neonPink,
  549. paddingLeft: 10,
  550. marginBottom: 10,
  551. },
  552. shortcutsTitle: {
  553. color: "#fff",
  554. fontSize: 14,
  555. fontWeight: "bold",
  556. letterSpacing: 2,
  557. },
  558. shortcutsGrid: {
  559. flexDirection: "row",
  560. backgroundColor: Colors.darkCard,
  561. borderRadius: 8,
  562. padding: 15,
  563. },
  564. shortcutItem: {
  565. flex: 1,
  566. alignItems: "center",
  567. },
  568. shortcutIconBox: {
  569. width: 48,
  570. height: 48,
  571. borderRadius: 24,
  572. backgroundColor: "rgba(255, 255, 255, 0.05)",
  573. justifyContent: "center",
  574. alignItems: "center",
  575. marginBottom: 8,
  576. borderWidth: 1,
  577. borderColor: "rgba(255, 255, 255, 0.1)",
  578. },
  579. shortcutIcon: {
  580. width: 24,
  581. height: 24,
  582. tintColor: Colors.neonBlue, // Apply theme color
  583. },
  584. shortcutText: {
  585. color: Colors.textSecondary,
  586. fontSize: 12,
  587. },
  588. // Orders
  589. orderContainer: {
  590. marginHorizontal: 15,
  591. marginBottom: 20,
  592. },
  593. orderGrid: {
  594. flexDirection: "row",
  595. gap: 15,
  596. },
  597. orderBtn: {
  598. flex: 1,
  599. height: 60,
  600. backgroundColor: Colors.darkCard,
  601. borderRadius: 8,
  602. justifyContent: "center",
  603. alignItems: "center",
  604. borderWidth: 1,
  605. borderColor: "rgba(255, 255, 255, 0.1)",
  606. position: "relative",
  607. overflow: "hidden",
  608. },
  609. orderBtnText: {
  610. color: "#fff",
  611. fontSize: 14,
  612. fontWeight: "bold",
  613. letterSpacing: 2,
  614. },
  615. orderBtnLine: {
  616. position: "absolute",
  617. bottom: 0,
  618. left: 0,
  619. right: 0,
  620. height: 2,
  621. backgroundColor: Colors.neonBlue,
  622. },
  623. // Menu
  624. menuSection: {
  625. marginHorizontal: 15,
  626. backgroundColor: Colors.darkCard,
  627. borderRadius: 8,
  628. paddingVertical: 5,
  629. borderWidth: 1,
  630. borderColor: "rgba(255, 255, 255, 0.1)",
  631. },
  632. // Footer
  633. filingBox: {
  634. alignItems: "center",
  635. marginTop: 20,
  636. opacity: 0.5,
  637. },
  638. filingText: {
  639. color: Colors.textTertiary,
  640. fontSize: 10,
  641. textAlign: "center",
  642. },
  643. });