From 5a78e0bc76074d95ff3ea164d5f179f44bb17ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=B0=B8=E5=B9=B3?= Date: Sat, 5 Jul 2025 15:44:07 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=BE=AE=E4=BF=A1=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E8=BF=9B=E5=BA=A6=E4=BF=9D=E5=AD=98=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/App.tsx | 65 +- nkebao/src/contexts/WechatAccountContext.tsx | 54 ++ .../wechat-accounts/WechatAccountDetail.tsx | 790 +++++++++++++++++- .../pages/wechat-accounts/WechatAccounts.tsx | 11 +- 4 files changed, 881 insertions(+), 39 deletions(-) create mode 100644 nkebao/src/contexts/WechatAccountContext.tsx diff --git a/nkebao/src/App.tsx b/nkebao/src/App.tsx index 6590d77b..ca6d6ec0 100644 --- a/nkebao/src/App.tsx +++ b/nkebao/src/App.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { AuthProvider } from './contexts/AuthContext'; +import { WechatAccountProvider } from './contexts/WechatAccountContext'; import { ToastProvider } from './components/ui/toast'; import ProtectedRoute from './components/ProtectedRoute'; import LayoutWrapper from './components/LayoutWrapper'; @@ -38,37 +39,39 @@ function App() { return ( - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - {/* 你可以继续添加更多路由 */} - - - - + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* 你可以继续添加更多路由 */} + + + + + ); diff --git a/nkebao/src/contexts/WechatAccountContext.tsx b/nkebao/src/contexts/WechatAccountContext.tsx new file mode 100644 index 00000000..5f928293 --- /dev/null +++ b/nkebao/src/contexts/WechatAccountContext.tsx @@ -0,0 +1,54 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +export interface WechatAccountData { + id: string; + avatar: string; + nickname: string; + status: "normal" | "abnormal"; + wechatId: string; + wechatAccount: string; + deviceName: string; + deviceId: string; +} + +interface WechatAccountContextType { + currentAccount: WechatAccountData | null; + setCurrentAccount: (account: WechatAccountData) => void; + clearCurrentAccount: () => void; +} + +const WechatAccountContext = createContext({ + currentAccount: null, + setCurrentAccount: () => {}, + clearCurrentAccount: () => {}, +}); + +export const useWechatAccount = () => useContext(WechatAccountContext); + +interface WechatAccountProviderProps { + children: ReactNode; +} + +export function WechatAccountProvider({ children }: WechatAccountProviderProps) { + const [currentAccount, setCurrentAccountState] = useState(null); + + const setCurrentAccount = (account: WechatAccountData) => { + setCurrentAccountState(account); + }; + + const clearCurrentAccount = () => { + setCurrentAccountState(null); + }; + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx b/nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx index be57854a..d6f769db 100644 --- a/nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx +++ b/nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx @@ -1,7 +1,789 @@ -import React from 'react'; -import { useParams } from 'react-router-dom'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + ChevronLeft, + Smartphone, + Users, + Star, + Clock, + MessageSquare, + Shield, + Info, + UserPlus, + Search, + Tag, + ChevronRight, + Loader2, + AlertCircle, + ArrowRightLeft +} from 'lucide-react'; +import { useWechatAccount } from '../../contexts/WechatAccountContext'; +import { fetchWechatAccountSummary, fetchWechatFriends, fetchWechatFriendDetail } from '../../api/wechat-accounts'; +import { useToast } from '../../components/ui/toast'; + +interface WechatAccountSummary { + accountAge: string; + activityLevel: { + allTimes: number; + dayTimes: number; + }; + accountWeight: { + scope: number; + ageWeight: number; + activityWeigth: number; + restrictWeight: number; + realNameWeight: number; + }; + statistics: { + todayAdded: number; + addLimit: number; + }; + restrictions: { + id: number; + level: string; + reason: string; + date: string; + }[]; +} + +interface Friend { + id: string; + avatar: string; + nickname: string; + wechatId: string; + remark: string; + addTime: string; + lastInteraction: string; + tags: Array<{ + id: string; + name: string; + color: string; + }>; + region: string; + source: string; + notes: string; +} + +interface WechatFriendDetail { + id: number; + avatar: string; + nickname: string; + region: string; + wechatId: string; + addDate: string; + tags: string[]; + memo: string; + source: string; +} export default function WechatAccountDetail() { - const { id } = useParams(); - return
微信号详情页,当前ID: {id}
; + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const { toast } = useToast(); + const { currentAccount, clearCurrentAccount } = useWechatAccount(); + + const [accountSummary, setAccountSummary] = useState(null); + const [showRestrictions, setShowRestrictions] = useState(false); + const [showTransferConfirm, setShowTransferConfirm] = useState(false); + const [showFriendDetail, setShowFriendDetail] = useState(false); + const [selectedFriend, setSelectedFriend] = useState(null); + const [friendDetail, setFriendDetail] = useState(null); + const [isLoadingFriendDetail, setIsLoadingFriendDetail] = useState(false); + const [friendDetailError, setFriendDetailError] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); + const [activeTab, setActiveTab] = useState("overview"); + const [isLoading, setIsLoading] = useState(false); + + // 好友列表相关状态 + const [friends, setFriends] = useState([]); + const [friendsPage, setFriendsPage] = useState(1); + const [friendsTotal, setFriendsTotal] = useState(0); + const [hasMoreFriends, setHasMoreFriends] = useState(true); + const [isFetchingFriends, setIsFetchingFriends] = useState(false); + const friendsObserver = useRef(null); + const friendsLoadingRef = useRef(null); + + // 如果没有账号数据,返回上一页 + useEffect(() => { + if (!currentAccount) { + toast({ + title: "数据错误", + description: "未找到账号信息,请重新选择", + variant: "destructive" + }); + navigate('/wechat-accounts'); + return; + } + }, [currentAccount, navigate, toast]); + + // 获取账号概览信息 + const fetchAccountSummary = useCallback(async () => { + if (!id) return; + + try { + setIsLoading(true); + const response = await fetchWechatAccountSummary(id); + + if (response && response.code === 200 && response.data) { + setAccountSummary(response.data); + } else { + toast({ + title: "获取账号概览失败", + description: response?.message || "请稍后重试", + variant: "destructive" + }); + } + } catch (error) { + console.error("获取账号概览失败:", error); + toast({ + title: "获取账号概览失败", + description: "请检查网络连接后重试", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + }, [id, toast]); + + // 获取好友列表 + const fetchFriends = useCallback(async (page: number = 1, isNewSearch: boolean = false) => { + if (!id || isFetchingFriends) return; + + try { + setIsFetchingFriends(true); + const response = await fetchWechatFriends(id, page, 20, searchQuery); + + if (response && response.code === 200 && response.data) { + const newFriends = response.data.list.map((friend: any) => ({ + id: friend.id.toString(), + avatar: friend.avatar || "/placeholder.svg", + nickname: friend.nickname || "未知用户", + wechatId: friend.wechatId || "", + remark: friend.memo || "", + addTime: friend.createTime || new Date().toISOString().split('T')[0], + lastInteraction: friend.lastInteraction || new Date().toISOString().split('T')[0], + tags: friend.tags ? friend.tags.map((tag: string, index: number) => ({ + id: `tag-${index}`, + name: tag, + color: getRandomTagColor() + })) : [], + region: friend.region || "未知", + source: friend.source || "未知", + notes: friend.notes || "" + })); + + if (isNewSearch) { + setFriends(newFriends); + } else { + setFriends(prev => [...prev, ...newFriends]); + } + + setFriendsTotal(response.data.total); + setHasMoreFriends(newFriends.length === 20); + setFriendsPage(page); + } else { + toast({ + title: "获取好友列表失败", + description: response?.message || "请稍后重试", + variant: "destructive" + }); + } + } catch (error) { + console.error("获取好友列表失败:", error); + toast({ + title: "获取好友列表失败", + description: "请检查网络连接后重试", + variant: "destructive" + }); + } finally { + setIsFetchingFriends(false); + } + }, [id, searchQuery, isFetchingFriends, toast]); + + // 初始化数据 + useEffect(() => { + if (id) { + fetchAccountSummary(); + if (activeTab === "friends") { + fetchFriends(1, true); + } + } + }, [id, fetchAccountSummary]); + + // 监听标签切换 + useEffect(() => { + if (activeTab === "friends" && id) { + fetchFriends(1, true); + } + }, [activeTab, id, fetchFriends]); + + // 无限滚动加载好友 + useEffect(() => { + if (!friendsLoadingRef.current || !hasMoreFriends || isFetchingFriends) return; + + friendsObserver.current = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasMoreFriends && !isFetchingFriends) { + fetchFriends(friendsPage + 1, false); + } + }, + { threshold: 0.1 } + ); + + friendsObserver.current.observe(friendsLoadingRef.current); + + return () => { + if (friendsObserver.current) { + friendsObserver.current.disconnect(); + } + }; + }, [hasMoreFriends, isFetchingFriends, friendsPage, fetchFriends]); + + // 工具函数 + const getRandomTagColor = (): string => { + const colors = [ + "bg-blue-100 text-blue-800", + "bg-green-100 text-green-800", + "bg-red-100 text-red-800", + "bg-pink-100 text-pink-800", + "bg-emerald-100 text-emerald-800", + "bg-amber-100 text-amber-800", + ]; + return colors[Math.floor(Math.random() * colors.length)]; + }; + + const calculateAccountAge = (registerTime: string) => { + const registerDate = new Date(registerTime); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - registerDate.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const years = Math.floor(diffDays / 365); + const months = Math.floor((diffDays % 365) / 30); + return { years, months }; + }; + + const formatAccountAge = (age: { years: number; months: number }) => { + if (age.years > 0) { + return `${age.years}年${age.months}个月`; + } + return `${age.months}个月`; + }; + + const getWeightColor = (weight: number) => { + if (weight >= 80) return "text-green-600"; + if (weight >= 60) return "text-yellow-600"; + return "text-red-600"; + }; + + const getWeightDescription = (weight: number) => { + if (weight >= 80) return "账号质量优秀,可以正常使用"; + if (weight >= 60) return "账号质量良好,需要注意使用频率"; + return "账号质量较差,建议谨慎使用"; + }; + + const handleTransferFriends = () => { + setShowTransferConfirm(true); + }; + + const confirmTransferFriends = () => { + toast({ + title: "好友转移计划已创建", + description: "请在场景获客中查看详情", + }); + setShowTransferConfirm(false); + navigate("/scenarios"); + }; + + const handleBack = () => { + clearCurrentAccount(); + navigate('/wechat-accounts'); + }; + + const handleFriendClick = async (friend: Friend) => { + setSelectedFriend(friend); + setShowFriendDetail(true); + setIsLoadingFriendDetail(true); + setFriendDetailError(null); + + try { + const response = await fetchWechatFriendDetail(friend.id); + if (response && response.code === 200 && response.data) { + setFriendDetail(response.data); + } else { + setFriendDetailError(response?.message || "获取好友详情失败"); + } + } catch (error) { + console.error("获取好友详情失败:", error); + setFriendDetailError("网络错误,请稍后重试"); + } finally { + setIsLoadingFriendDetail(false); + } + }; + + const getRestrictionLevelColor = (level: string) => { + switch (level) { + case "high": + return "bg-red-100 text-red-700"; + case "medium": + return "bg-yellow-100 text-yellow-700"; + default: + return "bg-gray-100 text-gray-700"; + } + }; + + if (!currentAccount) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* 固定header */} +
+
+ +

