From 0e947401dc5b91850f36becdaea7df50c77338ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Tue, 13 May 2025 14:34:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98=E6=89=8B?= =?UTF-8?q?=20-=20=E5=AF=B9=E6=8E=A5=E6=B5=81=E9=87=8F=E6=B1=A0=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/traffic-pool/page.tsx | 349 +++++++++++------- ...issociateListWithInCompanyV1Controller.php | 2 +- 2 files changed, 213 insertions(+), 138 deletions(-) diff --git a/Cunkebao/app/traffic-pool/page.tsx b/Cunkebao/app/traffic-pool/page.tsx index e5f2736a..07a7d9f0 100644 --- a/Cunkebao/app/traffic-pool/page.tsx +++ b/Cunkebao/app/traffic-pool/page.tsx @@ -56,18 +56,51 @@ interface ApiResponse { data: T } +// 修改流量池数据类型定义 +interface TrafficPoolUser { + id: string + avatar: string + nickname: string + name: string + wechatId: string + phone: string + region: string + note: string + status: number + createTime: string + fromd: string + assignedTo: string + category: "potential" | "customer" | "lost" + tags: UserTag[] +} + +interface TrafficPoolResponse { + list: TrafficPoolUser[] + pagination: { + total: number + current: number + pageSize: number + totalPages: number + } + statistics: { + total: number + todayNew: number + } +} + export default function TrafficPoolPage() { const router = useRouter() const searchParams = useSearchParams() const [users, setUsers] = useState([]) - const [loading, setLoading] = useState(true) // Start with loading state - const [activeCategory, setActiveCategory] = useState("potential") // Changed default from "all" to "potential" + const [loading, setLoading] = useState(true) + const [activeCategory, setActiveCategory] = useState("potential") const [sourceFilter, setSourceFilter] = useState("all") const [statusTypes, setStatusTypes] = useState([]) - const [statusFilter, setStatusFilter] = useState("all") + const [statusFilter, setStatusFilter] = useState("all") const [searchQuery, setSearchQuery] = useState("") const [currentPage, setCurrentPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) + const [hasMore, setHasMore] = useState(true) + const [isFetching, setIsFetching] = useState(false) const [stats, setStats] = useState({ total: 0, todayNew: 0, @@ -76,24 +109,45 @@ export default function TrafficPoolPage() { const [showUserDetail, setShowUserDetail] = useState(false) const { toast } = useToast() + const observerRef = useRef(null) + const loadingRef = useRef(null) const abortControllerRef = useRef(null) const debouncedSearchQuery = useDebounce(searchQuery, 300) - const fetchUsers = useCallback(async () => { + // 添加格式化时间的函数 + const formatDateTime = (dateString: string) => { + if (!dateString) return '--'; + try { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }).replace(/\//g, '-'); + } catch (error) { + return dateString; + } + }; + + const fetchUsers = useCallback(async (page: number = 1, isNewSearch: boolean = false) => { try { if (abortControllerRef.current) { abortControllerRef.current.abort() } abortControllerRef.current = new AbortController() + setIsFetching(true) - setLoading(true) const params = new URLSearchParams({ - page: currentPage.toString(), - pageSize: "10", + page: page.toString(), + limit: "30", // 设置每页显示30条 search: debouncedSearchQuery, category: activeCategory, - source: sourceFilter, + source: sourceFilter !== "all" ? sourceFilter : "", status: statusFilter === "all" ? "" : statusFilter, }) @@ -103,62 +157,53 @@ export default function TrafficPoolPage() { params.append("wechatSource", sourceParam) } - const response = await fetch(`/api/users?${params}`, { - signal: abortControllerRef.current.signal, - }) + const response = await api.get>(`/v1/traffic/pool?${params.toString()}`) - if (!response.ok) throw new Error("请求失败") - - const data = await response.json() - - // 为每个用户添加标签 - const usersWithTags = data.users.map((user: TrafficUser) => { - // 生成随机标签 - const tagPool = [ - { name: "潜在客户", color: "bg-blue-100 text-blue-800" }, - { name: "高意向", color: "bg-green-100 text-green-800" }, - { name: "已成交", color: "bg-purple-100 text-purple-800" }, - { name: "需跟进", color: "bg-yellow-100 text-yellow-800" }, - { name: "活跃用户", color: "bg-indigo-100 text-indigo-800" }, - { name: "沉默用户", color: "bg-gray-100 text-gray-800" }, - { name: "企业客户", color: "bg-red-100 text-red-800" }, - { name: "个人用户", color: "bg-pink-100 text-pink-800" }, - { name: "新增好友", color: "bg-emerald-100 text-emerald-800" }, - { name: "老客户", color: "bg-amber-100 text-amber-800" }, - ] - - const tags = Array.from({ length: Math.floor(Math.random() * 4) + 1 }, () => { - const randomTag = tagPool[Math.floor(Math.random() * tagPool.length)] - return { - id: `tag-${Math.random().toString(36).substring(2, 9)}`, - name: randomTag.name, - color: randomTag.color, - } - }) - - return { + if (response.code === 200) { + const { list, pagination, statistics } = response.data + + // 转换数据格式 + const transformedUsers = list.map(user => ({ ...user, - tags, - } - }) + id: user.id.toString(), + status: getStatusFromCode(user.status), + tags: user.tags || [], + category: user.category || "potential", + addTime: formatDateTime(user.createTime), + source: user.fromd || '未知来源', + nickname: user.name || user.nickname || '未知用户' + })) - setUsers(usersWithTags) - setTotalPages(data.pagination.totalPages) - setStats(data.stats) + // 更新用户列表 + setUsers(prev => isNewSearch ? transformedUsers : [...prev, ...transformedUsers]) + setCurrentPage(page) + setHasMore(list.length > 0 && page < pagination.totalPages) + setStats({ + total: statistics.total, + todayNew: statistics.todayNew + }) + } else { + toast({ + title: "获取数据失败", + description: response.msg || "请稍后重试", + variant: "destructive", + }) + } } catch (error) { if (error instanceof Error && error.name === "AbortError") { return } toast({ - title: "错误", - description: "获取数据失败,请稍后重试", + title: "获取数据失败", + description: "请检查网络连接或稍后重试", variant: "destructive", }) } finally { + setIsFetching(false) setLoading(false) } - }, [currentPage, debouncedSearchQuery, activeCategory, sourceFilter, statusFilter, toast, searchParams]) + }, [debouncedSearchQuery, activeCategory, sourceFilter, statusFilter, searchParams]) const fetchStatusTypes = useCallback(async () => { try { @@ -183,21 +228,73 @@ export default function TrafficPoolPage() { } }, []) + // 处理搜索 + const handleSearch = useCallback(() => { + setUsers([]) + setCurrentPage(1) + setHasMore(true) + fetchUsers(1, true) + }, [fetchUsers]) + + // 设置 IntersectionObserver useEffect(() => { - fetchUsers() - fetchStatusTypes() + observerRef.current = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasMore && !isFetching) { + fetchUsers(currentPage + 1) + } + }, + { threshold: 0.5 } + ) + + return () => { + if (observerRef.current) { + observerRef.current.disconnect() + } + } + }, [fetchUsers, currentPage, hasMore, isFetching]) + + // 观察加载指示器 + useEffect(() => { + if (loadingRef.current && observerRef.current) { + observerRef.current.observe(loadingRef.current) + } + + return () => { + if (loadingRef.current && observerRef.current) { + observerRef.current.unobserve(loadingRef.current) + } + } + }, [loadingRef.current, observerRef.current]) + + // 初始加载 + useEffect(() => { + fetchUsers(1, true) return () => { if (abortControllerRef.current) { abortControllerRef.current.abort() } } - }, [fetchUsers, fetchStatusTypes]) + }, [fetchUsers]) const handleUserClick = (user: TrafficUser) => { setSelectedUser(user) setShowUserDetail(true) } + // 添加状态码转换函数 + const getStatusFromCode = (statusCode: number): "pending" | "added" | "failed" => { + const statusMap: Record = { + 1: "pending", // 待处理 + 2: "pending", // 处理中 + 3: "added", // 已添加 + 4: "failed", // 已拒绝 + 5: "failed", // 已过期 + 6: "failed", // 已取消 + } + return statusMap[statusCode] || "pending" + } + return (
@@ -307,7 +404,7 @@ export default function TrafficPoolPage() { {/* 用户列表 */}
- {loading ? ( + {loading && users.length === 0 ? (
加载中...
@@ -315,97 +412,75 @@ export default function TrafficPoolPage() { ) : users.length === 0 ? (
暂无数据
-
) : ( - users.map((user) => ( - handleUserClick(user)} - > -
- -
-
-
{user.nickname}
-
- {user.status === "added" ? "已添加" : user.status === "pending" ? "待处理" : "已失败"} + <> + {users.map((user) => ( + handleUserClick(user)} + > +
+ +
+
+
{user.nickname}
+
+ {user.status === "added" ? "已添加" : user.status === "pending" ? "待处理" : "已失败"} +
+
+
微信号: {user.wechatId}
+
来源: {user.source}
+
添加时间: {user.addTime}
+ + {/* 标签展示 */} +
+ {user.tags.slice(0, 2).map((tag) => ( + + {tag.name} + + ))} + {user.tags.length > 2 && ( + + +{user.tags.length - 2} + + )}
-
微信号: {user.wechatId}
-
来源: {user.source}
-
添加时间: {new Date(user.addTime).toLocaleString()}
- - {/* 标签展示 */} -
- {user.tags.slice(0, 2).map((tag) => ( - - {tag.name} - - ))} - {user.tags.length > 2 && ( - - +{user.tags.length - 2} - - )} -
+
+ ))} + + {/* 加载更多指示器 */} + {hasMore && ( +
+ {isFetching && }
- - )) + )} + + {/* 显示加载状态和总数 */} +
+ {stats.total > 0 && ( + + 已加载 {users.length} / {stats.total} 条记录 + + )} +
+ )}
- - {/* 分页 */} - {!loading && users.length > 0 && ( - - - - { - e.preventDefault() - setCurrentPage((prev) => Math.max(1, prev - 1)) - }} - /> - - {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( - - { - e.preventDefault() - setCurrentPage(page) - }} - > - {page} - - - ))} - - { - e.preventDefault() - setCurrentPage((prev) => Math.min(totalPages, prev + 1)) - }} - /> - - - - )}
@@ -458,7 +533,7 @@ export default function TrafficPoolPage() {
添加时间
-
{new Date(selectedUser.addTime).toLocaleString()}
+
{selectedUser.addTime}
diff --git a/Server/application/cunkebao/controller/traffic/GetDissociateListWithInCompanyV1Controller.php b/Server/application/cunkebao/controller/traffic/GetDissociateListWithInCompanyV1Controller.php index b725207c..6558de34 100644 --- a/Server/application/cunkebao/controller/traffic/GetDissociateListWithInCompanyV1Controller.php +++ b/Server/application/cunkebao/controller/traffic/GetDissociateListWithInCompanyV1Controller.php @@ -41,7 +41,7 @@ class GetDissociateListWithInCompanyV1Controller extends BaseController $query = TrafficPoolModel::alias('t') ->field( [ - 't.identifier', 't.mobile', 't.wechatId', + 't.identifier nickname', 't.mobile', 't.wechatId', 't.identifier', 's.id', 's.fromd', 's.status', 's.createTime' ] )