私域操盘手 - 修复流量池页面不会自动加载状态列表选择器的问题
This commit is contained in:
@@ -37,7 +37,7 @@ interface TrafficUser {
|
|||||||
phone: string
|
phone: string
|
||||||
region: string
|
region: string
|
||||||
note: string
|
note: string
|
||||||
status: "pending" | "added" | "failed"
|
status: number
|
||||||
addTime: string
|
addTime: string
|
||||||
source: string
|
source: string
|
||||||
assignedTo: string
|
assignedTo: string
|
||||||
@@ -48,6 +48,7 @@ interface TrafficUser {
|
|||||||
interface StatusType {
|
interface StatusType {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
@@ -144,37 +145,43 @@ export default function TrafficPoolPage() {
|
|||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
page: page.toString(),
|
page: page.toString(),
|
||||||
limit: "30", // 设置每页显示30条
|
limit: "30",
|
||||||
search: debouncedSearchQuery,
|
search: debouncedSearchQuery,
|
||||||
category: activeCategory,
|
category: activeCategory,
|
||||||
source: sourceFilter !== "all" ? sourceFilter : "",
|
source: sourceFilter !== "all" ? sourceFilter : "",
|
||||||
status: statusFilter === "all" ? "" : statusFilter,
|
status: statusFilter === "all" ? "" : statusFilter,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 检查是否有来源参数
|
|
||||||
const sourceParam = searchParams?.get("source")
|
const sourceParam = searchParams?.get("source")
|
||||||
if (sourceParam) {
|
if (sourceParam) {
|
||||||
params.append("wechatSource", sourceParam)
|
params.append("wechatSource", sourceParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.get<ApiResponse<TrafficPoolResponse>>(`/v1/traffic/pool?${params.toString()}`)
|
const response = await api.get<ApiResponse<TrafficPoolResponse>>(`/v1/traffic/pool?${params.toString()}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
} as any)
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
const { list, pagination, statistics } = response.data
|
const { list, pagination, statistics } = response.data
|
||||||
|
|
||||||
// 转换数据格式
|
|
||||||
const transformedUsers = list.map(user => ({
|
const transformedUsers = list.map(user => ({
|
||||||
...user,
|
|
||||||
id: user.id.toString(),
|
id: user.id.toString(),
|
||||||
status: getStatusFromCode(user.status),
|
avatar: user.avatar,
|
||||||
tags: user.tags || [],
|
nickname: user.name || user.nickname || '未知用户',
|
||||||
category: user.category || "potential",
|
wechatId: user.wechatId,
|
||||||
|
phone: user.phone,
|
||||||
|
region: user.region,
|
||||||
|
note: user.note,
|
||||||
|
status: user.status,
|
||||||
addTime: formatDateTime(user.createTime),
|
addTime: formatDateTime(user.createTime),
|
||||||
source: user.fromd || '未知来源',
|
source: user.fromd || '未知来源',
|
||||||
nickname: user.name || user.nickname || '未知用户'
|
assignedTo: user.assignedTo,
|
||||||
|
category: user.category || "potential",
|
||||||
|
tags: user.tags || []
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 更新用户列表
|
|
||||||
setUsers(prev => isNewSearch ? transformedUsers : [...prev, ...transformedUsers])
|
setUsers(prev => isNewSearch ? transformedUsers : [...prev, ...transformedUsers])
|
||||||
setCurrentPage(page)
|
setCurrentPage(page)
|
||||||
setHasMore(list.length > 0 && page < pagination.totalPages)
|
setHasMore(list.length > 0 && page < pagination.totalPages)
|
||||||
@@ -207,7 +214,11 @@ export default function TrafficPoolPage() {
|
|||||||
|
|
||||||
const fetchStatusTypes = useCallback(async () => {
|
const fetchStatusTypes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get<ApiResponse<StatusType[]>>('/v1/traffic/pool/types')
|
const response = await api.get<ApiResponse<StatusType[]>>('/v1/traffic/pool/types', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
} as any)
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
setStatusTypes(response.data)
|
setStatusTypes(response.data)
|
||||||
@@ -236,9 +247,9 @@ export default function TrafficPoolPage() {
|
|||||||
fetchUsers(1, true)
|
fetchUsers(1, true)
|
||||||
}, [fetchUsers])
|
}, [fetchUsers])
|
||||||
|
|
||||||
// 设置 IntersectionObserver
|
// 初始化 IntersectionObserver
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
observerRef.current = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
if (entries[0].isIntersecting && hasMore && !isFetching) {
|
if (entries[0].isIntersecting && hasMore && !isFetching) {
|
||||||
fetchUsers(currentPage + 1)
|
fetchUsers(currentPage + 1)
|
||||||
@@ -247,35 +258,30 @@ export default function TrafficPoolPage() {
|
|||||||
{ threshold: 0.5 }
|
{ threshold: 0.5 }
|
||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
if (loadingRef.current) {
|
||||||
if (observerRef.current) {
|
observer.observe(loadingRef.current)
|
||||||
observerRef.current.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [fetchUsers, currentPage, hasMore, isFetching])
|
|
||||||
|
|
||||||
// 观察加载指示器
|
|
||||||
useEffect(() => {
|
|
||||||
if (loadingRef.current && observerRef.current) {
|
|
||||||
observerRef.current.observe(loadingRef.current)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (loadingRef.current && observerRef.current) {
|
if (loadingRef.current) {
|
||||||
observerRef.current.unobserve(loadingRef.current)
|
observer.unobserve(loadingRef.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [loadingRef.current, observerRef.current])
|
}, [hasMore, isFetching, currentPage, fetchUsers])
|
||||||
|
|
||||||
// 初始加载
|
// 初始化数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
fetchStatusTypes()
|
||||||
fetchUsers(1, true)
|
fetchUsers(1, true)
|
||||||
return () => {
|
}, [])
|
||||||
if (abortControllerRef.current) {
|
|
||||||
abortControllerRef.current.abort()
|
// 监听筛选条件变化
|
||||||
}
|
useEffect(() => {
|
||||||
}
|
setUsers([])
|
||||||
}, [fetchUsers])
|
setCurrentPage(1)
|
||||||
|
setHasMore(true)
|
||||||
|
fetchUsers(1, true)
|
||||||
|
}, [activeCategory, sourceFilter, statusFilter, debouncedSearchQuery])
|
||||||
|
|
||||||
const handleUserClick = (user: TrafficUser) => {
|
const handleUserClick = (user: TrafficUser) => {
|
||||||
setSelectedUser(user)
|
setSelectedUser(user)
|
||||||
@@ -283,16 +289,8 @@ export default function TrafficPoolPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加状态码转换函数
|
// 添加状态码转换函数
|
||||||
const getStatusFromCode = (statusCode: number): "pending" | "added" | "failed" => {
|
const getStatusFromCode = (statusCode: number): number => {
|
||||||
const statusMap: Record<number, "pending" | "added" | "failed"> = {
|
return statusCode;
|
||||||
1: "pending", // 待处理
|
|
||||||
2: "pending", // 处理中
|
|
||||||
3: "added", // 已添加
|
|
||||||
4: "failed", // 已拒绝
|
|
||||||
5: "failed", // 已过期
|
|
||||||
6: "failed", // 已取消
|
|
||||||
}
|
|
||||||
return statusMap[statusCode] || "pending"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -431,14 +429,14 @@ export default function TrafficPoolPage() {
|
|||||||
<div className="font-medium truncate">{user.nickname}</div>
|
<div className="font-medium truncate">{user.nickname}</div>
|
||||||
<div
|
<div
|
||||||
className={`text-xs px-2 py-1 rounded-full ${
|
className={`text-xs px-2 py-1 rounded-full ${
|
||||||
user.status === "added"
|
user.status === 2
|
||||||
? "bg-green-100 text-green-800"
|
? "bg-green-100 text-green-800"
|
||||||
: user.status === "pending"
|
: user.status === 1
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? "bg-yellow-100 text-yellow-800"
|
||||||
: "bg-red-100 text-red-800"
|
: "bg-red-100 text-red-800"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{user.status === "added" ? "已添加" : user.status === "pending" ? "待处理" : "已失败"}
|
{user.status === 2 ? "已添加" : user.status === 1 ? "待处理" : "已失败"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">微信号: {user.wechatId}</div>
|
<div className="text-sm text-gray-500">微信号: {user.wechatId}</div>
|
||||||
@@ -463,19 +461,19 @@ export default function TrafficPoolPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 加载更多指示器 */}
|
{/* 加载状态显示 */}
|
||||||
{hasMore && (
|
<div ref={loadingRef} className="flex justify-center py-4">
|
||||||
<div ref={loadingRef} className="py-4 flex justify-center">
|
{isFetching && (
|
||||||
{isFetching && <RefreshCw className="h-6 w-6 animate-spin text-blue-500" />}
|
<div className="flex items-center space-x-2">
|
||||||
</div>
|
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
||||||
)}
|
<span className="text-gray-600">加载中...</span>
|
||||||
|
</div>
|
||||||
{/* 显示加载状态和总数 */}
|
)}
|
||||||
<div className="text-sm text-gray-500 text-center">
|
{!hasMore && users.length > 0 && (
|
||||||
{stats.total > 0 && (
|
<span className="text-gray-500">已加载全部数据</span>
|
||||||
<span>
|
)}
|
||||||
已加载 {users.length} / {stats.total} 条记录
|
{!isFetching && users.length === 0 && (
|
||||||
</span>
|
<span className="text-gray-500">暂无数据</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -502,18 +500,14 @@ export default function TrafficPoolPage() {
|
|||||||
<div className="text-sm text-gray-500">{selectedUser.wechatId}</div>
|
<div className="text-sm text-gray-500">{selectedUser.wechatId}</div>
|
||||||
<Badge
|
<Badge
|
||||||
className={`mt-1 ${
|
className={`mt-1 ${
|
||||||
selectedUser.status === "added"
|
selectedUser.status === 2
|
||||||
? "bg-green-100 text-green-800"
|
? "bg-green-100 text-green-800"
|
||||||
: selectedUser.status === "pending"
|
: selectedUser.status === 1
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? "bg-yellow-100 text-yellow-800"
|
||||||
: "bg-red-100 text-red-800"
|
: "bg-red-100 text-red-800"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedUser.status === "added"
|
{selectedUser.status === 2 ? "已添加" : selectedUser.status === 1 ? "待处理" : "已失败"}
|
||||||
? "已添加"
|
|
||||||
: selectedUser.status === "pending"
|
|
||||||
? "待处理"
|
|
||||||
: "已失败"}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ Route::group('v1/', function () {
|
|||||||
|
|
||||||
// 流量池相关
|
// 流量池相关
|
||||||
Route::group('traffic/pool', function () {
|
Route::group('traffic/pool', function () {
|
||||||
Route::get('', 'app\cunkebao\controller\traffic\GetDissociateListWithInCompanyV1Controller@index');
|
Route::get('', 'app\cunkebao\controller\traffic\GetPotentialListWithInCompanyV1Controller@index');
|
||||||
Route::get('types', 'app\cunkebao\controller\traffic\GetDissociateTypeListV1Controller@index');
|
Route::get('types', 'app\cunkebao\controller\traffic\GetPotentialTypeListV1Controller@index');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 工作台相关
|
// 工作台相关
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use library\ResponseHelper;
|
|||||||
/**
|
/**
|
||||||
* 流量池控制器
|
* 流量池控制器
|
||||||
*/
|
*/
|
||||||
class GetDissociateListWithInCompanyV1Controller extends BaseController
|
class GetPotentialListWithInCompanyV1Controller extends BaseController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 构建查询条件
|
* 构建查询条件
|
||||||
@@ -10,7 +10,7 @@ use library\ResponseHelper;
|
|||||||
/**
|
/**
|
||||||
* 流量池控制器
|
* 流量池控制器
|
||||||
*/
|
*/
|
||||||
class GetDissociateTypeListV1Controller extends BaseController
|
class GetPotentialTypeListV1Controller extends BaseController
|
||||||
{
|
{
|
||||||
protected function getTypeList(): array
|
protected function getTypeList(): array
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user