账号详情

+
+
+ + {/* 内容区域 */} +
+
+ {/* 账号基本信息卡片 */} +
+
+
+ {currentAccount.nickname} +
+
+
+
+

{currentAccount.nickname}

+ + {currentAccount.status === "normal" ? "正常" : "异常"} + +
+

微信号:{currentAccount.wechatAccount}

+
+ + +
+
+
+
+ + {/* 标签页 */} +
+
+ + +
+ +
+ {activeTab === "overview" ? ( +
+ {/* 账号基础信息 */} +
+
+
+ + 账号年龄 +
+ {accountSummary && ( + <> +
+ {formatAccountAge(calculateAccountAge(accountSummary.accountAge))} +
+
+ 注册时间:{new Date(accountSummary.accountAge).toLocaleDateString()} +
+ + )} +
+ +
+
+ + 活跃程度 +
+ {accountSummary && ( + <> +
{accountSummary.activityLevel.dayTimes.toLocaleString()}次/天
+
总聊天数:{accountSummary.activityLevel.allTimes.toLocaleString()}
+ + )} +
+
+ + {/* 账号权重评估 */} + {accountSummary && ( +
+
+
+ + 账号权重评估 +
+
+ {accountSummary.accountWeight.scope} + +
+
+

{getWeightDescription(accountSummary.accountWeight.scope)}

+
+
+ 账号年龄 +
+
+
+ {accountSummary.accountWeight.ageWeight}% +
+
+ 活跃度 +
+
+
+ {accountSummary.accountWeight.activityWeigth}% +
+
+ 限制影响 +
+
+
+ {accountSummary.accountWeight.restrictWeight}% +
+
+ 实名认证 +
+
+
+ {accountSummary.accountWeight.realNameWeight}% +
+
+
+ )} + + {/* 添加好友统计 */} + {accountSummary && ( +
+
+
+ + 添加好友统计 +
+
+ +
+ 根据账号权重计算每日可添加好友数量 +
+
+
+
+
+ 今日已添加 + {accountSummary.statistics.todayAdded} +
+
+
+ 添加进度 + + {accountSummary.statistics.todayAdded}/{accountSummary.statistics.addLimit} + +
+
+
+
+
+
+
+ )} + + {/* 限制记录 */} + {accountSummary && accountSummary.restrictions.length > 0 && ( +
+
+ + 限制记录 +
+
+ {accountSummary.restrictions.map((restriction) => ( +
+
+
+ {restriction.reason} + + {restriction.level === "high" ? "严重" : + restriction.level === "medium" ? "中等" : "轻微"} + +
+
{restriction.date}
+
+
+ ))} +
+
+ )} +
+ ) : ( +
+ {/* 搜索栏 */} +
+
+ + setSearchQuery(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && fetchFriends(1, true)} + /> +
+ +
+ + {/* 好友列表 */} +
+ {friends.map((friend) => ( +
handleFriendClick(friend)} + > + {friend.nickname} +
+
+

{friend.nickname}

+ {friend.tags.length > 0 && ( +
+ {friend.tags.slice(0, 2).map((tag) => ( + + {tag.name} + + ))} + {friend.tags.length > 2 && ( + + +{friend.tags.length - 2} + + )} +
+ )} +
+
+
备注:{friend.remark || "无"}
+
+ 地区:{friend.region} + 来源:{friend.source} +
+
+
+ +
+ ))} + + {/* 加载更多 */} + {hasMoreFriends && ( +
+ {isFetchingFriends && } +
+ )} + + {!hasMoreFriends && friends.length > 0 && ( +
+ 没有更多好友了 +
+ )} + + {friends.length === 0 && !isFetchingFriends && ( +
+ +

暂无好友数据

+
+ )} +
+
+ )} +
+
+
+
+ + {/* 好友转移确认对话框 */} + {showTransferConfirm && ( +
+
+

好友转移确认

+

+ 确认要将 {currentAccount.nickname} 的好友转移到场景获客吗?系统将自动创建一个获客计划。 +

+
+ + +
+
+
+ )} + + {/* 好友详情对话框 */} + {showFriendDetail && ( +
+
+
+

好友详情

+ +
+ + {isLoadingFriendDetail ? ( +
+ +
+ ) : friendDetailError ? ( +
+ +

{friendDetailError}

+
+ ) : friendDetail && selectedFriend ? ( +
+
+ {selectedFriend.nickname} +
+

{selectedFriend.nickname}

+

微信号:{selectedFriend.wechatId}

+
+
+ +
+
+ 地区 + {friendDetail.region || "未知"} +
+
+ 添加时间 + {friendDetail.addDate} +
+
+ 来源 + {friendDetail.source || "未知"} +
+ {friendDetail.memo && ( +
+ 备注 + {friendDetail.memo} +
+ )} + {friendDetail.tags && friendDetail.tags.length > 0 && ( +
+ 标签 +
+ {friendDetail.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+ )} +
+
+ ) : null} +
+
+ )} +
+ ); } \ No newline at end of file diff --git a/nkebao/src/pages/wechat-accounts/WechatAccounts.tsx b/nkebao/src/pages/wechat-accounts/WechatAccounts.tsx index 9cee3265..0cf699d1 100644 --- a/nkebao/src/pages/wechat-accounts/WechatAccounts.tsx +++ b/nkebao/src/pages/wechat-accounts/WechatAccounts.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { ChevronLeft, Search, RefreshCw, ArrowRightLeft, AlertCircle, Loader2 } from 'lucide-react'; import { fetchWechatAccountList, transformWechatAccount } from '../../api/wechat-accounts'; import { useToast } from '../../components/ui/toast'; +import { useWechatAccount } from '../../contexts/WechatAccountContext'; interface WechatAccount { id: string; @@ -23,6 +24,7 @@ interface WechatAccount { export default function WechatAccounts() { const navigate = useNavigate(); const { toast } = useToast(); + const { setCurrentAccount } = useWechatAccount(); const [accounts, setAccounts] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); @@ -220,8 +222,9 @@ export default function WechatAccounts() { key={account.id} className="bg-white p-4 rounded-xl shadow-sm border border-gray-100 hover:shadow-lg transition-all cursor-pointer" onClick={() => { - // 将需要的数据编码为 URL 安全的字符串 - const accountData = encodeURIComponent(JSON.stringify({ + // 使用Context存储数据,而不是URL参数 + setCurrentAccount({ + id: account.id, avatar: account.avatar, nickname: account.nickname, status: account.status, @@ -229,8 +232,8 @@ export default function WechatAccounts() { wechatAccount: account.wechatAccount, deviceName: account.deviceName, deviceId: account.deviceId, - })); - navigate(`/wechat-accounts/${account.id}?data=${accountData}`); + }); + navigate(`/wechat-accounts/${account.id}`); }} >