reports.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { StatusBadge } from '@/components/ui/status-badge';
  2. import { Ionicons } from '@expo/vector-icons';
  3. import React, { useState } from 'react';
  4. import { Pressable, ScrollView, Text, View } from 'react-native';
  5. import { SafeAreaView } from 'react-native-safe-area-context';
  6. type ReportTab = '全部' | '征信报告' | '匹配结果';
  7. type ReportTag = {
  8. text: string;
  9. variant: 'primary' | 'secondary' | 'tertiary' | 'error';
  10. };
  11. type Report = {
  12. id: string;
  13. customerName: string;
  14. type: '征信报告' | '匹配结果';
  15. time: string;
  16. status: '已完成' | '解析中' | '待匹配';
  17. score?: string;
  18. matchCount?: number;
  19. matchRate?: string;
  20. tags?: ReportTag[];
  21. };
  22. const TABS: ReportTab[] = ['全部', '征信报告', '匹配结果'];
  23. const REPORTS: Report[] = [
  24. {
  25. id: '1',
  26. customerName: '张德发',
  27. type: '征信报告',
  28. time: '今天 10:42',
  29. status: '已完成',
  30. score: 'B+',
  31. tags: [
  32. { text: '收入稳定', variant: 'primary' },
  33. { text: '查询偏多', variant: 'tertiary' },
  34. { text: '负债率偏高', variant: 'error' },
  35. ],
  36. },
  37. {
  38. id: '2',
  39. customerName: '张德发',
  40. type: '匹配结果',
  41. time: '今天 11:05',
  42. status: '已完成',
  43. matchCount: 3,
  44. matchRate: '92%',
  45. },
  46. {
  47. id: '3',
  48. customerName: '李美华',
  49. type: '征信报告',
  50. time: '昨天 15:20',
  51. status: '已完成',
  52. score: 'A',
  53. tags: [
  54. { text: '信用优秀', variant: 'primary' },
  55. { text: '负债较低', variant: 'secondary' },
  56. ],
  57. },
  58. {
  59. id: '4',
  60. customerName: '王五',
  61. type: '征信报告',
  62. time: '今天 09:16',
  63. status: '解析中',
  64. },
  65. {
  66. id: '5',
  67. customerName: '赵丽',
  68. type: '匹配结果',
  69. time: '5天前',
  70. status: '待匹配',
  71. },
  72. ];
  73. function getStatusVariant(status: Report['status']) {
  74. switch (status) {
  75. case '已完成':
  76. return 'success' as const;
  77. case '解析中':
  78. return 'secondary' as const;
  79. case '待匹配':
  80. return 'error' as const;
  81. default:
  82. return 'secondary' as const;
  83. }
  84. }
  85. export default function ReportsScreen() {
  86. const [activeTab, setActiveTab] = useState<ReportTab>('全部');
  87. const filteredReports = REPORTS.filter(
  88. (item) => activeTab === '全部' || item.type === activeTab
  89. );
  90. const completedCount = REPORTS.filter((item) => item.status === '已完成').length;
  91. return (
  92. <SafeAreaView className="flex-1 bg-surface" edges={['top']}>
  93. <ScrollView
  94. className="flex-1"
  95. contentContainerClassName="px-5 pt-3 pb-24"
  96. showsVerticalScrollIndicator={false}
  97. >
  98. <Text className="mb-2 text-3xl font-extrabold tracking-tight text-on-surface">
  99. 报表
  100. </Text>
  101. <Text className="mb-5 text-base leading-7 text-on-surface-variant">
  102. 汇总查看征信分析结果、匹配建议和当前处理进度
  103. </Text>
  104. <View className="mb-5 flex-row gap-2.5">
  105. <View className="flex-1 items-center rounded-2xl border border-outline-variant bg-surface-container-lowest px-3 py-3.5">
  106. <Text className="text-2xl font-extrabold text-primary">{REPORTS.length}</Text>
  107. <Text className="mt-2 text-xs font-bold uppercase tracking-widest text-outline">
  108. 总报告数
  109. </Text>
  110. </View>
  111. <View className="flex-1 items-center rounded-2xl border border-outline-variant bg-surface-container-lowest px-3 py-3.5">
  112. <Text className="text-2xl font-extrabold text-primary">{completedCount}</Text>
  113. <Text className="mt-2 text-xs font-bold uppercase tracking-widest text-outline">
  114. 已完成
  115. </Text>
  116. </View>
  117. <View className="flex-1 items-center rounded-2xl border border-outline-variant bg-surface-container-lowest px-3 py-3.5">
  118. <Text className="text-2xl font-extrabold text-primary">92%</Text>
  119. <Text className="mt-2 text-xs font-bold uppercase tracking-widest text-outline">
  120. 最高匹配
  121. </Text>
  122. </View>
  123. </View>
  124. <View className="mb-5 rounded-2xl bg-surface-container-low p-1">
  125. <View className="flex-row">
  126. {TABS.map((tab) => {
  127. const active = activeTab === tab;
  128. return (
  129. <Pressable
  130. key={tab}
  131. onPress={() => setActiveTab(tab)}
  132. className={`flex-1 rounded-xl py-2.5 ${
  133. active ? 'bg-surface-container-lowest' : ''
  134. }`}
  135. style={({ pressed }) => ({
  136. opacity: pressed ? 0.88 : 1,
  137. })}
  138. >
  139. <Text
  140. className={`text-center text-sm font-bold ${
  141. active ? 'text-primary' : 'text-on-surface-variant'
  142. }`}
  143. >
  144. {tab}
  145. </Text>
  146. </Pressable>
  147. );
  148. })}
  149. </View>
  150. </View>
  151. <View className="gap-3">
  152. {filteredReports.map((report) => (
  153. <Pressable
  154. key={report.id}
  155. className="rounded-2xl border border-outline-variant bg-surface-container-lowest px-4 py-4"
  156. style={({ pressed }) => ({
  157. opacity: pressed ? 0.93 : 1,
  158. })}
  159. >
  160. <View className="mb-3 flex-row items-start justify-between gap-3">
  161. <View className="flex-1 flex-row items-center gap-3">
  162. <View
  163. className={`h-11 w-11 items-center justify-center rounded-2xl ${
  164. report.type === '征信报告' ? 'bg-blue-50' : 'bg-green-50'
  165. }`}
  166. >
  167. <Ionicons
  168. name={
  169. report.type === '征信报告'
  170. ? 'document-text-outline'
  171. : 'git-compare-outline'
  172. }
  173. size={20}
  174. color={report.type === '征信报告' ? '#2563eb' : '#16a34a'}
  175. />
  176. </View>
  177. <View className="flex-1">
  178. <Text className="text-lg font-bold text-on-surface">
  179. {report.customerName}
  180. </Text>
  181. <Text className="mt-1 text-sm text-on-surface-variant">
  182. {report.type}
  183. </Text>
  184. </View>
  185. </View>
  186. <View className="items-end">
  187. <Text className="mb-1.5 text-xs text-on-surface-variant">{report.time}</Text>
  188. <StatusBadge
  189. text={report.status}
  190. variant={getStatusVariant(report.status)}
  191. />
  192. </View>
  193. </View>
  194. {report.type === '征信报告' && report.status === '已完成' ? (
  195. <View>
  196. <View className="mb-3 flex-row items-center gap-3">
  197. <View className="h-14 w-14 items-center justify-center rounded-full border-4 border-primary-fixed bg-surface-container-lowest">
  198. <Text className="text-lg font-extrabold text-primary">
  199. {report.score}
  200. </Text>
  201. </View>
  202. <View className="flex-1">
  203. <Text className="mb-1.5 text-sm text-on-surface-variant">关键标签</Text>
  204. <View className="flex-row flex-wrap gap-2">
  205. {report.tags?.map((tag) => (
  206. <StatusBadge
  207. key={`${report.id}-${tag.text}`}
  208. text={tag.text}
  209. variant={tag.variant}
  210. />
  211. ))}
  212. </View>
  213. </View>
  214. </View>
  215. <Text className="text-sm leading-6 text-on-surface-variant">
  216. 建议可申请额度:20万-35万,优先推荐更看重流水稳定性的产品。
  217. </Text>
  218. </View>
  219. ) : null}
  220. {report.type === '匹配结果' && report.status === '已完成' ? (
  221. <View className="flex-row gap-2.5">
  222. <View className="flex-1 rounded-xl bg-surface-container-low px-4 py-3.5">
  223. <Text className="text-2xl font-bold text-on-surface">
  224. {report.matchCount}
  225. </Text>
  226. <Text className="mt-1 text-xs text-on-surface-variant">匹配产品数</Text>
  227. </View>
  228. <View className="flex-1 rounded-xl bg-surface-container-low px-4 py-3.5">
  229. <Text className="text-2xl font-bold text-primary">
  230. {report.matchRate}
  231. </Text>
  232. <Text className="mt-1 text-xs text-on-surface-variant">最高匹配度</Text>
  233. </View>
  234. </View>
  235. ) : null}
  236. {report.status === '解析中' ? (
  237. <View className="mt-1">
  238. <View className="mb-2 flex-row items-center justify-between">
  239. <Text className="text-sm text-on-surface-variant">正在生成报告...</Text>
  240. <Text className="text-sm font-bold text-primary">72%</Text>
  241. </View>
  242. <View className="h-1.5 rounded-full bg-surface-container">
  243. <View
  244. className="h-full rounded-full bg-primary-container"
  245. style={{ width: '72%' }}
  246. />
  247. </View>
  248. </View>
  249. ) : null}
  250. {report.status === '待匹配' ? (
  251. <Text className="text-sm leading-6 text-on-surface-variant">
  252. 当前客户信息已同步,待进入智能匹配流程生成推荐结果。
  253. </Text>
  254. ) : null}
  255. {report.status === '已完成' ? (
  256. <View className="mt-3 flex-row gap-2.5">
  257. <Pressable
  258. className="flex-1 items-center rounded-xl bg-primary-container py-2.5"
  259. style={({ pressed }) => ({
  260. opacity: pressed ? 0.88 : 1,
  261. })}
  262. >
  263. <Text className="text-sm font-bold text-on-primary">
  264. {report.type === '征信报告' ? '查看详情' : '查看匹配'}
  265. </Text>
  266. </Pressable>
  267. <Pressable
  268. className="flex-1 items-center rounded-xl bg-surface-container-high py-2.5"
  269. style={({ pressed }) => ({
  270. opacity: pressed ? 0.88 : 1,
  271. })}
  272. >
  273. <Text className="text-sm font-semibold text-on-surface">
  274. {report.type === '征信报告' ? '智能匹配' : '重新匹配'}
  275. </Text>
  276. </Pressable>
  277. </View>
  278. ) : null}
  279. </Pressable>
  280. ))}
  281. </View>
  282. </ScrollView>
  283. </SafeAreaView>
  284. );
  285. }