import { ActivityIndicator, Toast } from '@ant-design/react-native'; import { Ionicons } from '@expo/vector-icons'; import clsx from 'clsx'; import { BlurView } from 'expo-blur'; import { Link, Stack } from 'expo-router'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Pressable, Text, TextInput, View } from 'react-native'; import Animated, { Extrapolation, interpolate, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StatusBadge } from '@/components/ui/status-badge'; import api from '@/utils/api'; import { getApiCache } from '@/utils/storage'; import type { ListResponse } from '@/utils/api'; export type CustomerLoanStatus = 'idle' | 'matched' | 'unmatch' | 'pending' | 'completed' | 'all' | undefined; export const CustomerLoanStatusText: Record, string> = { all: '所有', idle: '待匹配', pending: '匹配中', matched: '已匹配', completed: '已完成', unmatch: '匹配失败', }; export type Customer = { id: string; name: string; mobile: string; loan_status: CustomerLoanStatus; note: string; score?: string; updatetime: string; }; const PAGE_SIZE = 15; const CACHE_KEY = 'customer_first'; function CustomerCard({ item }: { item: Customer }) { return ( {item.name[0]} {item.name} {item.mobile} {item.note} {item.loan_status} {item.score ? ( 评分 {item.score} ) : null} {item.updatetime} {item.loan_status} 编辑资料 ); } const renderCustomer = ({ item }: { item: Customer }) => ; const keyExtractor = (item: Customer) => item.id; export default function CustomerScreens() { const [searchKey, setSearchKey] = useState(''); const [list, setList] = useState([]); const [loading, setLoading] = useState(true); const [activeStatus, setActiveStatus] = useState('idle'); const startRef = useRef(0); const loanStatusRef = useRef('idle'); const hasMoreRef = useRef(false); const loadingRef = useRef(false); const searchTimerRef = useRef | null>(null); const insets = useSafeAreaInsets(); const scrollOffsetY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler((e) => { scrollOffsetY.value = e.contentOffset.y; }); const headerStyle = useAnimatedStyle(() => ({ opacity: interpolate(scrollOffsetY.value, [0, 24 + insets.top], [0, 1], Extrapolation.CLAMP), })); const load = useCallback(async (start: number, loanStatus: CustomerLoanStatus, q?: string) => { if (loadingRef.current) return; loadingRef.current = true; loanStatusRef.current = loanStatus; startRef.current = start; setLoading(true); if (start === 0 && !loanStatus && !q) { const cache = getApiCache().getObject>(CACHE_KEY); if (cache) { setList(cache.list); startRef.current = cache.list.length; hasMoreRef.current = cache.list.length >= PAGE_SIZE; setLoading(false); loadingRef.current = false; return; } } try { const res = await api.post>('customer/list', { start, size: PAGE_SIZE, loanStatus, q, }); if (loanStatusRef.current !== loanStatus) return; const next = res?.list ?? []; if (start === 0 && !loanStatus && !q && next.length) { getApiCache().setObject(CACHE_KEY, res, 60); } hasMoreRef.current = next.length >= PAGE_SIZE; setList((prev) => (start === 0 ? next : prev.concat(next))); startRef.current = start + next.length; if (!next.length && start > 0) { Toast.offline('没有更多数据可加载'); } } catch { Toast.fail('加载列表失败!'); } finally { setLoading(false); loadingRef.current = false; } }, []); useEffect(() => { load(0, 'idle'); }, [load]); const loadMore = useCallback(() => { if (!hasMoreRef.current || loadingRef.current) return; load(startRef.current, loanStatusRef.current, searchKey); }, [load, searchKey]); const handleSelectStatus = useCallback( (status: CustomerLoanStatus) => { const next = activeStatus === status ? undefined : status; setActiveStatus(next); load(0, next, searchKey); }, [activeStatus, load, searchKey] ); const handleSearch = useCallback( (k: string) => { setSearchKey(k); if (searchTimerRef.current) clearTimeout(searchTimerRef.current); searchTimerRef.current = setTimeout(() => { load(0, loanStatusRef.current, k); }, 600); }, [load] ); const handleReset = useCallback(() => { handleSearch(''); }, [handleSearch]); useEffect(() => { return () => { if (searchTimerRef.current) clearTimeout(searchTimerRef.current); }; }, []); const ListHeader = ( <> 客户 统一跟进客户资料、征信进度和产品匹配状态 {searchKey.length > 0 ? ( ) : null} {Object.entries(CustomerLoanStatusText).map(([key, label]) => { const active = activeStatus === (key as CustomerLoanStatus); return ( handleSelectStatus(key as CustomerLoanStatus)} className={clsx('rounded-md px-1 border border-transparent active:opacity-84', { 'border-primary-container': active, })} > {label} ); })} ); const ListEmpty = !loading ? ( 暂无匹配客户 ) : null; const ListFooter = loading && list.length > 0 ? ( 加载中 ) : null; const ListInitialLoading = loading && list.length === 0 ? ( 加载中 ) : null; return ( <> ( 客户 ), }} /> ); }