index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import { Image } from 'expo-image';
  2. import { useLocalSearchParams, useRouter } from 'expo-router';
  3. import React, { useCallback, useEffect, useState } from 'react';
  4. import {
  5. ActivityIndicator,
  6. ImageBackground,
  7. RefreshControl,
  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 { getAwardOrders } from '@/services/award';
  18. const LEVEL_MAP: Record<string, { title: string; color: string }> = {
  19. A: { title: '超神', color: '#ff0000' },
  20. B: { title: '欧皇', color: '#ffae00' },
  21. C: { title: '隐藏', color: '#9745e6' },
  22. D: { title: '普通款', color: '#666666' },
  23. };
  24. const tabs = [
  25. { label: '已完成', value: 'complete' },
  26. { label: '未完成', value: 'uncomplete' },
  27. ];
  28. interface AwardOrderItem {
  29. tradeNo: string;
  30. createTime: string;
  31. status: number;
  32. luckPool: {
  33. name: string;
  34. cover: string;
  35. };
  36. price: number;
  37. quantity: number;
  38. couponAmount: number;
  39. magicAmount: number;
  40. paymentAmount: number;
  41. paymentTimeoutTime?: string;
  42. itemList?: Array<{
  43. spu: { cover: string };
  44. level: string;
  45. }>;
  46. }
  47. export default function OrdersScreen() {
  48. const router = useRouter();
  49. const insets = useSafeAreaInsets();
  50. const params = useLocalSearchParams();
  51. const [loading, setLoading] = useState(true);
  52. const [refreshing, setRefreshing] = useState(false);
  53. const [activeTab, setActiveTab] = useState(params.tab === '4' ? 1 : 0);
  54. const [orders, setOrders] = useState<AwardOrderItem[]>([]);
  55. const [page, setPage] = useState(1);
  56. const loadData = useCallback(async (tabValue?: string, refresh = false) => {
  57. if (refresh) {
  58. setRefreshing(true);
  59. setPage(1);
  60. } else {
  61. setLoading(true);
  62. }
  63. try {
  64. const data = await getAwardOrders(refresh ? 1 : page, 10, tabValue);
  65. if (data?.records) {
  66. setOrders(refresh ? data.records : [...orders, ...data.records]);
  67. }
  68. } catch (error) {
  69. console.error('加载订单失败:', error);
  70. }
  71. setLoading(false);
  72. setRefreshing(false);
  73. }, [page, orders]);
  74. useEffect(() => {
  75. loadData(tabs[activeTab].value, true);
  76. }, [activeTab]);
  77. const onRefresh = () => {
  78. loadData(tabs[activeTab].value, true);
  79. };
  80. const switchTab = (index: number) => {
  81. if (index !== activeTab) {
  82. setActiveTab(index);
  83. setOrders([]);
  84. }
  85. };
  86. const goBack = () => {
  87. router.back();
  88. };
  89. const goToShopOrders = () => {
  90. router.push('/orders/shop' as any);
  91. };
  92. const getStatusText = (status: number) => {
  93. if (status === 99) return { text: '已完成', color: '#20D7EF' };
  94. if (status === 0) return { text: '待支付', color: '#F62C71' };
  95. if (status === 10) return { text: '用户取消', color: '#999' };
  96. if (status === 11) return { text: '超时取消', color: '#999' };
  97. return { text: '未知', color: '#999' };
  98. };
  99. return (
  100. <View style={styles.container}>
  101. <StatusBar barStyle="light-content" />
  102. <ImageBackground
  103. source={{ uri: Images.mine.kaixinMineBg }}
  104. style={styles.background}
  105. resizeMode="cover"
  106. >
  107. {/* 顶部导航 */}
  108. <View style={[styles.header, { paddingTop: insets.top }]}>
  109. <TouchableOpacity style={styles.backBtn} onPress={goBack}>
  110. <Text style={styles.backText}>‹</Text>
  111. </TouchableOpacity>
  112. <Text style={styles.headerTitle}>奖池订单</Text>
  113. <View style={styles.placeholder} />
  114. </View>
  115. {/* Tab 栏 */}
  116. <View style={styles.tabBar}>
  117. {tabs.map((tab, index) => (
  118. <TouchableOpacity
  119. key={index}
  120. onPress={() => switchTab(index)}
  121. activeOpacity={0.8}
  122. >
  123. <ImageBackground
  124. source={{ uri: activeTab === index ? Images.home.typeBgOn : Images.home.typeBg }}
  125. style={styles.tabItem}
  126. resizeMode="stretch"
  127. >
  128. <Text style={[styles.tabText, activeTab === index && styles.tabTextActive]}>
  129. {tab.label}
  130. </Text>
  131. </ImageBackground>
  132. </TouchableOpacity>
  133. ))}
  134. </View>
  135. <ScrollView
  136. style={styles.scrollView}
  137. showsVerticalScrollIndicator={false}
  138. refreshControl={
  139. <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#fff" />
  140. }
  141. >
  142. {loading && orders.length === 0 ? (
  143. <View style={styles.loadingContainer}>
  144. <ActivityIndicator size="large" color="#fff" />
  145. </View>
  146. ) : orders.length === 0 ? (
  147. <View style={styles.emptyContainer}>
  148. <Text style={styles.emptyText}>暂无订单</Text>
  149. </View>
  150. ) : (
  151. orders.map((item) => {
  152. const statusInfo = getStatusText(item.status);
  153. return (
  154. <ImageBackground
  155. key={item.tradeNo}
  156. source={{ uri: Images.common.itemBg }}
  157. style={styles.orderCard}
  158. resizeMode="stretch"
  159. >
  160. <View style={styles.cardContent}>
  161. {/* 顶部信息 */}
  162. <View style={styles.orderTop}>
  163. <Text style={styles.orderTime}>下单时间:{item.createTime}</Text>
  164. <Text style={[styles.orderStatus, { color: statusInfo.color }]}>
  165. {statusInfo.text}
  166. </Text>
  167. </View>
  168. {item.status === 0 && item.paymentTimeoutTime && (
  169. <Text style={styles.timeoutText}>
  170. {item.paymentTimeoutTime} 将自动取消该订单,如有优惠券,将自动退回
  171. </Text>
  172. )}
  173. {/* 中间商品信息 */}
  174. <View style={styles.orderMiddle}>
  175. <ImageBackground
  176. source={{ uri: Images.box?.detail?.firstItemBg || Images.common.itemBg }}
  177. style={styles.productImgBg}
  178. >
  179. <Image
  180. source={{ uri: item.luckPool?.cover }}
  181. style={styles.productImg}
  182. contentFit="cover"
  183. />
  184. </ImageBackground>
  185. <View style={styles.productInfo}>
  186. <Text style={styles.productName} numberOfLines={1}>{item.luckPool?.name}</Text>
  187. <View style={styles.priceRow}>
  188. <Text style={styles.priceText}>¥{item.price}</Text>
  189. <Text style={styles.qtyText}>X{item.quantity}</Text>
  190. </View>
  191. <Text style={styles.discountText}>
  192. 使用优惠券-{item.couponAmount} 使用果实-{item.magicAmount}
  193. </Text>
  194. <View style={styles.paymentRow}>
  195. <Text style={styles.discountText}>实付款:</Text>
  196. <Text style={styles.paymentAmount}>¥{item.paymentAmount}</Text>
  197. </View>
  198. </View>
  199. </View>
  200. {/* 底部商品列表 */}
  201. {item.status === 99 && item.itemList && item.itemList.length > 0 && (
  202. <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.itemList}>
  203. {item.itemList.map((goods, idx) => {
  204. const levelInfo = LEVEL_MAP[goods.level] || LEVEL_MAP.D;
  205. return (
  206. <View key={idx} style={styles.itemBox}>
  207. <View style={styles.itemImgBox}>
  208. <Image source={{ uri: goods.spu?.cover }} style={styles.itemImg} contentFit="contain" />
  209. </View>
  210. <View style={[styles.levelTag, { backgroundColor: levelInfo.color }]}>
  211. <Text style={styles.levelText}>{levelInfo.title}</Text>
  212. </View>
  213. </View>
  214. );
  215. })}
  216. </ScrollView>
  217. )}
  218. {/* 未完成订单显示订单号 */}
  219. {item.status !== 99 && (
  220. <View style={styles.orderNoRow}>
  221. <Text style={styles.orderNoText} numberOfLines={1}>订单号:{item.tradeNo}</Text>
  222. <TouchableOpacity style={styles.copyBtn}>
  223. <Text style={styles.copyBtnText}>复制</Text>
  224. </TouchableOpacity>
  225. </View>
  226. )}
  227. </View>
  228. </ImageBackground>
  229. );
  230. })
  231. )}
  232. <View style={{ height: 80 }} />
  233. </ScrollView>
  234. {/* 商城订单入口 */}
  235. <TouchableOpacity style={[styles.shopOrderBtn, { bottom: insets.bottom + 154 }]} onPress={goToShopOrders}>
  236. <Image source={{ uri: Images.mine.shopOrder }} style={styles.shopOrderIcon} contentFit="contain" />
  237. </TouchableOpacity>
  238. </ImageBackground>
  239. </View>
  240. );
  241. }
  242. const styles = StyleSheet.create({
  243. container: {
  244. flex: 1,
  245. backgroundColor: '#1a1a2e',
  246. },
  247. background: {
  248. flex: 1,
  249. },
  250. header: {
  251. flexDirection: 'row',
  252. alignItems: 'center',
  253. justifyContent: 'space-between',
  254. paddingHorizontal: 15,
  255. paddingBottom: 10,
  256. },
  257. backBtn: {
  258. width: 40,
  259. height: 40,
  260. justifyContent: 'center',
  261. alignItems: 'center',
  262. },
  263. backText: {
  264. color: '#fff',
  265. fontSize: 32,
  266. fontWeight: 'bold',
  267. },
  268. headerTitle: {
  269. color: '#fff',
  270. fontSize: 15,
  271. fontWeight: 'bold',
  272. },
  273. placeholder: {
  274. width: 40,
  275. },
  276. tabBar: {
  277. flexDirection: 'row',
  278. marginHorizontal: 10,
  279. marginBottom: 15,
  280. },
  281. tabItem: {
  282. width: 80, // 原项目 160rpx
  283. height: 40, // 原项目 80rpx
  284. justifyContent: 'center',
  285. alignItems: 'center',
  286. marginRight: 10, // 原项目 20rpx
  287. },
  288. tabText: {
  289. color: '#000',
  290. fontSize: 14, // 原项目 28rpx
  291. fontWeight: '400',
  292. },
  293. tabTextActive: {
  294. fontWeight: 'bold',
  295. },
  296. scrollView: {
  297. flex: 1,
  298. paddingHorizontal: 10,
  299. },
  300. loadingContainer: {
  301. paddingTop: 100,
  302. alignItems: 'center',
  303. },
  304. emptyContainer: {
  305. paddingTop: 100,
  306. alignItems: 'center',
  307. },
  308. emptyText: {
  309. color: '#999',
  310. fontSize: 14,
  311. },
  312. orderCard: {
  313. marginVertical: 10,
  314. minHeight: 200,
  315. },
  316. cardContent: {
  317. padding: 15,
  318. },
  319. orderTop: {
  320. flexDirection: 'row',
  321. justifyContent: 'space-between',
  322. paddingBottom: 10,
  323. borderBottomWidth: 1,
  324. borderBottomColor: '#CDCDCD',
  325. },
  326. orderTime: {
  327. fontSize: 12,
  328. color: '#666',
  329. },
  330. orderStatus: {
  331. fontSize: 12,
  332. },
  333. timeoutText: {
  334. fontSize: 11,
  335. color: '#999',
  336. marginTop: 5,
  337. },
  338. orderMiddle: {
  339. flexDirection: 'row',
  340. paddingVertical: 12,
  341. },
  342. productImgBg: {
  343. width: 90,
  344. height: 90,
  345. padding: 8,
  346. },
  347. productImg: {
  348. width: '100%',
  349. height: '100%',
  350. borderRadius: 3,
  351. },
  352. productInfo: {
  353. flex: 1,
  354. marginLeft: 12,
  355. justifyContent: 'space-between',
  356. },
  357. productName: {
  358. fontSize: 14,
  359. color: '#333',
  360. fontWeight: 'bold',
  361. },
  362. priceRow: {
  363. flexDirection: 'row',
  364. justifyContent: 'space-between',
  365. },
  366. priceText: {
  367. fontSize: 14,
  368. color: '#333',
  369. },
  370. qtyText: {
  371. fontSize: 14,
  372. color: '#333',
  373. },
  374. discountText: {
  375. fontSize: 12,
  376. color: '#F1423D',
  377. fontWeight: '500',
  378. },
  379. paymentRow: {
  380. flexDirection: 'row',
  381. alignItems: 'center',
  382. },
  383. paymentAmount: {
  384. fontSize: 16,
  385. color: '#F1423D',
  386. fontWeight: 'bold',
  387. },
  388. itemList: {
  389. marginTop: 5,
  390. paddingBottom: 10,
  391. },
  392. itemBox: {
  393. marginRight: 10,
  394. alignItems: 'center',
  395. },
  396. itemImgBox: {
  397. width: 60,
  398. height: 65,
  399. backgroundColor: '#fff',
  400. borderRadius: 3,
  401. overflow: 'hidden',
  402. },
  403. itemImg: {
  404. width: '100%',
  405. height: '100%',
  406. },
  407. levelTag: {
  408. width: '100%',
  409. paddingVertical: 2,
  410. alignItems: 'center',
  411. },
  412. levelText: {
  413. color: '#fff',
  414. fontSize: 10,
  415. fontWeight: 'bold',
  416. },
  417. orderNoRow: {
  418. flexDirection: 'row',
  419. alignItems: 'center',
  420. justifyContent: 'space-between',
  421. height: 29,
  422. },
  423. orderNoText: {
  424. flex: 1,
  425. fontSize: 12,
  426. color: '#666',
  427. },
  428. copyBtn: {
  429. paddingHorizontal: 6,
  430. paddingVertical: 3,
  431. backgroundColor: '#1FA4FF',
  432. borderRadius: 4,
  433. },
  434. copyBtnText: {
  435. color: '#fff',
  436. fontSize: 12,
  437. },
  438. shopOrderBtn: {
  439. position: 'absolute',
  440. left: 10,
  441. width: 60,
  442. height: 63,
  443. },
  444. shopOrderIcon: {
  445. width: '100%',
  446. height: '100%',
  447. },
  448. });