mine.tsx 17 KB

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