mine.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. import { KefuPopup, KefuPopupRef } from '@/components/mine/KefuPopup';
  2. import { MenuCell } from '@/components/mine/MenuCell';
  3. import { Images } from '@/constants/images';
  4. import { getMagicIndex } from '@/services/award';
  5. import { getParamConfig, getUserInfo, UserInfo } from '@/services/user';
  6. import * as Clipboard from 'expo-clipboard';
  7. import { Image } from 'expo-image';
  8. import { useFocusEffect, useRouter } from 'expo-router';
  9. import React, { useCallback, useRef, useState } from 'react';
  10. import {
  11. Alert,
  12. ImageBackground,
  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<{ state: number; data: string } | null>(null);
  34. const [showWallet, setShowWallet] = useState(false);
  35. const kefuRef = useRef<KefuPopupRef>(null);
  36. const loadData = useCallback(async () => {
  37. try {
  38. const [info, magicData] = await Promise.all([
  39. getUserInfo(),
  40. getMagicIndex(),
  41. ]);
  42. setUserInfo(info);
  43. setIndexData(magicData);
  44. // 获取邀请配置
  45. const inviteConfig = await getParamConfig('invite_config');
  46. setInviteShow(inviteConfig?.state !== 0);
  47. // 获取备案信息
  48. const filingConfig = await getParamConfig('beian_icp');
  49. if (filingConfig) {
  50. setFilingInfo({ state: filingConfig.state, data: filingConfig.data });
  51. }
  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. // Loose check for undefined to match legacy
  116. if (typeof indexData[key] === 'undefined') return '-';
  117. return bigNumberTransform(indexData[key]!);
  118. };
  119. const bigNumberTransform = (value: number) => {
  120. const newValue = ['', '', ''];
  121. let fr = 1000;
  122. let num = 3;
  123. let text1 = '';
  124. let fm = 1;
  125. // Determine magnitude
  126. let tempValue = value;
  127. while (tempValue / fr >= 1) {
  128. fr *= 10;
  129. num += 1;
  130. }
  131. if (num <= 4) {
  132. // 千 (Thousand)
  133. newValue[0] = parseInt(String(value / 1000)) + '';
  134. newValue[1] = '千';
  135. } else if (num <= 8) {
  136. // 万 (Ten Thousand)
  137. text1 = (num - 4) / 3 > 1 ? '千万' : '万';
  138. fm = text1 === '万' ? 10000 : 10000000;
  139. if (value % fm === 0) {
  140. newValue[0] = parseInt(String(value / fm)) + '';
  141. } else {
  142. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  143. }
  144. newValue[1] = text1;
  145. } else if (num <= 16) {
  146. // 亿 (Hundred Million)
  147. text1 = (num - 8) / 3 > 1 ? '千亿' : '亿';
  148. text1 = (num - 8) / 4 > 1 ? '万亿' : text1;
  149. text1 = (num - 8) / 7 > 1 ? '千万亿' : text1;
  150. fm = 1;
  151. if (text1 === '亿') {
  152. fm = 100000000;
  153. } else if (text1 === '千亿') {
  154. fm = 100000000000;
  155. } else if (text1 === '万亿') {
  156. fm = 1000000000000;
  157. } else if (text1 === '千万亿') {
  158. fm = 1000000000000000;
  159. }
  160. if (value % fm === 0) {
  161. newValue[0] = parseInt(String(value / fm)) + '';
  162. } else {
  163. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  164. }
  165. newValue[1] = text1;
  166. }
  167. if (value < 1000) {
  168. newValue[0] = String(value);
  169. newValue[1] = '';
  170. }
  171. return newValue.join('');
  172. };
  173. return (
  174. <View style={styles.container}>
  175. <StatusBar barStyle="light-content" />
  176. <ImageBackground
  177. source={{ uri: Images.mine.kaixinMineBg }}
  178. style={styles.background}
  179. resizeMode="cover"
  180. >
  181. {/* 顶部背景 */}
  182. <Image
  183. source={{ uri: Images.mine.kaixinMineHeadBg }}
  184. style={[styles.headerBg, { top: 0 }]}
  185. contentFit="cover"
  186. />
  187. <ScrollView
  188. style={styles.scrollView}
  189. contentContainerStyle={{ paddingTop: insets.top }}
  190. showsVerticalScrollIndicator={false}
  191. >
  192. {/* 顶部标题 */}
  193. <View style={styles.header}>
  194. <Text style={styles.title}>个人中心</Text>
  195. </View>
  196. {/* 用户信息 */}
  197. <TouchableOpacity
  198. style={styles.userBox}
  199. onPress={handleLogin}
  200. activeOpacity={0.8}
  201. >
  202. <ImageBackground
  203. source={{ uri: Images.mine.avatarBorderBg }}
  204. style={styles.avatarBorder}
  205. resizeMode="contain"
  206. >
  207. <Image
  208. source={{ uri: userInfo?.avatar || Images.common.defaultAvatar }}
  209. style={styles.avatar}
  210. contentFit="cover"
  211. />
  212. </ImageBackground>
  213. <View style={styles.userInfo}>
  214. <View style={styles.nicknameRow}>
  215. <Text style={styles.nickname}>
  216. {userInfo?.nickname || '暂未登录!'}
  217. </Text>
  218. {userInfo && (
  219. <TouchableOpacity onPress={() => handleMenuPress('/profile')}>
  220. <Image
  221. source={{ uri: Images.mine.editIcon }}
  222. style={styles.editIcon}
  223. contentFit="contain"
  224. />
  225. </TouchableOpacity>
  226. )}
  227. </View>
  228. {userInfo ? (
  229. <View style={styles.idRow}>
  230. <TouchableOpacity
  231. style={styles.idItem}
  232. onPress={() => handleCopy(userInfo.username || userInfo.id || '')}
  233. >
  234. <Text style={styles.idText}>ID:{userInfo.username || userInfo.id}</Text>
  235. <Image
  236. source={{ uri: Images.mine.kaixinUserCopyIcon }}
  237. style={styles.copyIcon}
  238. contentFit="contain"
  239. />
  240. </TouchableOpacity>
  241. {(userInfo.mobile || userInfo.phone) && (
  242. <Text style={styles.phoneText}>手机:{userInfo.mobile || userInfo.phone}</Text>
  243. )}
  244. </View>
  245. ) : (
  246. <Text style={styles.loginTip}>点此登录账号</Text>
  247. )}
  248. </View>
  249. </TouchableOpacity>
  250. {/* 数据统计 */}
  251. <ImageBackground
  252. source={{ uri: Images.mine.kaixinUserDataBg }}
  253. style={styles.dataBox}
  254. resizeMode="stretch"
  255. >
  256. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/coupon')}>
  257. <Text style={styles.dataNum}>{showNumber('couponCount')}</Text>
  258. <Text style={styles.dataLabel}>优惠券</Text>
  259. </TouchableOpacity>
  260. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/store')}>
  261. <Text style={styles.dataNum}>{showNumber('inventoryCount')}</Text>
  262. <Text style={styles.dataLabel}>仓库</Text>
  263. </TouchableOpacity>
  264. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/magic')}>
  265. <Text style={styles.dataNum}>{showNumber('magicBalance')}</Text>
  266. <Text style={styles.dataLabel}>果实</Text>
  267. </TouchableOpacity>
  268. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/boxInBox/boxList')}>
  269. <Text style={styles.dataNum}>{showNumber('treasureBoxCount')}</Text>
  270. <Text style={styles.dataLabel}>宝箱</Text>
  271. </TouchableOpacity>
  272. </ImageBackground>
  273. {/* 功能入口 */}
  274. <ImageBackground
  275. source={{ uri: Images.mine.userSection1Bg }}
  276. style={styles.funcBox}
  277. resizeMode="stretch"
  278. >
  279. <View style={styles.funcList}>
  280. {inviteShow && userInfo && (
  281. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/invite')}>
  282. <Image source={{ uri: Images.mine.invite }} style={styles.funcIcon} contentFit="contain" />
  283. <Text style={styles.funcText}>邀新有礼</Text>
  284. </TouchableOpacity>
  285. )}
  286. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/message')}>
  287. <Image source={{ uri: Images.mine.message }} style={styles.funcIcon} contentFit="contain" />
  288. <Text style={styles.funcText}>系统消息</Text>
  289. </TouchableOpacity>
  290. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/orders')}>
  291. <Image source={{ uri: Images.mine.kaixinorder }} style={styles.funcIcon} contentFit="contain" />
  292. <Text style={styles.funcText}>宝箱订单</Text>
  293. </TouchableOpacity>
  294. </View>
  295. </ImageBackground>
  296. {/* 订单入口 */}
  297. <ImageBackground
  298. source={{ uri: Images.welfare.roomBg }}
  299. style={styles.orderBox}
  300. resizeMode="stretch"
  301. >
  302. <View style={styles.orderList}>
  303. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders/shop?active=1')}>
  304. <Image source={{ uri: Images.mine.order1 }} style={styles.orderImage} contentFit="contain" />
  305. </TouchableOpacity>
  306. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders/shop?active=4')}>
  307. <Image source={{ uri: Images.mine.order2 }} style={styles.orderImage} contentFit="contain" />
  308. </TouchableOpacity>
  309. </View>
  310. </ImageBackground>
  311. {/* 菜单列表 */}
  312. <View style={styles.menuSection}>
  313. <MenuCell onItemPress={handleMenuItemPress} showWallet={showWallet} />
  314. </View>
  315. {/* 备案信息 */}
  316. {filingInfo && filingInfo.state !== 0 && (
  317. <View style={styles.filingBox}>
  318. <Text style={styles.filingText}>{filingInfo.data}</Text>
  319. </View>
  320. )}
  321. {/* 底部占位 */}
  322. <View style={{ height: 120 }} />
  323. </ScrollView>
  324. </ImageBackground>
  325. {/* 客服弹窗 */}
  326. <KefuPopup ref={kefuRef} />
  327. </View>
  328. );
  329. }
  330. const styles = StyleSheet.create({
  331. container: {
  332. flex: 1,
  333. backgroundColor: '#1a1a2e',
  334. },
  335. background: {
  336. flex: 1,
  337. },
  338. headerBg: {
  339. position: 'absolute',
  340. left: 0,
  341. width: '100%',
  342. height: 260,
  343. zIndex: 1,
  344. },
  345. scrollView: {
  346. flex: 1,
  347. zIndex: 2,
  348. },
  349. header: {
  350. alignItems: 'center',
  351. paddingBottom: 10,
  352. },
  353. title: {
  354. color: '#fff',
  355. fontSize: 16,
  356. fontWeight: 'bold',
  357. height: 40,
  358. lineHeight: 40,
  359. },
  360. userBox: {
  361. flexDirection: 'row',
  362. paddingHorizontal: 22,
  363. paddingVertical: 8,
  364. marginHorizontal: 8,
  365. },
  366. avatarBorder: {
  367. width: 64,
  368. height: 64,
  369. marginRight: 21,
  370. padding: 7.5, // 原项目 15rpx
  371. justifyContent: 'center',
  372. alignItems: 'center',
  373. },
  374. avatarBox: {
  375. width: '100%',
  376. height: '100%',
  377. borderRadius: 4,
  378. overflow: 'hidden',
  379. },
  380. avatar: {
  381. width: 49,
  382. height: 49,
  383. borderRadius: 2,
  384. },
  385. userInfo: {
  386. flex: 1,
  387. justifyContent: 'center',
  388. },
  389. nicknameRow: {
  390. flexDirection: 'row',
  391. alignItems: 'center',
  392. justifyContent: 'space-between',
  393. marginBottom: 10,
  394. },
  395. nickname: {
  396. color: '#fff',
  397. fontSize: 16,
  398. fontWeight: '700',
  399. },
  400. editIcon: {
  401. width: 67,
  402. height: 23,
  403. },
  404. idRow: {
  405. flexDirection: 'row',
  406. alignItems: 'center',
  407. },
  408. idItem: {
  409. flexDirection: 'row',
  410. alignItems: 'center',
  411. marginRight: 22,
  412. },
  413. idText: {
  414. color: '#fff',
  415. fontSize: 11,
  416. fontWeight: 'bold',
  417. },
  418. copyIcon: {
  419. width: 14,
  420. height: 14,
  421. marginLeft: 6,
  422. },
  423. phoneText: {
  424. color: '#fff',
  425. fontSize: 11,
  426. fontWeight: 'bold',
  427. },
  428. loginTip: {
  429. color: '#fff',
  430. fontSize: 12,
  431. },
  432. dataBox: {
  433. flexDirection: 'row',
  434. justifyContent: 'space-between',
  435. width: 360,
  436. height: 57,
  437. marginHorizontal: 'auto',
  438. paddingHorizontal: 11,
  439. alignSelf: 'center',
  440. },
  441. dataItem: {
  442. width: '25%',
  443. alignItems: 'center',
  444. justifyContent: 'center',
  445. paddingTop: 10,
  446. },
  447. dataNum: {
  448. color: '#000',
  449. fontSize: 16,
  450. fontWeight: 'bold',
  451. },
  452. dataLabel: {
  453. color: '#934800',
  454. fontSize: 12,
  455. },
  456. funcBox: {
  457. height: 115, // 原项目 230rpx
  458. marginHorizontal: 0,
  459. paddingTop: 20,
  460. paddingHorizontal: 10,
  461. },
  462. funcList: {
  463. flexDirection: 'row',
  464. justifyContent: 'space-around',
  465. },
  466. funcItem: {
  467. flex: 1,
  468. alignItems: 'center',
  469. },
  470. funcIcon: {
  471. width: 59, // 原项目 118rpx
  472. height: 57, // 原项目 114rpx
  473. },
  474. funcText: {
  475. color: '#000',
  476. fontSize: 12, // 原项目 24rpx
  477. fontWeight: '400',
  478. marginTop: 4,
  479. },
  480. orderBox: {
  481. marginHorizontal: 8,
  482. paddingTop: 20,
  483. paddingHorizontal: 16,
  484. paddingBottom: 25,
  485. },
  486. orderList: {
  487. flexDirection: 'row',
  488. justifyContent: 'space-between',
  489. },
  490. orderItem: {
  491. width: 161,
  492. },
  493. orderImage: {
  494. width: 161,
  495. height: 75,
  496. },
  497. filingBox: {
  498. alignItems: 'center',
  499. marginTop: 20,
  500. paddingHorizontal: 20,
  501. },
  502. filingText: {
  503. color: '#fff',
  504. fontSize: 14,
  505. textAlign: 'center',
  506. },
  507. menuSection: {
  508. backgroundColor: '#fff',
  509. marginHorizontal: 8,
  510. marginTop: 10,
  511. borderRadius: 8,
  512. overflow: 'hidden',
  513. },
  514. });