mine.tsx 16 KB

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