mine.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. import * as Clipboard from 'expo-clipboard';
  2. import { Image } from 'expo-image';
  3. import { useFocusEffect, useRouter } from 'expo-router';
  4. import React, { useCallback, useState } from 'react';
  5. import {
  6. Alert,
  7. ImageBackground,
  8. ScrollView,
  9. StatusBar,
  10. StyleSheet,
  11. Text,
  12. TouchableOpacity,
  13. View,
  14. } from 'react-native';
  15. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  16. import { MenuCell } from '@/components/mine/MenuCell';
  17. import { Images } from '@/constants/images';
  18. import { getMagicIndex } from '@/services/award';
  19. import { getParamConfig, getUserInfo, UserInfo } from '@/services/user';
  20. interface IndexData {
  21. couponCount?: number;
  22. inventoryCount?: number;
  23. magicBalance?: number;
  24. treasureBoxCount?: number;
  25. }
  26. export default function MineScreen() {
  27. const router = useRouter();
  28. const insets = useSafeAreaInsets();
  29. const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
  30. const [indexData, setIndexData] = useState<IndexData | null>(null);
  31. const [inviteShow, setInviteShow] = useState(false);
  32. const [showCredit, setShowCredit] = useState(false);
  33. const [filingInfo, setFilingInfo] = useState<{ state: number; data: string } | null>(null);
  34. const [showWallet, setShowWallet] = useState(false);
  35. const loadData = useCallback(async () => {
  36. try {
  37. const [info, magicData] = await Promise.all([
  38. getUserInfo(),
  39. getMagicIndex(),
  40. ]);
  41. setUserInfo(info);
  42. setIndexData(magicData);
  43. // 获取邀请配置
  44. const inviteConfig = await getParamConfig('invite_config');
  45. setInviteShow(inviteConfig?.state !== 0);
  46. // 获取积分显示配置
  47. const creditConfig = await getParamConfig('credit_show');
  48. setShowCredit(creditConfig?.state !== 0);
  49. // 获取备案信息
  50. const filingConfig = await getParamConfig('beian_icp');
  51. if (filingConfig) {
  52. setFilingInfo({ state: filingConfig.state, data: filingConfig.data });
  53. }
  54. // 获取钱包显示配置
  55. const walletConfig = await getParamConfig('wallet_recharge_show');
  56. setShowWallet(walletConfig?.state === 1);
  57. } catch (error) {
  58. console.error('获取数据失败:', error);
  59. }
  60. }, []);
  61. useFocusEffect(
  62. useCallback(() => {
  63. loadData();
  64. }, [loadData])
  65. );
  66. const handleLogin = () => {
  67. if (!userInfo) {
  68. router.push('/login' as any);
  69. }
  70. };
  71. const handleCopy = async (text: string) => {
  72. await Clipboard.setStringAsync(text);
  73. Alert.alert('提示', '复制成功');
  74. };
  75. const handleMenuPress = (route: string) => {
  76. if (!userInfo) {
  77. router.push('/login' as any);
  78. return;
  79. }
  80. if (route) {
  81. router.push(route as any);
  82. }
  83. };
  84. const handleMenuItemPress = (type: string) => {
  85. if (!userInfo && type !== '4_4') {
  86. router.push('/login' as any);
  87. return;
  88. }
  89. switch (type) {
  90. case '1_1': // 钱包
  91. router.push('/wallet' as any);
  92. break;
  93. case '2_0': // 全部订单
  94. router.push('/orders' as any);
  95. break;
  96. case '6_1': // 兑换码
  97. Alert.alert('提示', '兑换码功能');
  98. break;
  99. case '4_4': // 联系客服
  100. Alert.alert('联系客服', '客服时间:10:00 ~ 18:00');
  101. break;
  102. case '4_3': // 地址
  103. router.push('/address' as any);
  104. break;
  105. case '4_9': // 意见反馈
  106. Alert.alert('提示', '意见反馈功能');
  107. break;
  108. case '4_5': // 设置
  109. Alert.alert('提示', '设置功能');
  110. break;
  111. default:
  112. break;
  113. }
  114. };
  115. const showNumber = (key: keyof IndexData) => {
  116. if (!indexData) return '-';
  117. // Loose check for undefined to match legacy
  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. // Determine magnitude
  128. let tempValue = value;
  129. while (tempValue / fr >= 1) {
  130. fr *= 10;
  131. num += 1;
  132. }
  133. if (num <= 4) {
  134. // 千 (Thousand)
  135. newValue[0] = parseInt(String(value / 1000)) + '';
  136. newValue[1] = '千';
  137. } else if (num <= 8) {
  138. // 万 (Ten Thousand)
  139. text1 = (num - 4) / 3 > 1 ? '千万' : '万';
  140. fm = text1 === '万' ? 10000 : 10000000;
  141. if (value % fm === 0) {
  142. newValue[0] = parseInt(String(value / fm)) + '';
  143. } else {
  144. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  145. }
  146. newValue[1] = text1;
  147. } else if (num <= 16) {
  148. // 亿 (Hundred Million)
  149. text1 = (num - 8) / 3 > 1 ? '千亿' : '亿';
  150. text1 = (num - 8) / 4 > 1 ? '万亿' : text1;
  151. text1 = (num - 8) / 7 > 1 ? '千万亿' : text1;
  152. fm = 1;
  153. if (text1 === '亿') {
  154. fm = 100000000;
  155. } else if (text1 === '千亿') {
  156. fm = 100000000000;
  157. } else if (text1 === '万亿') {
  158. fm = 1000000000000;
  159. } else if (text1 === '千万亿') {
  160. fm = 1000000000000000;
  161. }
  162. if (value % fm === 0) {
  163. newValue[0] = parseInt(String(value / fm)) + '';
  164. } else {
  165. newValue[0] = String(Math.floor((value / fm) * 10) / 10);
  166. }
  167. newValue[1] = text1;
  168. }
  169. if (value < 1000) {
  170. newValue[0] = String(value);
  171. newValue[1] = '';
  172. }
  173. return newValue.join('');
  174. };
  175. return (
  176. <View style={styles.container}>
  177. <StatusBar barStyle="light-content" />
  178. <ImageBackground
  179. source={{ uri: Images.mine.kaixinMineBg }}
  180. style={styles.background}
  181. resizeMode="cover"
  182. >
  183. {/* 顶部背景 */}
  184. <Image
  185. source={{ uri: Images.mine.kaixinMineHeadBg }}
  186. style={[styles.headerBg, { top: 0 }]}
  187. contentFit="cover"
  188. />
  189. <ScrollView
  190. style={styles.scrollView}
  191. contentContainerStyle={{ paddingTop: insets.top }}
  192. showsVerticalScrollIndicator={false}
  193. >
  194. {/* 顶部标题 */}
  195. <View style={styles.header}>
  196. <Text style={styles.title}>个人中心</Text>
  197. </View>
  198. {/* 用户信息 */}
  199. <TouchableOpacity
  200. style={styles.userBox}
  201. onPress={handleLogin}
  202. activeOpacity={0.8}
  203. >
  204. <ImageBackground
  205. source={{ uri: Images.common.defaultAvatar }}
  206. style={styles.avatarBox}
  207. resizeMode="cover"
  208. >
  209. {userInfo?.avatar && (
  210. <Image
  211. source={{ uri: userInfo.avatar }}
  212. style={styles.avatar}
  213. contentFit="cover"
  214. />
  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('/user-info')}>
  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.id || '')}
  237. >
  238. <Text style={styles.idText}>ID:{userInfo.id}</Text>
  239. <Image
  240. source={{ uri: Images.mine.kaixinUserCopyIcon }}
  241. style={styles.copyIcon}
  242. contentFit="contain"
  243. />
  244. </TouchableOpacity>
  245. {userInfo.phone && (
  246. <Text style={styles.phoneText}>手机:{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. </View>
  336. );
  337. }
  338. const styles = StyleSheet.create({
  339. container: {
  340. flex: 1,
  341. backgroundColor: '#1a1a2e',
  342. },
  343. background: {
  344. flex: 1,
  345. },
  346. headerBg: {
  347. position: 'absolute',
  348. left: 0,
  349. width: '100%',
  350. height: 260,
  351. zIndex: 1,
  352. },
  353. scrollView: {
  354. flex: 1,
  355. zIndex: 2,
  356. },
  357. header: {
  358. alignItems: 'center',
  359. paddingBottom: 10,
  360. },
  361. title: {
  362. color: '#fff',
  363. fontSize: 16,
  364. fontWeight: 'bold',
  365. height: 40,
  366. lineHeight: 40,
  367. },
  368. userBox: {
  369. flexDirection: 'row',
  370. paddingHorizontal: 22,
  371. paddingVertical: 8,
  372. marginHorizontal: 8,
  373. },
  374. avatarBox: {
  375. width: 64,
  376. height: 64,
  377. borderRadius: 32,
  378. overflow: 'hidden',
  379. marginRight: 21,
  380. },
  381. avatar: {
  382. width: '100%',
  383. height: '100%',
  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: 11,
  420. height: 11,
  421. marginLeft: 5,
  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,
  458. marginHorizontal: 0,
  459. paddingTop: 20,
  460. paddingHorizontal: 10,
  461. },
  462. funcList: {
  463. flexDirection: 'row',
  464. justifyContent: 'space-around',
  465. },
  466. funcItem: {
  467. alignItems: 'center',
  468. },
  469. funcIcon: {
  470. width: 59,
  471. height: 57,
  472. },
  473. funcText: {
  474. color: '#000',
  475. fontSize: 12,
  476. },
  477. orderBox: {
  478. marginHorizontal: 8,
  479. paddingTop: 20,
  480. paddingHorizontal: 16,
  481. paddingBottom: 25,
  482. },
  483. orderList: {
  484. flexDirection: 'row',
  485. justifyContent: 'space-between',
  486. },
  487. orderItem: {
  488. width: 161,
  489. },
  490. orderImage: {
  491. width: 161,
  492. height: 75,
  493. },
  494. filingBox: {
  495. alignItems: 'center',
  496. marginTop: 20,
  497. paddingHorizontal: 20,
  498. },
  499. filingText: {
  500. color: '#fff',
  501. fontSize: 14,
  502. textAlign: 'center',
  503. },
  504. menuSection: {
  505. backgroundColor: '#fff',
  506. marginHorizontal: 8,
  507. marginTop: 10,
  508. borderRadius: 8,
  509. overflow: 'hidden',
  510. },
  511. });