微信号的好友列表

This commit is contained in:
柳清爽
2025-04-03 10:12:02 +08:00
parent bb44a51f33
commit 170192c7a3
5 changed files with 527 additions and 262 deletions

View File

@@ -204,4 +204,39 @@ const mapRestrictionType = (type: string): "friend_limit" | "marketing" | "spam"
};
return typeMap[type] || 'other';
};
};
/**
* 获取微信好友列表
* @param wechatId 微信账号ID
* @param page 页码
* @param limit 每页数量
* @param keyword 搜索关键词
* @returns 好友列表数据
*/
export const fetchWechatFriends = async (
wechatId: string | number,
page: number = 1,
limit: number = 20,
keyword: string = ""
): Promise<{ code: number; msg: string; data: any }> => {
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;
} catch (error) {
console.error('获取微信好友列表失败:', error);
throw error;
}
}

View File

@@ -1,6 +1,6 @@
"use client"
import { useState, useEffect } from "react"
import { useState, useEffect, useRef, useCallback } from "react"
import { useRouter } from "next/navigation"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
@@ -44,7 +44,7 @@ import {
PaginationPrevious,
} from "@/components/ui/pagination"
import { toast } from "@/components/ui/use-toast"
import { fetchWechatAccountDetail, transformWechatAccountDetail } from "@/api/wechat-accounts"
import { fetchWechatAccountDetail, transformWechatAccountDetail, fetchWechatFriends } from "@/api/wechat-accounts"
interface RestrictionRecord {
id: string
@@ -120,11 +120,274 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
const [showFriendDetail, setShowFriendDetail] = useState(false)
const [selectedFriend, setSelectedFriend] = useState<WechatFriend | null>(null)
const [searchQuery, setSearchQuery] = useState("")
const [currentPage, setCurrentPage] = useState(1)
const [activeTab, setActiveTab] = useState("overview")
const friendsPerPage = 10
const [isLoading, setIsLoading] = useState(false)
// 好友列表相关状态
const [friends, setFriends] = useState<any[]>([])
const [friendsPage, setFriendsPage] = useState(1)
const [friendsTotal, setFriendsTotal] = useState(0)
const [hasMoreFriends, setHasMoreFriends] = useState(true)
const [isFetchingFriends, setIsFetchingFriends] = useState(false)
const friendsObserver = useRef<IntersectionObserver | null>(null)
const friendsLoadingRef = useRef<HTMLDivElement | null>(null)
const friendsContainerRef = useRef<HTMLDivElement | null>(null)
// 计算好友列表容器高度
const getFriendsContainerHeight = () => {
// 最少显示一条记录的高度,最多显示十条记录的高度
const minHeight = 80; // 单条记录高度
const maxHeight = 800; // 十条记录高度
if (friends.length === 0) return minHeight;
return Math.min(Math.max(friends.length * 80, minHeight), maxHeight);
};
// 生成模拟账号数据(作为备用,服务器请求失败时使用)
const generateMockAccountData = (): WechatAccountDetail => {
// 生成随机标签
const generateRandomTags = (count: number): FriendTag[] => {
const tagPool = [
{ name: "潜在客户", color: "bg-blue-100 text-blue-800" },
{ name: "高意向", color: "bg-green-100 text-green-800" },
{ name: "已成交", color: "bg-purple-100 text-purple-800" },
{ name: "需跟进", color: "bg-yellow-100 text-yellow-800" },
{ name: "活跃用户", color: "bg-indigo-100 text-indigo-800" },
{ name: "沉默用户", color: "bg-gray-100 text-gray-800" },
{ name: "企业客户", color: "bg-red-100 text-red-800" },
{ name: "个人用户", color: "bg-pink-100 text-pink-800" },
{ name: "新增好友", color: "bg-emerald-100 text-emerald-800" },
{ name: "老客户", color: "bg-amber-100 text-amber-800" },
];
return Array.from({ length: Math.floor(Math.random() * count) + 1 }, () => {
const randomTag = tagPool[Math.floor(Math.random() * tagPool.length)];
return {
id: `tag-${Math.random().toString(36).substring(2, 9)}`,
name: randomTag.name,
color: randomTag.color,
};
});
};
// 生成随机好友
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150;
const generateFriends = (count: number): WechatFriend[] => {
return Array.from({ length: count }, (_, i) => {
const firstName = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"][Math.floor(Math.random() * 10)];
const secondName = ["小", "大", "明", "华", "强", "伟", "芳", "娜", "秀", "英"][
Math.floor(Math.random() * 10)
];
const lastName = ["明", "华", "强", "伟", "芳", "娜", "秀", "英", "军", "杰"][Math.floor(Math.random() * 10)];
const nickname = firstName + secondName + lastName;
// 生成随机的添加时间过去1年内
const addDate = new Date();
addDate.setDate(addDate.getDate() - Math.floor(Math.random() * 365));
// 生成随机的最后互动时间过去30天内
const lastDate = new Date();
lastDate.setDate(lastDate.getDate() - Math.floor(Math.random() * 30));
return {
id: `friend-${i}`,
avatar: `/placeholder.svg?height=40&width=40&text=${nickname[0]}`,
nickname,
wechatId: `wxid_${Math.random().toString(36).substring(2, 9)}`,
remark:
Math.random() > 0.5
? `${nickname}${["同事", "客户", "朋友", "同学"][Math.floor(Math.random() * 4)]}`
: "",
addTime: addDate.toISOString().split("T")[0],
lastInteraction: lastDate.toISOString().split("T")[0],
tags: generateRandomTags(3),
region: ["广东", "北京", "上海", "浙江", "江苏", "四川", "湖北", "福建", "山东", "河南"][
Math.floor(Math.random() * 10)
],
source: ["抖音", "小红书", "朋友介绍", "搜索添加", "群聊", "附近的人", "名片分享"][
Math.floor(Math.random() * 7)
],
notes:
Math.random() > 0.7
? ["对产品很感兴趣", "需要进一步跟进", "已购买过产品", "价格敏感", "需要更多信息"][
Math.floor(Math.random() * 5)
]
: "",
};
});
};
const friends = generateFriends(friendCount);
const mockAccount: WechatAccountDetail = {
id: params.id,
avatar:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/img_v3_02jn_e7fcc2a4-3560-478d-911a-4ccd69c6392g.jpg-a8zVtwxMuSrPWN9dfWH93EBY0yM3Dh.jpeg",
nickname: "卡若-25vig",
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
deviceId: "device-1",
deviceName: "设备1",
friendCount: friends.length,
todayAdded: 12,
status: "normal",
lastActive: new Date().toLocaleString(),
messageCount: 1234,
activeRate: 87,
accountAge: {
years: 2,
months: 8,
},
totalChats: 15234,
chatFrequency: 42,
restrictionRecords: [
{
id: "1",
date: "2024-02-25",
reason: "添加好友过于频繁",
recoveryTime: "2024-02-26",
type: "friend_limit",
},
{
id: "2",
date: "2024-01-15",
reason: "营销内容违规",
recoveryTime: "2024-01-16",
type: "marketing",
},
],
isVerified: true,
firstMomentDate: "2021-06-15",
accountWeight: 85,
weightFactors: {
restrictionFactor: 0.8,
verificationFactor: 1.0,
ageFactor: 0.9,
activityFactor: 0.85,
},
weeklyStats: Array.from({ length: 7 }, (_, i) => ({
date: `Day ${i + 1}`,
friends: Math.floor(Math.random() * 50) + 50,
messages: Math.floor(Math.random() * 100) + 100,
})),
friends: friends,
};
return mockAccount;
};
// 随机生成标签颜色
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 fetchFriends = useCallback(async (page: number = 1, isNewSearch: boolean = false) => {
if (!account || isFetchingFriends) return;
try {
setIsFetchingFriends(true);
// 调用API获取好友列表
const response = await fetchWechatFriends(account.wechatId, page, 20, searchQuery);
if (response && response.code === 200) {
const newFriends = response.data.list.map((friend: any) => ({
id: friend.wechatId,
avatar: friend.avatar,
nickname: friend.nickname || '未设置昵称',
wechatId: friend.wechatId,
remark: friend.remark || '',
addTime: '2024-01-01', // 接口未返回,使用默认值
lastInteraction: '2024-01-01', // 接口未返回,使用默认值
tags: (friend.labels || []).map((label: string, index: number) => ({
id: `tag-${index}`,
name: label,
color: getRandomTagColor(),
})),
region: friend.region || '未知地区',
source: '微信好友', // 接口未返回,使用默认值
notes: '',
}));
// 更新状态
if (isNewSearch) {
setFriends(newFriends);
} else {
setFriends(prev => [...prev, ...newFriends]);
}
setFriendsTotal(response.data.total);
setFriendsPage(page);
setHasMoreFriends(page * 20 < response.data.total);
} else {
toast({
title: "获取好友列表失败",
description: response?.msg || "请稍后再试",
variant: "destructive"
});
}
} catch (error) {
console.error("获取好友列表失败:", error);
toast({
title: "获取好友列表失败",
description: "请检查网络连接或稍后再试",
variant: "destructive"
});
} finally {
setIsFetchingFriends(false);
}
}, [account, searchQuery]);
// 处理搜索
const handleSearch = useCallback(() => {
setFriends([]);
setFriendsPage(1);
setHasMoreFriends(true);
fetchFriends(1, true);
}, [fetchFriends]);
// 处理标签切换
useEffect(() => {
if (activeTab === "friends" && account && friends.length === 0) {
fetchFriends(1, true);
}
}, [activeTab, account, friends.length, fetchFriends]);
// 设置IntersectionObserver用于懒加载
useEffect(() => {
friendsObserver.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMoreFriends && !isFetchingFriends) {
fetchFriends(friendsPage + 1);
}
}, { threshold: 0.5 });
return () => {
if (friendsObserver.current) {
friendsObserver.current.disconnect();
}
};
}, [fetchFriends, friendsPage, hasMoreFriends, isFetchingFriends]);
// 观察加载指示器
useEffect(() => {
if (friendsLoadingRef.current && friendsObserver.current) {
friendsObserver.current.observe(friendsLoadingRef.current);
}
return () => {
if (friendsLoadingRef.current && friendsObserver.current) {
friendsObserver.current.unobserve(friendsLoadingRef.current);
}
};
}, [friendsLoadingRef.current, friendsObserver.current]);
useEffect(() => {
// 模拟API调用获取账号详情
const fetchAccount = async () => {
@@ -199,23 +462,23 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
}
const formatAccountAge = (age: { years: number; months: number }) => {
if (age.years === 0) {
return `${age.months}个月`
if (age.years > 0) {
return `${age.years}${age.months}个月`;
}
if (age.months === 0) {
return `${age.years}`
}
return `${age.years}${age.months}个月`
}
return `${age.months}个月`;
};
const handleTransferFriends = () => {
setShowTransferConfirm(true)
}
const confirmTransferFriends = () => {
// 模拟API调用
toast({
title: "好友转移成功",
description: `已成功转移 ${account?.friends.length} 个好友`,
});
setShowTransferConfirm(false)
// 跳转到新建计划的订单导入场景
router.push(`/scenarios/new?type=order&source=${account.wechatId}`)
}
const handleFriendClick = (friend: WechatFriend) => {
@@ -223,150 +486,6 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
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 tagPool = [
{ name: "潜在客户", color: "bg-blue-100 text-blue-800" },
{ name: "高意向", color: "bg-green-100 text-green-800" },
{ name: "已成交", color: "bg-purple-100 text-purple-800" },
{ name: "需跟进", color: "bg-yellow-100 text-yellow-800" },
{ name: "活跃用户", color: "bg-indigo-100 text-indigo-800" },
{ name: "沉默用户", color: "bg-gray-100 text-gray-800" },
{ name: "企业客户", color: "bg-red-100 text-red-800" },
{ name: "个人用户", color: "bg-pink-100 text-pink-800" },
{ name: "新增好友", color: "bg-emerald-100 text-emerald-800" },
{ name: "老客户", color: "bg-amber-100 text-amber-800" },
]
return Array.from({ length: Math.floor(Math.random() * count) + 1 }, () => {
const randomTag = tagPool[Math.floor(Math.random() * tagPool.length)]
return {
id: `tag-${Math.random().toString(36).substring(2, 9)}`,
name: randomTag.name,
color: randomTag.color,
}
})
}
// 生成随机好友
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150
const generateFriends = (count: number) => {
return Array.from({ length: count }, (_, i) => {
const firstName = ["张", "王", "李", "赵", "陈", "刘", "杨", "黄", "周", "吴"][Math.floor(Math.random() * 10)]
const secondName = ["小", "大", "明", "华", "强", "伟", "芳", "娜", "秀", "英"][
Math.floor(Math.random() * 10)
]
const lastName = ["明", "华", "强", "伟", "芳", "娜", "秀", "英", "军", "杰"][Math.floor(Math.random() * 10)]
const nickname = firstName + secondName + lastName
// 生成随机的添加时间过去1年内
const addDate = new Date()
addDate.setDate(addDate.getDate() - Math.floor(Math.random() * 365))
// 生成随机的最后互动时间过去30天内
const lastDate = new Date()
lastDate.setDate(lastDate.getDate() - Math.floor(Math.random() * 30))
return {
id: `friend-${i}`,
avatar: `/placeholder.svg?height=40&width=40&text=${nickname[0]}`,
nickname,
wechatId: `wxid_${Math.random().toString(36).substring(2, 9)}`,
remark:
Math.random() > 0.5
? `${nickname}${["同事", "客户", "朋友", "同学"][Math.floor(Math.random() * 4)]}`
: "",
addTime: addDate.toISOString().split("T")[0],
lastInteraction: lastDate.toISOString().split("T")[0],
tags: generateRandomTags(3),
region: ["广东", "北京", "上海", "浙江", "江苏", "四川", "湖北", "福建", "山东", "河南"][
Math.floor(Math.random() * 10)
],
source: ["抖音", "小红书", "朋友介绍", "搜索添加", "群聊", "附近的人", "名片分享"][
Math.floor(Math.random() * 7)
],
notes:
Math.random() > 0.7
? ["对产品很感兴趣", "需要进一步跟进", "已购买过产品", "价格敏感", "需要更多信息"][
Math.floor(Math.random() * 5)
]
: "",
}
})
}
const friends = generateFriends(friendCount)
const mockAccount: WechatAccountDetail = {
id: params.id,
avatar:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/img_v3_02jn_e7fcc2a4-3560-478d-911a-4ccd69c6392g.jpg-a8zVtwxMuSrPWN9dfWH93EBY0yM3Dh.jpeg",
nickname: "卡若-25vig",
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
deviceId: "device-1",
deviceName: "设备1",
friendCount: friends.length,
todayAdded: 12,
status: "normal",
lastActive: new Date().toLocaleString(),
messageCount: 1234,
activeRate: 87,
accountAge: {
years: 2,
months: 8,
},
totalChats: 15234,
chatFrequency: 42,
restrictionRecords: [
{
id: "1",
date: "2024-02-25",
reason: "添加好友过于频繁",
recoveryTime: "2024-02-26",
type: "friend_limit",
},
{
id: "2",
date: "2024-01-15",
reason: "营销内容违规",
recoveryTime: "2024-01-16",
type: "marketing",
},
],
isVerified: true,
firstMomentDate: "2021-06-15",
accountWeight: 85,
weightFactors: {
restrictionFactor: 0.8,
verificationFactor: 1.0,
ageFactor: 0.9,
activityFactor: 0.85,
},
weeklyStats: Array.from({ length: 7 }, (_, i) => ({
date: `Day ${i + 1}`,
friends: Math.floor(Math.random() * 50) + 50,
messages: Math.floor(Math.random() * 100) + 100,
})),
friends: friends,
}
return mockAccount
}
return (
<TooltipProvider>
{isLoading ? (
@@ -561,107 +680,82 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
placeholder="搜索好友昵称/微信号/备注/标签"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" onClick={handleSearch}>
<Filter className="h-4 w-4" />
</Button>
</div>
{/* 好友列表 */}
<div className="space-y-2">
{paginatedFriends.length === 0 ? (
<div
ref={friendsContainerRef}
className="space-y-2 transition-all duration-300"
style={{
minHeight: '80px',
height: `${getFriendsContainerHeight()}px`,
overflowY: 'auto'
}}
>
{friends.length === 0 && !isFetchingFriends ? (
<div className="text-center py-8 text-gray-500"></div>
) : (
paginatedFriends.map((friend) => (
<div
key={friend.id}
className="flex items-center p-3 border rounded-lg hover:bg-gray-50 cursor-pointer"
onClick={() => handleFriendClick(friend)}
>
<Avatar className="h-10 w-10 mr-3">
<AvatarImage src={friend.avatar} />
<AvatarFallback>{friend.nickname[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate max-w-[180px]">
{friend.nickname}
{friend.remark && <span className="text-gray-500 ml-1 truncate">({friend.remark})</span>}
<>
{friends.map((friend) => (
<div
key={friend.id}
className="flex items-center p-3 border rounded-lg hover:bg-gray-50 cursor-pointer"
onClick={() => handleFriendClick(friend)}
>
<Avatar className="h-10 w-10 mr-3">
<AvatarImage src={friend.avatar} />
<AvatarFallback>{friend.nickname?.[0] || 'U'}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate max-w-[180px]">
{friend.nickname}
{friend.remark && <span className="text-gray-500 ml-1 truncate">({friend.remark})</span>}
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
<div className="text-sm text-gray-500 truncate">{friend.wechatId}</div>
<div className="flex flex-wrap gap-1 mt-1">
{friend.tags.slice(0, 3).map((tag: FriendTag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{friend.tags.length > 3 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{friend.tags.length - 3}
</span>
)}
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
</div>
<div className="text-sm text-gray-500 truncate">{friend.wechatId}</div>
<div className="flex flex-wrap gap-1 mt-1">
{friend.tags.slice(0, 3).map((tag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{friend.tags.length > 3 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{friend.tags.length - 3}
</span>
)}
</div>
</div>
</div>
))
))}
{/* 懒加载指示器 */}
{hasMoreFriends && (
<div ref={friendsLoadingRef} className="py-4 flex justify-center">
{isFetchingFriends && <Loader2 className="h-6 w-6 animate-spin text-blue-500" />}
</div>
)}
</>
)}
</div>
{/* 分页 */}
{totalPages > 1 && (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.max(1, prev - 1))
}}
/>
</PaginationItem>
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNumber
if (totalPages <= 5) {
pageNumber = i + 1
} else if (currentPage <= 3) {
pageNumber = i + 1
} else if (currentPage >= totalPages - 2) {
pageNumber = totalPages - 4 + i
} else {
pageNumber = currentPage - 2 + i
}
return (
<PaginationItem key={pageNumber}>
<PaginationLink
href="#"
isActive={currentPage === pageNumber}
onClick={(e) => {
e.preventDefault()
setCurrentPage(pageNumber)
}}
>
{pageNumber}
</PaginationLink>
</PaginationItem>
)
})}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
setCurrentPage((prev) => Math.min(totalPages, prev + 1))
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
{/* 显示加载状态和总数 */}
<div className="text-sm text-gray-500 text-center">
{friendsTotal > 0 && (
<span>
{Math.min(friends.length, friendsTotal)} / {friendsTotal}
</span>
)}
</div>
</div>
</Card>
</TabsContent>
@@ -770,7 +864,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
</div>
<div className="flex flex-wrap gap-2">
{selectedFriend.tags.map((tag) => (
{selectedFriend.tags.map((tag: FriendTag) => (
<span key={tag.id} className={`text-sm px-2 py-1 rounded-full ${tag.color}`}>
{tag.name}
</span>

View File

@@ -23,6 +23,7 @@ Route::group('v1/', function () {
// 设备微信相关
Route::group('device/wechats', function () {
Route::get('friends', 'app\\devices\\controller\\DeviceWechat@getFriends'); // 获取微信好友列表
Route::get('count', 'app\\devices\\controller\\DeviceWechat@count'); // 获取在线微信账号数量
Route::get('device-count', 'app\\devices\\controller\\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
Route::get('', 'app\\devices\\controller\\DeviceWechat@index'); // 获取在线微信账号列表

View File

@@ -274,23 +274,6 @@ class DeviceWechat extends Controller
]
];
// 获取微信好友列表
$friends = Db::table('tk_wechat_friend')
->where('wechatAccountId', $id)
->where('isDeleted', 0)
->field([
'id',
'wechatId',
'nickname',
'avatar',
'gender',
'region',
'signature',
'labels',
'createTime'
])
->select();
// 处理返回数据
$data = [
'basicInfo' => [
@@ -322,7 +305,6 @@ class DeviceWechat extends Controller
'lastUpdateTime' => $wechat['updateTime']
],
'restrictions' => $restrictions,
'friends' => $friends
];
return json([
@@ -528,4 +510,73 @@ class DeviceWechat extends Controller
]);
}
}
/**
* 获取微信好友列表
* 根据wechatId查询微信好友支持分页和关键词筛选
*
* @return \think\response\Json
*/
public function getFriends()
{
try {
// 获取请求参数
$wechatId = Request::param('wechatId');
$page = (int)Request::param('page', 1);
$limit = (int)Request::param('limit', 20);
$keyword = Request::param('keyword', '');
// 参数验证
if (empty($wechatId)) {
return json([
'code' => 400,
'msg' => '参数错误微信ID不能为空'
]);
}
// 查询参数
$params = [];
if (!empty($keyword)) {
$params['keyword'] = $keyword;
}
// 调用模型方法获取好友列表
$result = \app\devices\model\WechatFriend::getFriendsByWechatId($wechatId, $params, $page, $limit);
// 处理返回的数据
$friendsList = [];
foreach ($result['list'] as $friend) {
$friendsList[] = [
'wechatId' => $friend['wechatId'],
'avatar' => $friend['avatar'] ?: '/placeholder.svg',
'labels' => $friend['labels'] ?: [],
'accountNickname' => $friend['accountNickname'] ?: '',
'accountRealName' => $friend['accountRealName'] ?: '',
'nickname' => $friend['nickname'] ?: '',
'remark' => $friend['conRemark'] ?: '',
'alias' => $friend['alias'] ?: '',
'gender' => $friend['gender'] ?: 0,
'region' => $friend['region'] ?: ''
];
}
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'total' => $result['total'],
'page' => $result['page'],
'limit' => $result['limit'],
'list' => $friendsList
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage()
]);
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace app\devices\model;
use think\Model;
use think\Db;
/**
* 微信好友模型类
*/
class WechatFriend extends Model
{
// 设置表名
protected $name = 'wechat_friend';
protected $prefix = 'tk_';
// 设置主键
protected $pk = 'id';
// 自动写入时间戳
protected $autoWriteTimestamp = 'datetime';
// 定义时间戳字段名
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
// 定义字段类型
protected $type = [
'id' => 'integer',
'wechatAccountId' => 'integer',
'gender' => 'integer',
'addFrom' => 'integer',
'isDeleted' => 'integer',
'isPassed' => 'integer',
'accountId' => 'integer',
'groupId' => 'integer',
'labels' => 'json',
'deleteTime' => 'datetime',
'passTime' => 'datetime',
'createTime' => 'datetime'
];
/**
* 根据微信账号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
];
}
}