index.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { useRouter } from 'expo-router';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import {
  4. ActivityIndicator,
  5. FlatList,
  6. RefreshControl,
  7. StatusBar,
  8. StyleSheet,
  9. Text,
  10. TouchableOpacity,
  11. View
  12. } from 'react-native';
  13. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  14. import { getMessageList } from '@/services/base';
  15. interface MessageItem {
  16. id: string;
  17. title: string;
  18. content: string;
  19. createTime: string;
  20. type?: string;
  21. }
  22. export default function MessageScreen() {
  23. const router = useRouter();
  24. const insets = useSafeAreaInsets();
  25. const [messages, setMessages] = useState<MessageItem[]>([]);
  26. const [loading, setLoading] = useState(false);
  27. const [refreshing, setRefreshing] = useState(false);
  28. const [pageNum, setPageNum] = useState(1);
  29. const [hasMore, setHasMore] = useState(true);
  30. const pageSize = 10;
  31. const loadData = useCallback(async (page: number, isRefresh = false) => {
  32. if (loading && !isRefresh) return;
  33. try {
  34. if (isRefresh) {
  35. setRefreshing(true);
  36. } else {
  37. setLoading(true);
  38. }
  39. const res = await getMessageList(page, pageSize);
  40. const records = res?.records || [];
  41. if (isRefresh) {
  42. setMessages(records);
  43. } else {
  44. setMessages(prev => [...prev, ...records]);
  45. }
  46. setHasMore(records.length >= pageSize);
  47. setPageNum(page);
  48. } catch (error) {
  49. console.error('获取消息失败:', error);
  50. } finally {
  51. setLoading(false);
  52. setRefreshing(false);
  53. }
  54. }, [loading]);
  55. useEffect(() => {
  56. loadData(1, true);
  57. }, []);
  58. const handleRefresh = () => {
  59. loadData(1, true);
  60. };
  61. const handleLoadMore = () => {
  62. if (hasMore && !loading) {
  63. loadData(pageNum + 1);
  64. }
  65. };
  66. const handleBack = () => {
  67. router.back();
  68. };
  69. const renderItem = ({ item }: { item: MessageItem }) => (
  70. <View style={styles.cell}>
  71. <View style={styles.cellHeader}>
  72. <View style={styles.icon}>
  73. <Text style={styles.iconText}>🔔</Text>
  74. </View>
  75. <Text style={styles.title} numberOfLines={1}>{item.title}</Text>
  76. <Text style={styles.time}>{item.createTime}</Text>
  77. </View>
  78. <Text style={styles.content}>{item.content}</Text>
  79. </View>
  80. );
  81. const renderEmpty = () => (
  82. <View style={styles.emptyBox}>
  83. <Text style={styles.emptyText}>暂无消息</Text>
  84. </View>
  85. );
  86. const renderFooter = () => {
  87. if (!hasMore && messages.length > 0) {
  88. return (
  89. <View style={styles.footer}>
  90. <Text style={styles.footerText}>没有更多了</Text>
  91. </View>
  92. );
  93. }
  94. if (loading && messages.length > 0) {
  95. return (
  96. <View style={styles.footer}>
  97. <ActivityIndicator size="small" color="#999" />
  98. </View>
  99. );
  100. }
  101. return null;
  102. };
  103. return (
  104. <View style={styles.container}>
  105. <StatusBar barStyle="light-content" />
  106. {/* 顶部导航 */}
  107. <View style={[styles.header, { paddingTop: insets.top }]}>
  108. <TouchableOpacity style={styles.backBtn} onPress={handleBack}>
  109. <Text style={styles.backIcon}>‹</Text>
  110. </TouchableOpacity>
  111. <Text style={styles.headerTitle}>消息</Text>
  112. <View style={styles.placeholder} />
  113. </View>
  114. <FlatList
  115. data={messages}
  116. renderItem={renderItem}
  117. keyExtractor={(item, index) => item.id || String(index)}
  118. contentContainerStyle={[
  119. styles.listContent,
  120. messages.length === 0 && styles.emptyList
  121. ]}
  122. refreshControl={
  123. <RefreshControl
  124. refreshing={refreshing}
  125. onRefresh={handleRefresh}
  126. colors={['#FC7D2E']}
  127. tintColor="#FC7D2E"
  128. />
  129. }
  130. onEndReached={handleLoadMore}
  131. onEndReachedThreshold={0.2}
  132. ListEmptyComponent={!loading ? renderEmpty : null}
  133. ListFooterComponent={renderFooter}
  134. />
  135. </View>
  136. );
  137. }
  138. const styles = StyleSheet.create({
  139. container: {
  140. flex: 1,
  141. backgroundColor: '#1a1a2e',
  142. },
  143. header: {
  144. flexDirection: 'row',
  145. alignItems: 'center',
  146. justifyContent: 'space-between',
  147. paddingHorizontal: 10,
  148. height: 80,
  149. backgroundColor: 'transparent',
  150. },
  151. backBtn: {
  152. width: 40,
  153. height: 40,
  154. justifyContent: 'center',
  155. alignItems: 'center',
  156. },
  157. backIcon: {
  158. fontSize: 32,
  159. color: '#fff',
  160. fontWeight: 'bold',
  161. },
  162. headerTitle: {
  163. fontSize: 16,
  164. fontWeight: 'bold',
  165. color: '#fff',
  166. },
  167. placeholder: {
  168. width: 40,
  169. },
  170. listContent: {
  171. paddingHorizontal: 14,
  172. paddingBottom: 30,
  173. },
  174. emptyList: {
  175. flex: 1,
  176. },
  177. cell: {
  178. backgroundColor: '#fff',
  179. borderRadius: 10,
  180. padding: 15,
  181. marginTop: 10,
  182. },
  183. cellHeader: {
  184. flexDirection: 'row',
  185. alignItems: 'center',
  186. },
  187. icon: {
  188. width: 20,
  189. height: 20,
  190. borderRadius: 10,
  191. backgroundColor: '#333',
  192. justifyContent: 'center',
  193. alignItems: 'center',
  194. },
  195. iconText: {
  196. fontSize: 10,
  197. },
  198. title: {
  199. flex: 1,
  200. fontSize: 14,
  201. fontWeight: 'bold',
  202. color: '#333',
  203. marginLeft: 10,
  204. },
  205. time: {
  206. fontSize: 12,
  207. color: '#999',
  208. },
  209. content: {
  210. fontSize: 14,
  211. color: '#666',
  212. marginTop: 8,
  213. lineHeight: 20,
  214. },
  215. emptyBox: {
  216. flex: 1,
  217. justifyContent: 'center',
  218. alignItems: 'center',
  219. paddingTop: 200,
  220. },
  221. emptyText: {
  222. fontSize: 14,
  223. color: '#999',
  224. },
  225. footer: {
  226. paddingVertical: 20,
  227. alignItems: 'center',
  228. },
  229. footerText: {
  230. fontSize: 12,
  231. color: '#999',
  232. },
  233. });