微信号详情
This commit is contained in:
@@ -89,3 +89,119 @@ export const transformWechatAccount = (serverAccount: any): import("@/types/wech
|
|||||||
lastActive: new Date().toLocaleString() // 服务端未提供,使用当前时间
|
lastActive: new Date().toLocaleString() // 服务端未提供,使用当前时间
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将服务端的微信账号详情转换为前端详情页面所需的格式
|
||||||
|
* @param detailResponse 服务端微信账号详情响应
|
||||||
|
* @returns 前端页面所需的微信账号详情格式
|
||||||
|
*/
|
||||||
|
export const transformWechatAccountDetail = (detailResponse: WechatAccountDetailResponse): any => {
|
||||||
|
if (!detailResponse || !detailResponse.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { basicInfo, statistics, accountInfo, restrictions, friends } = detailResponse.data;
|
||||||
|
|
||||||
|
// 设备信息处理
|
||||||
|
let deviceId = '';
|
||||||
|
let deviceName = '';
|
||||||
|
|
||||||
|
if (basicInfo.deviceInfo) {
|
||||||
|
const deviceInfoParts = basicInfo.deviceInfo.split(' ');
|
||||||
|
deviceId = deviceInfoParts[0] || '';
|
||||||
|
deviceName = deviceInfoParts[1] ? deviceInfoParts[1].replace(/[()]/g, '') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 账号年龄计算
|
||||||
|
let accountAgeYears = 0;
|
||||||
|
let accountAgeMonths = 0;
|
||||||
|
|
||||||
|
if (accountInfo.createTime) {
|
||||||
|
const createDate = new Date(accountInfo.createTime);
|
||||||
|
const currentDate = new Date();
|
||||||
|
const diffInMonths = (currentDate.getFullYear() - createDate.getFullYear()) * 12 +
|
||||||
|
(currentDate.getMonth() - createDate.getMonth());
|
||||||
|
|
||||||
|
accountAgeYears = Math.floor(diffInMonths / 12);
|
||||||
|
accountAgeMonths = diffInMonths % 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换限制记录
|
||||||
|
const restrictionRecords = restrictions?.map((restriction, index) => ({
|
||||||
|
id: `${index}`,
|
||||||
|
date: restriction.startTime,
|
||||||
|
reason: restriction.reason,
|
||||||
|
recoveryTime: restriction.endTime,
|
||||||
|
type: mapRestrictionType(restriction.type)
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// 转换好友数据
|
||||||
|
const transformedFriends = friends?.map(friend => ({
|
||||||
|
id: friend.id.toString(),
|
||||||
|
avatar: friend.avatar || `/placeholder.svg?height=40&width=40&text=${friend.nickname?.[0] || ''}`,
|
||||||
|
nickname: friend.nickname,
|
||||||
|
wechatId: friend.wechatId,
|
||||||
|
remark: '', // 服务端未提供
|
||||||
|
addTime: friend.createTime,
|
||||||
|
lastInteraction: '', // 服务端未提供
|
||||||
|
tags: [], // 服务端未提供
|
||||||
|
region: friend.region || '',
|
||||||
|
source: '', // 服务端未提供
|
||||||
|
notes: '', // 服务端未提供
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// 创建每周统计数据(模拟数据,服务端未提供)
|
||||||
|
const weeklyStats = Array.from({ length: 7 }, (_, i) => ({
|
||||||
|
date: `Day ${i + 1}`,
|
||||||
|
friends: Math.floor(Math.random() * 50) + 50,
|
||||||
|
messages: Math.floor(Math.random() * 100) + 100,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: basicInfo.id.toString(),
|
||||||
|
avatar: basicInfo.avatar || '',
|
||||||
|
nickname: basicInfo.nickname || '',
|
||||||
|
wechatId: basicInfo.wechatId || '',
|
||||||
|
deviceId,
|
||||||
|
deviceName,
|
||||||
|
friendCount: statistics.totalFriend || 0,
|
||||||
|
todayAdded: 0, // 服务端未提供,默认为0
|
||||||
|
status: basicInfo.status === '在线' ? 'normal' : 'abnormal',
|
||||||
|
lastActive: accountInfo.lastUpdateTime || new Date().toLocaleString(),
|
||||||
|
messageCount: statistics.thirtyDayMsgCount || 0,
|
||||||
|
activeRate: 0, // 服务端未提供,默认为0
|
||||||
|
accountAge: {
|
||||||
|
years: accountAgeYears,
|
||||||
|
months: accountAgeMonths,
|
||||||
|
},
|
||||||
|
totalChats: statistics.sevenDayMsgCount + statistics.yesterdayMsgCount || 0,
|
||||||
|
chatFrequency: Math.floor((statistics.sevenDayMsgCount || 0) / 7), // 每日平均聊天次数
|
||||||
|
restrictionRecords,
|
||||||
|
isVerified: true, // 服务端未提供,默认为true
|
||||||
|
firstMomentDate: accountInfo.createTime || '',
|
||||||
|
accountWeight: accountInfo.weight || 50,
|
||||||
|
weightFactors: {
|
||||||
|
restrictionFactor: restrictionRecords.length > 0 ? 0.8 : 1.0,
|
||||||
|
verificationFactor: 1.0,
|
||||||
|
ageFactor: Math.min(1.0, accountAgeYears * 0.1 + 0.5),
|
||||||
|
activityFactor: statistics.totalFriend > 0 ? 0.9 : 0.7,
|
||||||
|
},
|
||||||
|
weeklyStats,
|
||||||
|
friends: transformedFriends,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将服务端的限制类型映射为前端类型
|
||||||
|
* @param type 服务端限制类型
|
||||||
|
* @returns 前端限制类型
|
||||||
|
*/
|
||||||
|
const mapRestrictionType = (type: string): "friend_limit" | "marketing" | "spam" | "other" => {
|
||||||
|
const typeMap: Record<string, "friend_limit" | "marketing" | "spam" | "other"> = {
|
||||||
|
'friend': 'friend_limit',
|
||||||
|
'marketing': 'marketing',
|
||||||
|
'spam': 'spam'
|
||||||
|
};
|
||||||
|
|
||||||
|
return typeMap[type] || 'other';
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
Tag,
|
Tag,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Loader2,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||||
@@ -42,6 +43,8 @@ import {
|
|||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination"
|
} from "@/components/ui/pagination"
|
||||||
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
import { fetchWechatAccountDetail, transformWechatAccountDetail } from "@/api/wechat-accounts"
|
||||||
|
|
||||||
interface RestrictionRecord {
|
interface RestrictionRecord {
|
||||||
id: string
|
id: string
|
||||||
@@ -120,10 +123,121 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
|||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [activeTab, setActiveTab] = useState("overview")
|
const [activeTab, setActiveTab] = useState("overview")
|
||||||
const friendsPerPage = 10
|
const friendsPerPage = 10
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 模拟API调用获取账号详情
|
// 模拟API调用获取账号详情
|
||||||
const fetchAccount = async () => {
|
const fetchAccount = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
// 调用API获取微信账号详情
|
||||||
|
const response = await fetchWechatAccountDetail(params.id)
|
||||||
|
|
||||||
|
if (response && response.code === 200) {
|
||||||
|
// 转换数据格式
|
||||||
|
const transformedAccount = transformWechatAccountDetail(response)
|
||||||
|
setAccount(transformedAccount)
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "获取微信账号详情失败",
|
||||||
|
description: response?.msg || "请稍后再试",
|
||||||
|
variant: "destructive"
|
||||||
|
})
|
||||||
|
// 获取失败时使用模拟数据
|
||||||
|
setAccount(generateMockAccountData())
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取微信账号详情失败:", error)
|
||||||
|
toast({
|
||||||
|
title: "获取微信账号详情失败",
|
||||||
|
description: "请检查网络连接或稍后再试",
|
||||||
|
variant: "destructive"
|
||||||
|
})
|
||||||
|
// 请求出错时使用模拟数据
|
||||||
|
setAccount(generateMockAccountData())
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAccount()
|
||||||
|
}, [params.id])
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return <div>加载中...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 calculateMaxDailyAdds = (weight: number) => {
|
||||||
|
const baseLimit = 20
|
||||||
|
return Math.floor(baseLimit * (weight / 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRestrictionTypeColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "friend_limit":
|
||||||
|
return "text-yellow-600"
|
||||||
|
case "marketing":
|
||||||
|
return "text-red-600"
|
||||||
|
case "spam":
|
||||||
|
return "text-orange-600"
|
||||||
|
default:
|
||||||
|
return "text-gray-600"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAccountAge = (age: { years: number; months: number }) => {
|
||||||
|
if (age.years === 0) {
|
||||||
|
return `${age.months}个月`
|
||||||
|
}
|
||||||
|
if (age.months === 0) {
|
||||||
|
return `${age.years}年`
|
||||||
|
}
|
||||||
|
return `${age.years}年${age.months}个月`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTransferFriends = () => {
|
||||||
|
setShowTransferConfirm(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmTransferFriends = () => {
|
||||||
|
setShowTransferConfirm(false)
|
||||||
|
// 跳转到新建计划的订单导入场景
|
||||||
|
router.push(`/scenarios/new?type=order&source=${account.wechatId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFriendClick = (friend: WechatFriend) => {
|
||||||
|
setSelectedFriend(friend)
|
||||||
|
setShowFriendDetail(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤好友
|
||||||
|
const filteredFriends = account.friends.filter(
|
||||||
|
(friend) =>
|
||||||
|
friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
friend.remark.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
friend.tags.some((tag) => tag.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const totalPages = Math.ceil(filteredFriends.length / friendsPerPage)
|
||||||
|
const paginatedFriends = filteredFriends.slice((currentPage - 1) * friendsPerPage, currentPage * friendsPerPage)
|
||||||
|
|
||||||
|
// 生成模拟账号数据(作为备用,服务器请求失败时使用)
|
||||||
|
const generateMockAccountData = () => {
|
||||||
// 生成随机标签
|
// 生成随机标签
|
||||||
const generateRandomTags = (count: number) => {
|
const generateRandomTags = (count: number) => {
|
||||||
const tagPool = [
|
const tagPool = [
|
||||||
@@ -250,86 +364,16 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
|||||||
})),
|
})),
|
||||||
friends: friends,
|
friends: friends,
|
||||||
}
|
}
|
||||||
setAccount(mockAccount)
|
return mockAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAccount()
|
|
||||||
}, [params.id])
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return <div>加载中...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
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 calculateMaxDailyAdds = (weight: number) => {
|
|
||||||
const baseLimit = 20
|
|
||||||
return Math.floor(baseLimit * (weight / 100))
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRestrictionTypeColor = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case "friend_limit":
|
|
||||||
return "text-yellow-600"
|
|
||||||
case "marketing":
|
|
||||||
return "text-red-600"
|
|
||||||
case "spam":
|
|
||||||
return "text-orange-600"
|
|
||||||
default:
|
|
||||||
return "text-gray-600"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatAccountAge = (age: { years: number; months: number }) => {
|
|
||||||
if (age.years === 0) {
|
|
||||||
return `${age.months}个月`
|
|
||||||
}
|
|
||||||
if (age.months === 0) {
|
|
||||||
return `${age.years}年`
|
|
||||||
}
|
|
||||||
return `${age.years}年${age.months}个月`
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTransferFriends = () => {
|
|
||||||
setShowTransferConfirm(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmTransferFriends = () => {
|
|
||||||
setShowTransferConfirm(false)
|
|
||||||
// 跳转到新建计划的订单导入场景
|
|
||||||
router.push(`/scenarios/new?type=order&source=${account.wechatId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFriendClick = (friend: WechatFriend) => {
|
|
||||||
setSelectedFriend(friend)
|
|
||||||
setShowFriendDetail(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤好友
|
|
||||||
const filteredFriends = account.friends.filter(
|
|
||||||
(friend) =>
|
|
||||||
friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
||||||
friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
||||||
friend.remark.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
||||||
friend.tags.some((tag) => tag.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 分页
|
|
||||||
const totalPages = Math.ceil(filteredFriends.length / friendsPerPage)
|
|
||||||
const paginatedFriends = filteredFriends.slice((currentPage - 1) * friendsPerPage, currentPage * friendsPerPage)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex justify-center items-center py-20">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-blue-500" />
|
||||||
|
</div>
|
||||||
|
) : account ? (
|
||||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen pb-16">
|
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen pb-16">
|
||||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||||
<div className="flex items-center p-4">
|
<div className="flex items-center p-4">
|
||||||
@@ -349,7 +393,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
|||||||
<AvatarFallback>{account.nickname[0]}</AvatarFallback>
|
<AvatarFallback>{account.nickname[0]}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{account.isVerified && (
|
{account.isVerified && (
|
||||||
<Badge variant="success" className="absolute -top-2 -right-2 px-2 py-0.5 text-xs">
|
<Badge variant="outline" className="absolute -top-2 -right-2 px-2 py-0.5 text-xs">
|
||||||
已认证
|
已认证
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -357,7 +401,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
|||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<h2 className="text-xl font-semibold">{account.nickname}</h2>
|
<h2 className="text-xl font-semibold">{account.nickname}</h2>
|
||||||
<Badge variant={account.status === "normal" ? "success" : "destructive"}>
|
<Badge variant={account.status === "normal" ? "outline" : "destructive"}>
|
||||||
{account.status === "normal" ? "正常" : "异常"}
|
{account.status === "normal" ? "正常" : "异常"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -761,6 +805,11 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center items-center py-20">
|
||||||
|
<p className="text-gray-500">未找到账号信息</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user