mine.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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 { Images } from '@/constants/images';
  17. import { getMagicIndex } from '@/services/award';
  18. import { getParamConfig, getUserInfo, UserInfo } from '@/services/user';
  19. interface IndexData {
  20. couponCount?: number;
  21. inventoryCount?: number;
  22. magicBalance?: number;
  23. treasureBoxCount?: number;
  24. }
  25. export default function MineScreen() {
  26. const router = useRouter();
  27. const insets = useSafeAreaInsets();
  28. const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
  29. const [indexData, setIndexData] = useState<IndexData | null>(null);
  30. const [inviteShow, setInviteShow] = useState(false);
  31. const [showCredit, setShowCredit] = useState(false);
  32. const [filingInfo, setFilingInfo] = useState<{ state: number; data: string } | null>(null);
  33. const loadData = useCallback(async () => {
  34. try {
  35. const [info, magicData] = await Promise.all([
  36. getUserInfo(),
  37. getMagicIndex(),
  38. ]);
  39. setUserInfo(info);
  40. setIndexData(magicData);
  41. // 获取邀请配置
  42. const inviteConfig = await getParamConfig('invite_config');
  43. setInviteShow(inviteConfig?.state !== 0);
  44. // 获取积分显示配置
  45. const creditConfig = await getParamConfig('credit_show');
  46. setShowCredit(creditConfig?.state !== 0);
  47. // 获取备案信息
  48. const filingConfig = await getParamConfig('beian_icp');
  49. if (filingConfig) {
  50. setFilingInfo({ state: filingConfig.state, data: filingConfig.data });
  51. }
  52. } catch (error) {
  53. console.error('获取数据失败:', error);
  54. }
  55. }, []);
  56. useFocusEffect(
  57. useCallback(() => {
  58. loadData();
  59. }, [loadData])
  60. );
  61. const handleLogin = () => {
  62. if (!userInfo) {
  63. router.push('/login' as any);
  64. }
  65. };
  66. const handleCopy = async (text: string) => {
  67. await Clipboard.setStringAsync(text);
  68. Alert.alert('提示', '复制成功');
  69. };
  70. const handleMenuPress = (route: string) => {
  71. if (!userInfo) {
  72. router.push('/login' as any);
  73. return;
  74. }
  75. if (route) {
  76. router.push(route as any);
  77. }
  78. };
  79. const showNumber = (key: keyof IndexData) => {
  80. if (!indexData) return '-';
  81. const val = indexData[key];
  82. if (typeof val === 'undefined') return '-';
  83. return String(val);
  84. };
  85. return (
  86. <View style={styles.container}>
  87. <StatusBar barStyle="light-content" />
  88. <ImageBackground
  89. source={{ uri: Images.mine.kaixinMineBg }}
  90. style={styles.background}
  91. resizeMode="cover"
  92. >
  93. {/* 顶部背景 */}
  94. <Image
  95. source={{ uri: Images.mine.kaixinMineHeadBg }}
  96. style={[styles.headerBg, { top: 0 }]}
  97. contentFit="cover"
  98. />
  99. <ScrollView
  100. style={styles.scrollView}
  101. contentContainerStyle={{ paddingTop: insets.top }}
  102. showsVerticalScrollIndicator={false}
  103. >
  104. {/* 顶部标题 */}
  105. <View style={styles.header}>
  106. <Text style={styles.title}>个人中心</Text>
  107. </View>
  108. {/* 用户信息 */}
  109. <TouchableOpacity
  110. style={styles.userBox}
  111. onPress={handleLogin}
  112. activeOpacity={0.8}
  113. >
  114. <ImageBackground
  115. source={{ uri: Images.common.defaultAvatar }}
  116. style={styles.avatarBox}
  117. resizeMode="cover"
  118. >
  119. {userInfo?.avatar && (
  120. <Image
  121. source={{ uri: userInfo.avatar }}
  122. style={styles.avatar}
  123. contentFit="cover"
  124. />
  125. )}
  126. </ImageBackground>
  127. <View style={styles.userInfo}>
  128. <View style={styles.nicknameRow}>
  129. <Text style={styles.nickname}>
  130. {userInfo?.nickname || '暂未登录!'}
  131. </Text>
  132. {userInfo && (
  133. <TouchableOpacity onPress={() => handleMenuPress('/user-info')}>
  134. <Image
  135. source={{ uri: Images.mine.editIcon }}
  136. style={styles.editIcon}
  137. contentFit="contain"
  138. />
  139. </TouchableOpacity>
  140. )}
  141. </View>
  142. {userInfo ? (
  143. <View style={styles.idRow}>
  144. <TouchableOpacity
  145. style={styles.idItem}
  146. onPress={() => handleCopy(userInfo.id || '')}
  147. >
  148. <Text style={styles.idText}>ID:{userInfo.id}</Text>
  149. <Image
  150. source={{ uri: Images.mine.kaixinUserCopyIcon }}
  151. style={styles.copyIcon}
  152. contentFit="contain"
  153. />
  154. </TouchableOpacity>
  155. {userInfo.phone && (
  156. <Text style={styles.phoneText}>手机:{userInfo.phone}</Text>
  157. )}
  158. </View>
  159. ) : (
  160. <Text style={styles.loginTip}>点此登录账号</Text>
  161. )}
  162. </View>
  163. </TouchableOpacity>
  164. {/* 数据统计 */}
  165. <ImageBackground
  166. source={{ uri: Images.mine.kaixinUserDataBg }}
  167. style={styles.dataBox}
  168. resizeMode="stretch"
  169. >
  170. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/coupon')}>
  171. <Text style={styles.dataNum}>{showNumber('couponCount')}</Text>
  172. <Text style={styles.dataLabel}>优惠券</Text>
  173. </TouchableOpacity>
  174. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/store')}>
  175. <Text style={styles.dataNum}>{showNumber('inventoryCount')}</Text>
  176. <Text style={styles.dataLabel}>仓库</Text>
  177. </TouchableOpacity>
  178. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/magic')}>
  179. <Text style={styles.dataNum}>{showNumber('magicBalance')}</Text>
  180. <Text style={styles.dataLabel}>果实</Text>
  181. </TouchableOpacity>
  182. <TouchableOpacity style={styles.dataItem} onPress={() => handleMenuPress('/box-list')}>
  183. <Text style={styles.dataNum}>{showNumber('treasureBoxCount')}</Text>
  184. <Text style={styles.dataLabel}>宝箱</Text>
  185. </TouchableOpacity>
  186. </ImageBackground>
  187. {/* 功能入口 */}
  188. <ImageBackground
  189. source={{ uri: Images.mine.userSection1Bg }}
  190. style={styles.funcBox}
  191. resizeMode="stretch"
  192. >
  193. <View style={styles.funcList}>
  194. {inviteShow && userInfo && (
  195. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/invite')}>
  196. <Image source={{ uri: Images.mine.invite }} style={styles.funcIcon} contentFit="contain" />
  197. <Text style={styles.funcText}>邀新有礼</Text>
  198. </TouchableOpacity>
  199. )}
  200. {showCredit && (
  201. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/integral')}>
  202. <Image source={{ uri: Images.mine.kaixinintegral }} style={styles.funcIcon} contentFit="contain" />
  203. <Text style={styles.funcText}>签到领积分</Text>
  204. </TouchableOpacity>
  205. )}
  206. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/message')}>
  207. <Image source={{ uri: Images.mine.message }} style={styles.funcIcon} contentFit="contain" />
  208. <Text style={styles.funcText}>系统消息</Text>
  209. </TouchableOpacity>
  210. <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/orders')}>
  211. <Image source={{ uri: Images.mine.kaixinorder }} style={styles.funcIcon} contentFit="contain" />
  212. <Text style={styles.funcText}>宝箱订单</Text>
  213. </TouchableOpacity>
  214. </View>
  215. </ImageBackground>
  216. {/* 订单入口 */}
  217. <ImageBackground
  218. source={{ uri: Images.mine.userSection2Bg }}
  219. style={styles.orderBox}
  220. resizeMode="stretch"
  221. >
  222. <View style={styles.orderList}>
  223. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=1')}>
  224. <Image source={{ uri: Images.mine.order1 }} style={styles.orderImage} contentFit="contain" />
  225. </TouchableOpacity>
  226. <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=4')}>
  227. <Image source={{ uri: Images.mine.order2 }} style={styles.orderImage} contentFit="contain" />
  228. </TouchableOpacity>
  229. </View>
  230. </ImageBackground>
  231. {/* 备案信息 */}
  232. {filingInfo && filingInfo.state !== 0 && (
  233. <View style={styles.filingBox}>
  234. <Text style={styles.filingText}>{filingInfo.data}</Text>
  235. </View>
  236. )}
  237. {/* 底部占位 */}
  238. <View style={{ height: 120 }} />
  239. </ScrollView>
  240. </ImageBackground>
  241. </View>
  242. );
  243. }
  244. const styles = StyleSheet.create({
  245. container: {
  246. flex: 1,
  247. backgroundColor: '#1a1a2e',
  248. },
  249. background: {
  250. flex: 1,
  251. },
  252. headerBg: {
  253. position: 'absolute',
  254. left: 0,
  255. width: '100%',
  256. height: 260,
  257. zIndex: 1,
  258. },
  259. scrollView: {
  260. flex: 1,
  261. zIndex: 2,
  262. },
  263. header: {
  264. alignItems: 'center',
  265. paddingBottom: 10,
  266. },
  267. title: {
  268. color: '#fff',
  269. fontSize: 16,
  270. fontWeight: 'bold',
  271. height: 40,
  272. lineHeight: 40,
  273. },
  274. userBox: {
  275. flexDirection: 'row',
  276. paddingHorizontal: 22,
  277. paddingVertical: 8,
  278. marginHorizontal: 8,
  279. },
  280. avatarBox: {
  281. width: 64,
  282. height: 64,
  283. borderRadius: 32,
  284. overflow: 'hidden',
  285. marginRight: 21,
  286. },
  287. avatar: {
  288. width: '100%',
  289. height: '100%',
  290. },
  291. userInfo: {
  292. flex: 1,
  293. justifyContent: 'center',
  294. },
  295. nicknameRow: {
  296. flexDirection: 'row',
  297. alignItems: 'center',
  298. justifyContent: 'space-between',
  299. marginBottom: 10,
  300. },
  301. nickname: {
  302. color: '#fff',
  303. fontSize: 16,
  304. fontWeight: '700',
  305. },
  306. editIcon: {
  307. width: 67,
  308. height: 23,
  309. },
  310. idRow: {
  311. flexDirection: 'row',
  312. alignItems: 'center',
  313. },
  314. idItem: {
  315. flexDirection: 'row',
  316. alignItems: 'center',
  317. marginRight: 22,
  318. },
  319. idText: {
  320. color: '#fff',
  321. fontSize: 11,
  322. fontWeight: 'bold',
  323. },
  324. copyIcon: {
  325. width: 11,
  326. height: 11,
  327. marginLeft: 5,
  328. },
  329. phoneText: {
  330. color: '#fff',
  331. fontSize: 11,
  332. fontWeight: 'bold',
  333. },
  334. loginTip: {
  335. color: '#fff',
  336. fontSize: 12,
  337. },
  338. dataBox: {
  339. flexDirection: 'row',
  340. justifyContent: 'space-between',
  341. width: 360,
  342. height: 57,
  343. marginHorizontal: 'auto',
  344. paddingHorizontal: 11,
  345. alignSelf: 'center',
  346. },
  347. dataItem: {
  348. width: '25%',
  349. alignItems: 'center',
  350. justifyContent: 'center',
  351. paddingTop: 10,
  352. },
  353. dataNum: {
  354. color: '#000',
  355. fontSize: 16,
  356. fontWeight: 'bold',
  357. },
  358. dataLabel: {
  359. color: '#934800',
  360. fontSize: 12,
  361. },
  362. funcBox: {
  363. height: 115,
  364. marginHorizontal: 0,
  365. paddingTop: 20,
  366. paddingHorizontal: 10,
  367. },
  368. funcList: {
  369. flexDirection: 'row',
  370. justifyContent: 'space-around',
  371. },
  372. funcItem: {
  373. alignItems: 'center',
  374. },
  375. funcIcon: {
  376. width: 59,
  377. height: 57,
  378. },
  379. funcText: {
  380. color: '#000',
  381. fontSize: 12,
  382. },
  383. orderBox: {
  384. marginHorizontal: 8,
  385. paddingTop: 20,
  386. paddingHorizontal: 16,
  387. paddingBottom: 25,
  388. },
  389. orderList: {
  390. flexDirection: 'row',
  391. justifyContent: 'space-between',
  392. },
  393. orderItem: {
  394. width: 161,
  395. },
  396. orderImage: {
  397. width: 161,
  398. height: 75,
  399. },
  400. filingBox: {
  401. alignItems: 'center',
  402. marginTop: 20,
  403. paddingHorizontal: 20,
  404. },
  405. filingText: {
  406. color: '#fff',
  407. fontSize: 14,
  408. textAlign: 'center',
  409. },
  410. });