Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop
This commit is contained in:
@@ -2,9 +2,40 @@ import { api } from "@/lib/api";
|
||||
import {
|
||||
ServerWechatAccountsResponse,
|
||||
QueryWechatAccountParams,
|
||||
WechatAccountDetailResponse
|
||||
} from "@/types/wechat-account";
|
||||
|
||||
// 添加接口返回数据类型定义
|
||||
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 WechatAccountSummaryResponse {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: WechatAccountSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信账号列表
|
||||
* @param params 查询参数
|
||||
@@ -24,15 +55,6 @@ export const fetchWechatAccountList = async (params: QueryWechatAccountParams =
|
||||
return api.get<ServerWechatAccountsResponse>(`/v1/device/wechats?${queryParams.toString()}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取微信账号详情
|
||||
* @param id 微信账号ID
|
||||
* @returns 微信账号详情响应
|
||||
*/
|
||||
export const fetchWechatAccountDetail = async (id: string | number): Promise<WechatAccountDetailResponse> => {
|
||||
return api.get<WechatAccountDetailResponse>(`/v1/device/wechats/${id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新微信账号状态
|
||||
* @returns 刷新结果
|
||||
@@ -115,173 +137,35 @@ export const transformWechatAccount = (serverAccount: any): import("@/types/wech
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 将服务端的微信账号详情转换为前端详情页面所需的格式
|
||||
* @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(' ');
|
||||
if (deviceInfoParts.length > 0) {
|
||||
// 提取数字部分作为设备ID,确保是整数
|
||||
const possibleId = deviceInfoParts[0].trim();
|
||||
// 验证是否为数字
|
||||
deviceId = /^\d+$/.test(possibleId) ? possibleId : '';
|
||||
|
||||
// 提取设备名称
|
||||
if (deviceInfoParts.length > 1) {
|
||||
deviceName = deviceInfoParts[1].replace(/[()]/g, '').trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果从deviceInfo无法获取有效的设备ID,直接使用微信账号ID作为备选
|
||||
if (!deviceId && basicInfo.id) {
|
||||
deviceId = basicInfo.id.toString();
|
||||
}
|
||||
|
||||
// 如果没有设备名称,使用备用名称
|
||||
if (!deviceName) {
|
||||
deviceName = '未命名设备';
|
||||
}
|
||||
|
||||
// 账号年龄计算
|
||||
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';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取微信好友列表
|
||||
* @param wechatId 微信账号ID
|
||||
* @param page 页码
|
||||
* @param limit 每页数量
|
||||
* @param keyword 搜索关键词
|
||||
* @param pageSize 每页数量
|
||||
* @param searchQuery 搜索关键词
|
||||
* @returns 好友列表数据
|
||||
*/
|
||||
export const fetchWechatFriends = async (
|
||||
wechatId: string | number,
|
||||
page: number = 1,
|
||||
limit: number = 20,
|
||||
keyword: string = ""
|
||||
): Promise<{ code: number; msg: string; data: any }> => {
|
||||
export const fetchWechatFriends = async (wechatId: string, page: number = 1, pageSize: number = 20, searchQuery: string = '') => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
wechatId: String(wechatId),
|
||||
page: String(page),
|
||||
limit: String(limit)
|
||||
});
|
||||
|
||||
if (keyword) {
|
||||
params.append('keyword', keyword);
|
||||
}
|
||||
|
||||
const url = `/v1/device/wechats/friends?${params.toString()}`;
|
||||
const response = await api.get<{ code: number; msg: string; data: any }>(url);
|
||||
|
||||
return response;
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/device/wechats/${wechatId}/friends?page=${page}&pageSize=${pageSize}&search=${searchQuery}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('获取微信好友列表失败:', error);
|
||||
console.error("获取好友列表失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取微信账号概览信息
|
||||
* @param id 微信账号ID
|
||||
* @returns 微信账号概览信息
|
||||
*/
|
||||
export const fetchWechatAccountSummary = async (id: string): Promise<WechatAccountSummaryResponse> => {
|
||||
try {
|
||||
return api.get<WechatAccountSummaryResponse>(`/v1/device/wechats/${id}/summary`);
|
||||
} catch (error) {
|
||||
console.error("获取账号概览失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from "react"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { api } from "@/lib/api"
|
||||
import { fetchWechatAccountSummary } from "@/api/wechat-accounts"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import {
|
||||
ChevronLeft,
|
||||
Smartphone,
|
||||
@@ -20,11 +29,6 @@ import {
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -43,8 +47,25 @@ import {
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { fetchWechatAccountDetail, transformWechatAccountDetail, fetchWechatFriends } from "@/api/wechat-accounts"
|
||||
import { fetchWechatFriends } from "@/api/wechat-accounts"
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface FriendsResponse {
|
||||
list: Array<{
|
||||
id: number;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
wechatId: string;
|
||||
memo: string;
|
||||
tags: string[];
|
||||
}>;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface RestrictionRecord {
|
||||
id: string
|
||||
@@ -60,18 +81,22 @@ interface FriendTag {
|
||||
color: string
|
||||
}
|
||||
|
||||
interface WechatFriend {
|
||||
id: string
|
||||
avatar: string
|
||||
nickname: string
|
||||
wechatId: string
|
||||
remark: string
|
||||
addTime: string
|
||||
lastInteraction: string
|
||||
tags: FriendTag[]
|
||||
region: string
|
||||
source: string
|
||||
notes: 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 WechatAccountDetail {
|
||||
@@ -87,13 +112,12 @@ interface WechatAccountDetail {
|
||||
lastActive: string
|
||||
messageCount: number
|
||||
activeRate: number
|
||||
// 新增和修改的字段
|
||||
accountAge: {
|
||||
years: number
|
||||
months: number
|
||||
}
|
||||
totalChats: number
|
||||
chatFrequency: number // 每日平均聊天次数
|
||||
chatFrequency: number
|
||||
restrictionRecords: RestrictionRecord[]
|
||||
isVerified: boolean
|
||||
firstMomentDate: string
|
||||
@@ -109,22 +133,56 @@ interface WechatAccountDetail {
|
||||
friends: number
|
||||
messages: number
|
||||
}[]
|
||||
friends: WechatFriend[]
|
||||
friends: Friend[]
|
||||
}
|
||||
|
||||
export default function WechatAccountDetailPage({ params }: { params: { id: string } }) {
|
||||
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 PageProps {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export default function WechatAccountDetailPage() {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const id = params?.id as string
|
||||
const [account, setAccount] = useState<WechatAccountDetail | null>(null)
|
||||
const [accountSummary, setAccountSummary] = useState<WechatAccountSummary | null>(null)
|
||||
const [showRestrictions, setShowRestrictions] = useState(false)
|
||||
const [showTransferConfirm, setShowTransferConfirm] = useState(false)
|
||||
const [showFriendDetail, setShowFriendDetail] = useState(false)
|
||||
const [selectedFriend, setSelectedFriend] = useState<WechatFriend | null>(null)
|
||||
const [selectedFriend, setSelectedFriend] = useState<Friend | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// 好友列表相关状态
|
||||
const [friends, setFriends] = useState<any[]>([])
|
||||
const [friends, setFriends] = useState<Friend[]>([])
|
||||
const [friendsPage, setFriendsPage] = useState(1)
|
||||
const [friendsTotal, setFriendsTotal] = useState(0)
|
||||
const [hasMoreFriends, setHasMoreFriends] = useState(true)
|
||||
@@ -151,11 +209,30 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
try {
|
||||
const decodedData = JSON.parse(decodeURIComponent(dataParam));
|
||||
setInitialData(decodedData);
|
||||
// 使用初始数据设置account
|
||||
const mockData = generateMockAccountData(id);
|
||||
if (decodedData) {
|
||||
mockData.avatar = decodedData.avatar;
|
||||
mockData.nickname = decodedData.nickname;
|
||||
mockData.status = decodedData.status;
|
||||
mockData.wechatId = decodedData.wechatId;
|
||||
mockData.deviceName = decodedData.deviceName;
|
||||
}
|
||||
setAccount(mockData);
|
||||
setFriendsTotal(mockData.friendCount);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error('解析初始数据失败:', error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
// 如果没有初始数据,使用模拟数据
|
||||
const mockData = generateMockAccountData(id);
|
||||
setAccount(mockData);
|
||||
setFriendsTotal(mockData.friendCount);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [id]);
|
||||
|
||||
// 计算好友列表容器高度
|
||||
const getFriendsContainerHeight = () => {
|
||||
@@ -168,7 +245,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
};
|
||||
|
||||
// 生成模拟账号数据(作为备用,服务器请求失败时使用)
|
||||
const generateMockAccountData = (): WechatAccountDetail => {
|
||||
const generateMockAccountData = (accountId: string): WechatAccountDetail => {
|
||||
// 生成随机标签
|
||||
const generateRandomTags = (count: number): FriendTag[] => {
|
||||
const tagPool = [
|
||||
@@ -196,7 +273,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
|
||||
// 生成随机好友
|
||||
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150;
|
||||
const generateFriends = (count: number): WechatFriend[] => {
|
||||
const generateFriends = (count: number): Friend[] => {
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const firstName = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"][Math.floor(Math.random() * 10)];
|
||||
const secondName = ["小", "大", "明", "华", "强", "伟", "芳", "娜", "秀", "英"][
|
||||
@@ -244,7 +321,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
const friends = generateFriends(friendCount);
|
||||
|
||||
const mockAccount: WechatAccountDetail = {
|
||||
id: params.id,
|
||||
id: accountId,
|
||||
avatar:
|
||||
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/img_v3_02jn_e7fcc2a4-3560-478d-911a-4ccd69c6392g.jpg-a8zVtwxMuSrPWN9dfWH93EBY0yM3Dh.jpeg",
|
||||
nickname: "卡若-25vig",
|
||||
@@ -311,7 +388,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
};
|
||||
|
||||
// 获取好友列表数据
|
||||
// 修改fetchFriends函数
|
||||
const fetchFriends = useCallback(async (page: number = 1, isNewSearch: boolean = false) => {
|
||||
if (!account || isFetchingFriends) return;
|
||||
|
||||
@@ -319,30 +396,29 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
setIsFetchingFriends(true);
|
||||
setHasFriendLoadError(false);
|
||||
|
||||
// 调用API获取好友列表
|
||||
const response = await fetchWechatFriends(account.wechatId, page, 20, searchQuery);
|
||||
const data = await api.get<ApiResponse<FriendsResponse>>(`/v1/device/wechats/${id}/friends?page=${page}&limit=30`, true);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
// 更新总数计数,确保在第一次加载时设置
|
||||
if (data && data.code === 200) {
|
||||
// 更新总数计数
|
||||
if (isNewSearch || friendsTotal === 0) {
|
||||
setFriendsTotal(response.data.total || 0);
|
||||
setFriendsTotal(data.data.total || 0);
|
||||
}
|
||||
|
||||
const newFriends = response.data.list.map((friend: any) => ({
|
||||
id: friend.wechatId,
|
||||
const newFriends = data.data.list.map((friend) => ({
|
||||
id: friend.id.toString(),
|
||||
avatar: friend.avatar,
|
||||
nickname: friend.nickname || '未设置昵称',
|
||||
nickname: friend.nickname,
|
||||
wechatId: friend.wechatId,
|
||||
remark: friend.remark || '',
|
||||
remark: friend.memo || '',
|
||||
addTime: '2024-01-01', // 接口未返回,使用默认值
|
||||
lastInteraction: '2024-01-01', // 接口未返回,使用默认值
|
||||
tags: (friend.labels || []).map((label: string, index: number) => ({
|
||||
tags: (friend.tags || []).map((label: string, index: number) => ({
|
||||
id: `tag-${index}`,
|
||||
name: label,
|
||||
color: getRandomTagColor(),
|
||||
})),
|
||||
region: friend.region || '未知地区',
|
||||
source: '微信好友', // 接口未返回,使用默认值
|
||||
region: '未知地区',
|
||||
source: '微信好友',
|
||||
notes: '',
|
||||
}));
|
||||
|
||||
@@ -355,14 +431,13 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
|
||||
setFriendsPage(page);
|
||||
// 判断是否还有更多数据
|
||||
setHasMoreFriends(page * 20 < response.data.total);
|
||||
setHasMoreFriends(page * 30 < data.data.total);
|
||||
|
||||
console.log("好友列表加载成功,总数:", response.data.total);
|
||||
} else {
|
||||
setHasFriendLoadError(true);
|
||||
toast({
|
||||
title: "获取好友列表失败",
|
||||
description: response?.msg || "请稍后再试",
|
||||
description: data?.msg || "请稍后再试",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
@@ -377,7 +452,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
} finally {
|
||||
setIsFetchingFriends(false);
|
||||
}
|
||||
}, [account, searchQuery, friendsTotal]);
|
||||
}, [account, id, friendsTotal]);
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = useCallback(() => {
|
||||
@@ -388,11 +463,14 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
}, [fetchFriends]);
|
||||
|
||||
// 处理标签切换
|
||||
useEffect(() => {
|
||||
if (account && friends.length === 0) {
|
||||
const handleTabChange = (value: string) => {
|
||||
setActiveTab(value);
|
||||
if (value === "overview") {
|
||||
fetchSummaryData();
|
||||
} else if (value === "friends" && friends.length === 0) {
|
||||
fetchFriends(1, true);
|
||||
}
|
||||
}, [account, friends.length, fetchFriends]);
|
||||
};
|
||||
|
||||
// 设置IntersectionObserver用于懒加载
|
||||
useEffect(() => {
|
||||
@@ -422,79 +500,65 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
};
|
||||
}, [friendsLoadingRef.current, friendsObserver.current]);
|
||||
|
||||
useEffect(() => {
|
||||
// 模拟API调用获取账号详情
|
||||
const fetchAccount = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
// 调用API获取微信账号详情
|
||||
const response = await fetchWechatAccountDetail(params.id)
|
||||
|
||||
if (response && response.code === 200) {
|
||||
// 转换数据格式
|
||||
const transformedAccount = transformWechatAccountDetail(response)
|
||||
// 使用初始数据覆盖API返回的部分字段
|
||||
if (initialData) {
|
||||
transformedAccount.avatar = initialData.avatar;
|
||||
transformedAccount.nickname = initialData.nickname;
|
||||
transformedAccount.status = initialData.status;
|
||||
transformedAccount.wechatId = initialData.wechatId;
|
||||
transformedAccount.deviceName = initialData.deviceName;
|
||||
}
|
||||
setAccount(transformedAccount)
|
||||
|
||||
// 如果有好友总数,更新friendsTotal状态
|
||||
if (transformedAccount && transformedAccount.friendCount > 0) {
|
||||
setFriendsTotal(transformedAccount.friendCount);
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "获取微信账号详情失败",
|
||||
description: response?.msg || "请稍后再试",
|
||||
variant: "destructive"
|
||||
})
|
||||
// 获取失败时使用模拟数据
|
||||
const mockData = generateMockAccountData();
|
||||
// 使用初始数据覆盖模拟数据的部分字段
|
||||
if (initialData) {
|
||||
mockData.avatar = initialData.avatar;
|
||||
mockData.nickname = initialData.nickname;
|
||||
mockData.status = initialData.status;
|
||||
mockData.wechatId = initialData.wechatId;
|
||||
mockData.deviceName = initialData.deviceName;
|
||||
}
|
||||
setAccount(mockData);
|
||||
// 更新好友总数
|
||||
setFriendsTotal(mockData.friendCount);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取微信账号详情失败:", error)
|
||||
toast({
|
||||
title: "获取微信账号详情失败",
|
||||
description: "请检查网络连接或稍后再试",
|
||||
variant: "destructive"
|
||||
})
|
||||
// 请求出错时使用模拟数据
|
||||
const mockData = generateMockAccountData();
|
||||
// 使用初始数据覆盖模拟数据的部分字段
|
||||
if (initialData) {
|
||||
mockData.avatar = initialData.avatar;
|
||||
mockData.nickname = initialData.nickname;
|
||||
mockData.status = initialData.status;
|
||||
mockData.wechatId = initialData.wechatId;
|
||||
mockData.deviceName = initialData.deviceName;
|
||||
}
|
||||
setAccount(mockData);
|
||||
// 更新好友总数
|
||||
setFriendsTotal(mockData.friendCount);
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
// 计算账号年龄
|
||||
const calculateAccountAge = (registerTime: string) => {
|
||||
const register = new Date(registerTime);
|
||||
const now = new Date();
|
||||
const years = now.getFullYear() - register.getFullYear();
|
||||
const months = now.getMonth() - register.getMonth();
|
||||
|
||||
if (months < 0) {
|
||||
return {
|
||||
years: years - 1,
|
||||
months: months + 12
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
years,
|
||||
months
|
||||
};
|
||||
};
|
||||
|
||||
fetchAccount()
|
||||
}, [params.id, initialData])
|
||||
// 获取账号概览数据
|
||||
const fetchSummaryData = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchWechatAccountSummary(id);
|
||||
if (response.code === 200) {
|
||||
setAccountSummary(response.data);
|
||||
} else {
|
||||
toast({
|
||||
title: "获取账号概览失败",
|
||||
description: response.msg || "请稍后再试",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取账号概览失败:", error);
|
||||
toast({
|
||||
title: "获取账号概览失败",
|
||||
description: "请检查网络连接或稍后再试",
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
// 在页面加载和切换到概览标签时获取数据
|
||||
useEffect(() => {
|
||||
if (activeTab === "overview") {
|
||||
fetchSummaryData();
|
||||
}
|
||||
}, [activeTab, fetchSummaryData]);
|
||||
|
||||
// 在初始加载时获取数据
|
||||
useEffect(() => {
|
||||
if (activeTab === "overview") {
|
||||
fetchSummaryData();
|
||||
}
|
||||
}, [fetchSummaryData, activeTab]);
|
||||
|
||||
if (!account) {
|
||||
return <div>加载中...</div>
|
||||
@@ -550,11 +614,35 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
setShowTransferConfirm(false)
|
||||
}
|
||||
|
||||
const handleFriendClick = (friend: WechatFriend) => {
|
||||
const handleFriendClick = (friend: Friend) => {
|
||||
setSelectedFriend(friend)
|
||||
setShowFriendDetail(true)
|
||||
}
|
||||
|
||||
// 修改获取限制等级颜色的函数
|
||||
const getRestrictionLevelColor = (level: string) => {
|
||||
const colorMap = {
|
||||
"1": "text-gray-600",
|
||||
"2": "text-yellow-600",
|
||||
"3": "text-red-600"
|
||||
};
|
||||
return colorMap[level as keyof typeof colorMap] || "text-gray-600";
|
||||
}
|
||||
|
||||
// 添加时间格式化函数
|
||||
const formatDateTime = (dateString: string) => {
|
||||
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, '-');
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
{isLoading ? (
|
||||
@@ -562,7 +650,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
<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 overflow-x-hidden">
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen overflow-x-hidden">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="flex items-center p-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
@@ -618,12 +706,12 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="overview">账号概览</TabsTrigger>
|
||||
<TabsTrigger value="friends">
|
||||
好友列表 ({friendsTotal > 0 ? friendsTotal : account.friendCount})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="friends">
|
||||
好友列表{activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal})` : ''}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4 mt-4">
|
||||
@@ -634,8 +722,16 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
<Clock className="w-4 h-4" />
|
||||
<span className="text-sm">账号年龄</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-blue-600">{formatAccountAge(account.accountAge)}</div>
|
||||
<div className="text-sm text-gray-500 mt-1">注册时间:{account.firstMomentDate}</div>
|
||||
{accountSummary && (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-blue-600">
|
||||
{formatAccountAge(calculateAccountAge(accountSummary.accountAge))}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
注册时间:{new Date(accountSummary.accountAge).toLocaleDateString()}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
@@ -643,8 +739,12 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
<span className="text-sm">活跃程度</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-blue-600">{account.chatFrequency}次/天</div>
|
||||
<div className="text-sm text-gray-500 mt-1">总聊天数:{account.totalChats.toLocaleString()}</div>
|
||||
{accountSummary && (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-blue-600">{accountSummary.activityLevel.dayTimes}次/天</div>
|
||||
<div className="text-sm text-gray-500 mt-1">总聊天数:{accountSummary.activityLevel.allTimes.toLocaleString()}</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -655,34 +755,48 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
<Star className="w-4 h-4 text-yellow-500" />
|
||||
<span className="font-medium">账号权重评估</span>
|
||||
</div>
|
||||
<div className={`flex items-center space-x-2 ${getWeightColor(account.accountWeight)}`}>
|
||||
<span className="text-2xl font-bold">{account.accountWeight}</span>
|
||||
<span className="text-sm">分</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mb-4">{getWeightDescription(account.accountWeight)}</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex-shrink-0">账号年龄</span>
|
||||
<Progress value={account.weightFactors.ageFactor * 100} className="flex-1 min-w-0 mx-2" />
|
||||
<span className="flex-shrink-0">{(account.weightFactors.ageFactor * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex-shrink-0">活跃度</span>
|
||||
<Progress value={account.weightFactors.activityFactor * 100} className="flex-1 min-w-0 mx-2" />
|
||||
<span className="flex-shrink-0">{(account.weightFactors.activityFactor * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex-shrink-0">限制影响</span>
|
||||
<Progress value={account.weightFactors.restrictionFactor * 100} className="flex-1 min-w-0 mx-2" />
|
||||
<span className="flex-shrink-0">{(account.weightFactors.restrictionFactor * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex-shrink-0">实名认证</span>
|
||||
<Progress value={account.weightFactors.verificationFactor * 100} className="flex-1 min-w-0 mx-2" />
|
||||
<span className="flex-shrink-0">{(account.weightFactors.verificationFactor * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
{accountSummary && (
|
||||
<div className={`flex items-center space-x-2 ${getWeightColor(accountSummary.accountWeight.scope)}`}>
|
||||
<span className="text-2xl font-bold">{accountSummary.accountWeight.scope}</span>
|
||||
<span className="text-sm">分</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{accountSummary && (
|
||||
<>
|
||||
<p className="text-sm text-gray-500 mb-4">{getWeightDescription(accountSummary.accountWeight.scope)}</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center">
|
||||
<span className="flex-shrink-0 w-16 text-sm">账号年龄</span>
|
||||
<div className="flex-1 mx-4">
|
||||
<Progress value={accountSummary.accountWeight.ageWeight} className="h-2" />
|
||||
</div>
|
||||
<span className="flex-shrink-0 w-12 text-sm text-right">{accountSummary.accountWeight.ageWeight}%</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="flex-shrink-0 w-16 text-sm">活跃度</span>
|
||||
<div className="flex-1 mx-4">
|
||||
<Progress value={accountSummary.accountWeight.activityWeigth} className="h-2" />
|
||||
</div>
|
||||
<span className="flex-shrink-0 w-12 text-sm text-right">{accountSummary.accountWeight.activityWeigth}%</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="flex-shrink-0 w-16 text-sm">限制影响</span>
|
||||
<div className="flex-1 mx-4">
|
||||
<Progress value={accountSummary.accountWeight.restrictWeight} className="h-2" />
|
||||
</div>
|
||||
<span className="flex-shrink-0 w-12 text-sm text-right">{accountSummary.accountWeight.restrictWeight}%</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="flex-shrink-0 w-16 text-sm">实名认证</span>
|
||||
<div className="flex-1 mx-4">
|
||||
<Progress value={accountSummary.accountWeight.realNameWeight} className="h-2" />
|
||||
</div>
|
||||
<span className="flex-shrink-0 w-12 text-sm text-right">{accountSummary.accountWeight.realNameWeight}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 添加好友统计 */}
|
||||
@@ -701,29 +815,31 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
</TooltipContent>
|
||||
</UITooltip>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-500">今日已添加</span>
|
||||
<span className="text-xl font-bold text-blue-600">{account.todayAdded}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span className="text-gray-500">添加进度</span>
|
||||
<span>
|
||||
{account.todayAdded}/{calculateMaxDailyAdds(account.accountWeight)}
|
||||
</span>
|
||||
{accountSummary && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-500">今日已添加</span>
|
||||
<span className="text-xl font-bold text-blue-600">{accountSummary.statistics.todayAdded}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span className="text-gray-500">添加进度</span>
|
||||
<span>
|
||||
{accountSummary.statistics.todayAdded}/{accountSummary.statistics.addLimit}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={(accountSummary.statistics.todayAdded / accountSummary.statistics.addLimit) * 100}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
根据当前账号权重({accountSummary.accountWeight.scope}分),每日最多可添加{" "}
|
||||
<span className="font-medium text-blue-600">{accountSummary.statistics.addLimit}</span>{" "}
|
||||
个好友
|
||||
</div>
|
||||
<Progress
|
||||
value={(account.todayAdded / calculateMaxDailyAdds(account.accountWeight)) * 100}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
根据当前账号权重({account.accountWeight}分),每日最多可添加{" "}
|
||||
<span className="font-medium text-blue-600">{calculateMaxDailyAdds(account.accountWeight)}</span>{" "}
|
||||
个好友
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 限制记录 */}
|
||||
@@ -733,20 +849,26 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
<Shield className="w-4 h-4 text-red-500" />
|
||||
<span className="font-medium">限制记录</span>
|
||||
</div>
|
||||
<Badge variant="outline" className="cursor-pointer" onClick={() => setShowRestrictions(true)}>
|
||||
共 {account.restrictionRecords.length} 次
|
||||
</Badge>
|
||||
{accountSummary && (
|
||||
<Badge variant="outline" className="cursor-pointer" onClick={() => setShowRestrictions(true)}>
|
||||
共 {accountSummary.restrictions.length} 次
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{account.restrictionRecords.slice(0, 2).map((record) => (
|
||||
<div key={record.id} className="text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={getRestrictionTypeColor(record.type)}>{record.reason}</span>
|
||||
<span className="text-gray-500">{record.date}</span>
|
||||
{accountSummary && (
|
||||
<div className="space-y-2">
|
||||
{accountSummary.restrictions.slice(0, 2).map((record) => (
|
||||
<div key={record.id} className="text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={`${getRestrictionLevelColor(record.level)}`}>
|
||||
{record.reason}
|
||||
</span>
|
||||
<span className="text-gray-500">{formatDateTime(record.date)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
@@ -866,13 +988,15 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[400px]">
|
||||
<div className="space-y-4">
|
||||
{account.restrictionRecords.map((record) => (
|
||||
{accountSummary && accountSummary.restrictions.map((record) => (
|
||||
<div key={record.id} className="border-b pb-4 last:border-0">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className={`text-sm ${getRestrictionTypeColor(record.type)}`}>{record.reason}</div>
|
||||
<Badge variant="outline">{record.date}</Badge>
|
||||
<div className={`text-sm ${getRestrictionLevelColor(record.level)}`}>
|
||||
{record.reason}
|
||||
</div>
|
||||
<Badge variant="outline">{formatDateTime(record.date)}</Badge>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mt-1">恢复时间:{record.recoveryTime}</div>
|
||||
<div className="text-sm text-gray-500 mt-1">恢复时间:{formatDateTime(record.date)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -279,7 +279,10 @@ export default function WechatAccountsPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h3 className="font-medium truncate max-w-[180px]">{account.nickname}</h3>
|
||||
<Badge variant={account.status === "normal" ? "default" : "destructive"} className={account.status === "normal" ? "bg-green-500 hover:bg-green-600 text-white" : ""}>
|
||||
<Badge
|
||||
variant={account.status === "normal" ? "default" : "destructive"}
|
||||
className={`min-w-[48px] text-center justify-center ${account.status === "normal" ? "bg-green-500 hover:bg-green-600 text-white" : ""}`}
|
||||
>
|
||||
{account.status === "normal" ? "正常" : "异常"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,12 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 微信好友模型类
|
||||
*/
|
||||
class WechatFriend extends Model
|
||||
class WechatFriendShip extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名
|
||||
protected $name = 'wechat_friend';
|
||||
protected $name = 'wechat_friendship';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
@@ -23,9 +23,9 @@ Route::group('v1/', function () {
|
||||
// 设备微信相关
|
||||
Route::group('device/wechats', function () {
|
||||
Route::get('', 'app\cunkebao\controller\wechat\GetWechatsOnDevicesV1Controller@index'); // 获取在线微信账号列表
|
||||
Route::get(':id', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index'); // 获取微信号详情
|
||||
Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index'); // 获取微信号详情
|
||||
Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index'); // 获取微信好友列表
|
||||
|
||||
Route::get('friends', 'app\cunkebao\controller\DeviceWechat@getFriends'); // 获取微信好友列表
|
||||
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); // 获取在线微信账号数量
|
||||
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
|
||||
Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态
|
||||
|
||||
@@ -7,7 +7,7 @@ use app\common\model\DeviceTaskconf as DeviceTaskconfModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatFriend;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
@@ -97,7 +97,7 @@ class GetDeviceDetailV1Controller extends BaseController
|
||||
$ownerWechatId = DeviceWechatLogin::where(compact('companyId', 'deviceId'))->order('createTime desc')->value('wechatId');
|
||||
|
||||
if ($ownerWechatId) {
|
||||
return WechatFriend::where(['ownerWechatId' => $ownerWechatId])->count();
|
||||
return WechatFriendShipModel::where(['ownerWechatId' => $ownerWechatId])->count();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace app\cunkebao\controller\device;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatFriend as WechatFriendModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
@@ -105,7 +105,7 @@ class GetDeviceListV1Controller extends BaseController
|
||||
$sections = $item->toArray();
|
||||
|
||||
if ($item->wechatId) {
|
||||
$sections['totalFriend'] = WechatFriendModel::where(['ownerWechatId' => $item->wechatId])->count();
|
||||
$sections['totalFriend'] = WechatFriendShipModel::where(['ownerWechatId' => $item->wechatId])->count();
|
||||
}
|
||||
|
||||
array_push($resultSets, $sections);
|
||||
|
||||
@@ -6,7 +6,7 @@ use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriend;
|
||||
use app\common\model\WechatFriendShip;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
@@ -110,7 +110,7 @@ class GetRelatedAccountsV1Controller extends BaseController
|
||||
*/
|
||||
protected function countFriend(string $wechatId): int
|
||||
{
|
||||
return WechatFriend::where(['ownerWechatId' => $wechatId])->count();
|
||||
return WechatFriendShip::where(['ownerWechatId' => $wechatId])->count();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace app\cunkebao\controller\friend;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\WechatFriend;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use think\Db;
|
||||
|
||||
@@ -42,7 +42,7 @@ class GetFriendListV1Controller extends BaseController
|
||||
}
|
||||
|
||||
|
||||
$data = WechatFriend::alias('wf')
|
||||
$data = WechatFriendShipModel::alias('wf')
|
||||
->field(['wa1.nickname','wa1.avatar','wa1.alias','wf.id','wf.wechatId','wa2.nickname as ownerNickname','wa2.alias as ownerAlias','wa2.wechatId as ownerWechatId','wf.createTime'])
|
||||
->Join('wechat_account wa1','wf.wechatId = wa1.wechatId')
|
||||
->Join('wechat_account wa2','wf.ownerWechatId = wa2.wechatId')
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
|
||||
/**
|
||||
* 设备微信控制器
|
||||
*/
|
||||
class GetWechatOnDeviceFriendsV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* 构建返回数据
|
||||
*
|
||||
* @param \think\Paginator $result
|
||||
* @return array
|
||||
*/
|
||||
protected function makeResultedSet(\think\Paginator $result): array
|
||||
{
|
||||
$resultSets = [];
|
||||
|
||||
foreach ($result->items() as $item) {
|
||||
$item->tags = json_decode($item->tags);
|
||||
array_push($resultSets, $item->toArray());
|
||||
}
|
||||
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据微信账号ID获取好友列表
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getFriendsByWechatIdAndQueryParams(array $where): \think\Paginator
|
||||
{
|
||||
$query = WechatFriendShipModel::alias('f')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.nickname', 'w.avatar',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatId',
|
||||
'f.memo', 'f.tags'
|
||||
]
|
||||
)
|
||||
->join('wechat_account w', 'w.wechatId = f.wechatId');
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始的64位的微信id
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getStringWechatIdByNumberId(): string
|
||||
{
|
||||
$account = WechatAccountModel::find(
|
||||
$this->request->param('id/d')
|
||||
);
|
||||
|
||||
if (is_null($account)) {
|
||||
throw new \Exception('微信账号不存在', 404);
|
||||
}
|
||||
|
||||
return $account->wechatId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
// 关键词搜索(同时搜索好友备注和标签)
|
||||
if (!empty($keyword = $this->request->param('keyword'))) {
|
||||
$where[] = ['exp', "f.memo LIKE '%{$keyword}%' OR f.tags LIKE '%{$keyword}%'"];
|
||||
}
|
||||
|
||||
$where['f.ownerWechatId'] = $this->getStringWechatIdByNumberId();
|
||||
|
||||
return array_merge($where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信好友列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$result = $this->getFriendsByWechatIdAndQueryParams(
|
||||
$this->makeWhere()
|
||||
);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriend as WechatFriendModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
@@ -70,13 +70,13 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'type' => 'warnnig',
|
||||
'level' => 2,
|
||||
'reason' => '频繁添加好友',
|
||||
'date' => date('Y-m-d H:i:s', strtotime('-1 day')),
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'type' => 'error',
|
||||
'level' => 3,
|
||||
'reason' => '营销内容违规',
|
||||
'date' => date('Y-m-d H:i:s', strtotime('-1 day')),
|
||||
],
|
||||
@@ -208,7 +208,7 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
*/
|
||||
protected function getTodayNewFriendCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendModel::where( compact('ownerWechatId') )
|
||||
return WechatFriendShipModel::where( compact('ownerWechatId') )
|
||||
->whereBetween('createTime',
|
||||
[
|
||||
strtotime(date('Y-m-d 00:00:00')),
|
||||
@@ -228,7 +228,7 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
protected function getStatistics(string $wechatId, array $accountWeight): array
|
||||
{
|
||||
return [
|
||||
'addedCount' => $this->getTodayNewFriendCount($wechatId),
|
||||
'todayAdded' => $this->getTodayNewFriendCount($wechatId),
|
||||
'addLimit' => $this->_calAllowedFriends($accountWeight['scope'])
|
||||
];
|
||||
}
|
||||
@@ -239,9 +239,11 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getStringWechatId(): string
|
||||
protected function getStringWechatIdByNumberId(): string
|
||||
{
|
||||
$account = WechatAccountModel::find(333333);
|
||||
$account = WechatAccountModel::find(
|
||||
$this->request->param('id/d')
|
||||
);
|
||||
|
||||
if (is_null($account)) {
|
||||
throw new \Exception('微信账号不存在', 404);
|
||||
@@ -258,9 +260,9 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// $wechatId = $this->getStringWechatId();
|
||||
$wechatId = '1111111';
|
||||
$wechatId = $this->getStringWechatIdByNumberId();
|
||||
|
||||
// 以下内容依次加工数据
|
||||
$accountAge = $this->getRegisterDate($wechatId);
|
||||
$activityLevel = $this->getActivityLevel($wechatId);
|
||||
$accountWeight = $this->getAccountWeight($wechatId);
|
||||
|
||||
@@ -8,7 +8,7 @@ use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriend as WechatFriendModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
@@ -36,7 +36,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
*/
|
||||
protected function getTodayNewFriendCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendModel::where( compact('ownerWechatId') )
|
||||
return WechatFriendShipModel::where( compact('ownerWechatId') )
|
||||
->whereBetween('createTime',
|
||||
[
|
||||
strtotime(date('Y-m-d 00:00:00')),
|
||||
@@ -65,7 +65,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
*/
|
||||
protected function getFriendsCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendModel::where(compact('ownerWechatId'))->count();
|
||||
return WechatFriendShipModel::where(compact('ownerWechatId'))->count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
$where['w.wechatId'] = array('in', implode(',', $wechatIds));
|
||||
|
||||
return array_merge($params, $where);
|
||||
return array_merge($where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
namespace app\cunkebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 微信好友模型类
|
||||
*/
|
||||
class WechatFriend extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'wechat_friend';
|
||||
|
||||
/**
|
||||
* 根据微信账号ID获取好友列表
|
||||
*
|
||||
* @param string $ownerWechatId 所有者微信ID
|
||||
* @param array $params 查询条件参数
|
||||
* @param int $page 页码
|
||||
* @param int $limit 每页数量
|
||||
* @return array 好友列表和总数
|
||||
*/
|
||||
public static function getFriendsByWechatId($ownerWechatId, $params = [], $page = 1, $limit = 20)
|
||||
{
|
||||
// 构建基础查询
|
||||
$query = self::where('ownerWechatId', $ownerWechatId)
|
||||
->where('isDeleted', 0);
|
||||
|
||||
// 添加筛选条件(昵称、备注、微信号、标签)
|
||||
if (!empty($params['keyword'])) {
|
||||
$keyword = $params['keyword'];
|
||||
$query->where(function($q) use ($keyword) {
|
||||
$q->whereOr('nickname', 'like', "%{$keyword}%")
|
||||
->whereOr('conRemark', 'like', "%{$keyword}%")
|
||||
->whereOr('alias', 'like', "%{$keyword}%")
|
||||
->whereOr("JSON_SEARCH(labels, 'one', '%{$keyword}%') IS NOT NULL");
|
||||
});
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
$total = $query->count();
|
||||
|
||||
// 分页查询数据
|
||||
$friends = $query->page($page, $limit)
|
||||
->order('createTime desc')
|
||||
->field('wechatId, alias, avatar, labels, accountNickname, accountRealName, nickname, conRemark, gender, region')
|
||||
->select();
|
||||
|
||||
return [
|
||||
'list' => $friends,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use app\common\model\Company as CompanyModel;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatFriend as WechatFriendModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendModel;
|
||||
use app\superadmin\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace app\superadmin\controller\company;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\WechatFriend as WechatFriendModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
@@ -72,7 +72,7 @@ class GetCompanyDevicesForProfileController extends Controller
|
||||
$relations = $this->getDeviceWechatRelationsByDeviceIds($deviceIds);
|
||||
|
||||
// 统计微信好友数量
|
||||
$friendCounts = WechatFriendModel::alias('f')
|
||||
$friendCounts = WechatFriendShipModel::alias('f')
|
||||
->field([
|
||||
'f.ownerWechatId wechatId', 'count(*) friendCount'
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user