feat(dealer/customer): 实现客户列表的无限滚动和搜索功能- 在客户列表页面添加 InfiniteLoading 组件,实现无限滚动加载- 添加搜索功能,支持按关键词搜索客户

- 优化数据加载逻辑,解决重复请求问题
- 在 Header 组件中增加用户登录状态和信息的检查
This commit is contained in:
2025-09-06 10:29:35 +08:00
parent 42be544acd
commit 6f799e6775
3 changed files with 220 additions and 84 deletions

View File

@@ -1,7 +1,7 @@
import {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
import {Loading, Space, Tabs, TabPane, Tag, Button} from '@nutui/nutui-react-taro'
import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button} from '@nutui/nutui-react-taro'
import {Phone} from '@nutui/icons-react-taro'
import type {ShopDealerApply, ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model";
import {
@@ -26,18 +26,23 @@ const CustomerIndex = () => {
const [loading, setLoading] = useState<boolean>(false)
const [activeTab, setActiveTab] = useState<CustomerStatus>('all')
const [searchValue, _] = useState<string>('')
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
// Tab配置
const tabList = getStatusOptions();
// 获取客户数据
const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus) => {
const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => {
setLoading(true);
try {
const currentPage = resetPage ? 1 : (targetPage || page);
// 构建API参数根据状态筛选
const params: any = {
type: 0
type: 0,
page: currentPage
};
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
if (applyStatus !== undefined) {
@@ -45,21 +50,45 @@ const CustomerIndex = () => {
}
const res = await pageShopDealerApply(params);
if (res?.list) {
let newList: CustomerUser[];
if (res?.list && res.list.length > 0) {
// 正确映射状态
const mappedList = res.list.map(customer => ({
...customer,
customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10)
}));
setList(mappedList);
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
newList = resetPage ? mappedList : list.concat(mappedList);
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
newList = resetPage ? [] : list;
setHasMore(false);
}
setList(newList);
setPage(currentPage);
} catch (error) {
console.error('获取客户数据失败:', error);
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
}
}, [activeTab]);
const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
await fetchCustomerData(activeTab, false, nextPage);
}
// 根据搜索条件筛选数据状态筛选已在API层面处理
const getFilteredList = () => {
@@ -93,10 +122,10 @@ const CustomerIndex = () => {
try {
// 并行获取各状态的数量
const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([
pageShopDealerApply({type:0}), // 全部
pageShopDealerApply({applyStatus: 10,type:0}), // 跟进中
pageShopDealerApply({applyStatus: 20,type:0}), // 已签约
pageShopDealerApply({applyStatus: 30,type:0}) // 已取消
pageShopDealerApply({type: 0}), // 全部
pageShopDealerApply({applyStatus: 10, type: 0}), // 跟进中
pageShopDealerApply({applyStatus: 20, type: 0}), // 已签约
pageShopDealerApply({applyStatus: 30, type: 0}) // 已取消
]);
setStatusCounts({
@@ -122,26 +151,34 @@ const CustomerIndex = () => {
title: '取消成功',
icon: 'success'
});
fetchCustomerData().then();
// 重新加载当前tab的数据
setList([]);
setPage(1);
setHasMore(true);
fetchCustomerData(activeTab, true).then();
fetchStatusCounts().then();
})
};
// 初始化数据
useEffect(() => {
fetchCustomerData().then();
fetchCustomerData(activeTab, true).then();
fetchStatusCounts().then();
}, [fetchCustomerData, fetchStatusCounts]);
}, []);
// 当activeTab变化时重新获取数据
useEffect(() => {
fetchCustomerData(activeTab);
setList([]); // 清空列表
setPage(1); // 重置页码
setHasMore(true); // 重置加载状态
fetchCustomerData(activeTab, true);
}, [activeTab]);
// 渲染客户项
const renderCustomerItem = (customer: CustomerUser) => (
<View key={customer.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3" onClick={() => navTo(`/dealer/customer/add?id=${customer.applyId}`, true)}>
<View className="flex items-center mb-3"
onClick={() => navTo(`/dealer/customer/add?id=${customer.applyId}`, true)}>
<View className="flex-1">
<View className="flex items-center justify-between mb-1">
<Text className="font-semibold text-gray-800 mr-2">
@@ -192,26 +229,49 @@ const CustomerIndex = () => {
const renderCustomerList = () => {
const filteredList = getFilteredList();
if (loading) {
return (
<Space className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2">...</Text>
</Space>
);
}
if (filteredList.length === 0) {
return (
<View className="flex items-center justify-center py-8">
<Text className="text-gray-500"></Text>
</View>
);
}
return (
<View className="p-4">
{filteredList.map(renderCustomerItem)}
<View className="p-4" style={{
height: '75vh',
overflowY: 'auto',
overflowX: 'hidden'
}}>
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
onScroll={() => {
// 滚动事件处理
}}
onScrollToUpper={() => {
// 滚动到顶部事件处理
}}
loadingText={
<>
...
</>
}
loadMoreText={
filteredList.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无客户数据"}
/>
) : (
<View className={'h-12 flex items-center justify-center'}>
<Text className="text-gray-500 text-sm"></Text>
</View>
)
}
>
{loading && filteredList.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
filteredList.map(renderCustomerItem)
)}
</InfiniteLoading>
</View>
);
};