mine.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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. const val = indexData[key];
  118. if (typeof val === 'undefined') return '-';
  119. return String(val);
  120. };
  121. return (
  122. <View style={styles.container}>
  123. <StatusBar barStyle="light-content" />
  124. <ImageBackground
  125. source={{ uri: Images.mine.kaixinMineBg }}
  126. style={styles.background}
  127. resizeMode="cover"
  128. >
  129. {/* 顶部背景 */}
  130. <Image
  131. source={{ uri: Images.mine.kaixinMineHeadBg }}
  132. style={[styles.headerBg, { top: 0 }]}
  133. contentFit="cover"
  134. />
  135. <ScrollView
  136. style={styles.scrollView}
  137. contentContainerStyle={{ paddingTop: insets.top }}
  138. showsVerticalScrollIndicator={false}
  139. >
  140. {/* 顶部标题 */}
  141. <View style={styles.header}>
  142. <Text style={styles.title}>个人中心</Text>
  143. </View>
  144. {/* 用户信息 */}
  145. <TouchableOpacity
  146. style={styles.userBox}
  147. onPress={handleLogin}
  148. activeOpacity={0.8}
  149. >
  150. <ImageBackground
  151. source={{ uri: Images.common.defaultAvatar }}
  152. style={styles.avatarBox}
  153. resizeMode="cover"
  154. >
  155. {userInfo?.avatar && (
  156. <Image
  157. source={{ uri: userInfo.avatar }}
  158. style={styles.avatar}
  159. contentFit="cover"
  160. />
  161. )}
  162. </ImageBackground>
  163. <View style={styles.userInfo}>
  164. <View style={styles.nicknameRow}>
  165. <Text style={styles.nickname}>
  166. {userInfo?.nickname || '暂未登录!'}
  167. </Text>
  168. {userInfo && (
  169. <TouchableOpacity onPress={() => handleMenuPress('/user-info')}>
  170. <Image
  171. source={{ uri: Images.mine.editIcon }}
  172. style={styles.editIcon}
  173. contentFit="contain"
  174. />
  175. </TouchableOpacity>
  176. )}
  177. </View>
  178. {userInfo ? (
  179. <View style={styles.idRow}>
  180. <TouchableOpacity
  181. style={styles.idItem}
  182. onPress={() => handleCopy(userInfo.id || '')}
  183. >
  184. <Text style={styles.idText}>ID:{userInfo.id}</Text>
  185. <Image
  186. source={{ uri: Images.mine.kaixinUserCopyIcon }}
  187. style={styles.copyIcon}
  188. contentFit="contain"
  189. />
  190. </TouchableOpacity>
  191. {userInfo.phone && (
  192. <Text style={styles.phoneText}>手机:{userInfo.phone}</Text>
  193. )}
  194. </View>
  195. ) : (
  196. <Text style={styles.loginTip}>点此登录账号</Text>
  197. )}
  198. </View>
  199. </TouchableOpacity>
  200. {/* 数据统计 */}
  201. <ImageBackground
  202. source={{ uri: Images.mine.kaixinUserDataBg }}
  203. style={styles.dataBox}
  204. resizeMode="stretch"
  205. >
  206. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/coupon')}>
  207. <Text style={styles.dataNum}>{showNumber('couponCount')}</Text>
  208. <Text style={styles.dataLabel}>优惠券</Text>
  209. </TouchableOpacity>
  210. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/store')}>
  211. <Text style={styles.dataNum}>{showNumber('inventoryCount')}</Text>
  212. <Text style={styles.dataLabel}>仓库</Text>
  213. </TouchableOpacity>
  214. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/magic')}>
  215. <Text style={styles.dataNum}>{showNumber('magicBalance')}</Text>
  216. <Text style={styles.dataLabel}>果实</Text>
  217. </TouchableOpacity>
  218. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/box-list')}>
  219. <Text style={styles.dataNum}>{showNumber('treasureBoxCount')}</Text>
  220. <Text style={styles.dataLabel}>宝箱</Text>
  221. </TouchableOpacity>
  222. </ImageBackground>
  223. {/* 功能入口 */}
  224. <ImageBackground
  225. source={{ uri: Images.mine.userSection1Bg }}
  226. style={styles.funcBox}
  227. resizeMode="stretch"
  228. >
  229. <View style={styles.funcList}>
  230. {inviteShow && userInfo && (
  231. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/invite')}>
  232. <Image source={{ uri: Images.mine.invite }} style={styles.funcIcon} contentFit="contain" />
  233. <Text style={styles.funcText}>邀新有礼</Text>
  234. </TouchableOpacity>
  235. )}
  236. {showCredit && (
  237. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/integral')}>
  238. <Image source={{ uri: Images.mine.kaixinintegral }} style={styles.funcIcon} contentFit="contain" />
  239. <Text style={styles.funcText}>签到领积分</Text>
  240. </TouchableOpacity>
  241. )}
  242. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/message')}>
  243. <Image source={{ uri: Images.mine.message }} style={styles.funcIcon} contentFit="contain" />
  244. <Text style={styles.funcText}>系统消息</Text>
  245. </TouchableOpacity>
  246. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/orders')}>
  247. <Image source={{ uri: Images.mine.kaixinorder }} style={styles.funcIcon} contentFit="contain" />
  248. <Text style={styles.funcText}>宝箱订单</Text>
  249. </TouchableOpacity>
  250. </View>
  251. </ImageBackground>
  252. {/* 订单入口 */}
  253. <ImageBackground
  254. source={{ uri: Images.mine.userSection2Bg }}
  255. style={styles.orderBox}
  256. resizeMode="stretch"
  257. >
  258. <View style={styles.orderList}>
  259. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=1')}>
  260. <Image source={{ uri: Images.mine.order1 }} style={styles.orderImage} contentFit="contain" />
  261. </TouchableOpacity>
  262. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=4')}>
  263. <Image source={{ uri: Images.mine.order2 }} style={styles.orderImage} contentFit="contain" />
  264. </TouchableOpacity>
  265. </View>
  266. </ImageBackground>
  267. {/* 菜单列表 */}
  268. <View style={styles.menuSection}>
  269. <MenuCell onItemPress={handleMenuItemPress} showWallet={showWallet} />
  270. </View>
  271. {/* 备案信息 */}
  272. {filingInfo && filingInfo.state !== 0 && (
  273. <View style={styles.filingBox}>
  274. <Text style={styles.filingText}>{filingInfo.data}</Text>
  275. </View>
  276. )}
  277. {/* 底部占位 */}
  278. <View style={{ height: 120 }} />
  279. </ScrollView>
  280. </ImageBackground>
  281. </View>
  282. );
  283. }
  284. const styles = StyleSheet.create({
  285. container: {
  286. flex: 1,
  287. backgroundColor: '#1a1a2e',
  288. },
  289. background: {
  290. flex: 1,
  291. },
  292. headerBg: {
  293. position: 'absolute',
  294. left: 0,
  295. width: '100%',
  296. height: 260,
  297. zIndex: 1,
  298. },
  299. scrollView: {
  300. flex: 1,
  301. zIndex: 2,
  302. },
  303. header: {
  304. alignItems: 'center',
  305. paddingBottom: 10,
  306. },
  307. title: {
  308. color: '#fff',
  309. fontSize: 16,
  310. fontWeight: 'bold',
  311. height: 40,
  312. lineHeight: 40,
  313. },
  314. userBox: {
  315. flexDirection: 'row',
  316. paddingHorizontal: 22,
  317. paddingVertical: 8,
  318. marginHorizontal: 8,
  319. },
  320. avatarBox: {
  321. width: 64,
  322. height: 64,
  323. borderRadius: 32,
  324. overflow: 'hidden',
  325. marginRight: 21,
  326. },
  327. avatar: {
  328. width: '100%',
  329. height: '100%',
  330. },
  331. userInfo: {
  332. flex: 1,
  333. justifyContent: 'center',
  334. },
  335. nicknameRow: {
  336. flexDirection: 'row',
  337. alignItems: 'center',
  338. justifyContent: 'space-between',
  339. marginBottom: 10,
  340. },
  341. nickname: {
  342. color: '#fff',
  343. fontSize: 16,
  344. fontWeight: '700',
  345. },
  346. editIcon: {
  347. width: 67,
  348. height: 23,
  349. },
  350. idRow: {
  351. flexDirection: 'row',
  352. alignItems: 'center',
  353. },
  354. idItem: {
  355. flexDirection: 'row',
  356. alignItems: 'center',
  357. marginRight: 22,
  358. },
  359. idText: {
  360. color: '#fff',
  361. fontSize: 11,
  362. fontWeight: 'bold',
  363. },
  364. copyIcon: {
  365. width: 11,
  366. height: 11,
  367. marginLeft: 5,
  368. },
  369. phoneText: {
  370. color: '#fff',
  371. fontSize: 11,
  372. fontWeight: 'bold',
  373. },
  374. loginTip: {
  375. color: '#fff',
  376. fontSize: 12,
  377. },
  378. dataBox: {
  379. flexDirection: 'row',
  380. justifyContent: 'space-between',
  381. width: 360,
  382. height: 57,
  383. marginHorizontal: 'auto',
  384. paddingHorizontal: 11,
  385. alignSelf: 'center',
  386. },
  387. dataItem: {
  388. width: '25%',
  389. alignItems: 'center',
  390. justifyContent: 'center',
  391. paddingTop: 10,
  392. },
  393. dataNum: {
  394. color: '#000',
  395. fontSize: 16,
  396. fontWeight: 'bold',
  397. },
  398. dataLabel: {
  399. color: '#934800',
  400. fontSize: 12,
  401. },
  402. funcBox: {
  403. height: 115,
  404. marginHorizontal: 0,
  405. paddingTop: 20,
  406. paddingHorizontal: 10,
  407. },
  408. funcList: {
  409. flexDirection: 'row',
  410. justifyContent: 'space-around',
  411. },
  412. funcItem: {
  413. alignItems: 'center',
  414. },
  415. funcIcon: {
  416. width: 59,
  417. height: 57,
  418. },
  419. funcText: {
  420. color: '#000',
  421. fontSize: 12,
  422. },
  423. orderBox: {
  424. marginHorizontal: 8,
  425. paddingTop: 20,
  426. paddingHorizontal: 16,
  427. paddingBottom: 25,
  428. },
  429. orderList: {
  430. flexDirection: 'row',
  431. justifyContent: 'space-between',
  432. },
  433. orderItem: {
  434. width: 161,
  435. },
  436. orderImage: {
  437. width: 161,
  438. height: 75,
  439. },
  440. filingBox: {
  441. alignItems: 'center',
  442. marginTop: 20,
  443. paddingHorizontal: 20,
  444. },
  445. filingText: {
  446. color: '#fff',
  447. fontSize: 14,
  448. textAlign: 'center',
  449. },
  450. menuSection: {
  451. backgroundColor: '#fff',
  452. marginHorizontal: 8,
  453. marginTop: 10,
  454. borderRadius: 8,
  455. overflow: 'hidden',
  456. },
  457. });