私域抄盘手 - 修改微信号相关路由规则 及 统一微信号信息查询规则

This commit is contained in:
柳清爽
2025-05-14 14:17:36 +08:00
parent 2d60f23806
commit 798b0d7854
10 changed files with 152 additions and 182 deletions

View File

@@ -159,9 +159,9 @@ export const fetchWechatFriends = async (wechatId: string, page: number = 1, pag
* @param id 微信账号ID
* @returns 微信账号概览信息
*/
export const fetchWechatAccountSummary = async (id: string): Promise<WechatAccountSummaryResponse> => {
export const fetchWechatAccountSummary = async (wechatIdid: string): Promise<WechatAccountSummaryResponse> => {
try {
return api.get<WechatAccountSummaryResponse>(`/v1/wechats/${id}/summary`);
return api.get<WechatAccountSummaryResponse>(`/v1/wechats/${wechatIdid}/summary`);
} catch (error) {
console.error("获取账号概览失败:", error);
throw error;
@@ -193,9 +193,9 @@ interface WechatFriendDetailResponse {
data: WechatFriendDetail;
}
export const fetchWechatFriendDetail = async (wechatId: string, friendId: string): Promise<WechatFriendDetailResponse> => {
export const fetchWechatFriendDetail = async (wechatId: string): Promise<WechatFriendDetailResponse> => {
try {
return api.get<WechatFriendDetailResponse>(`/v1/wechats/${wechatId}/friend/${friendId}`);
return api.get<WechatFriendDetailResponse>(`/v1/wechats/${wechatId}`);
} catch (error) {
console.error("获取好友详情失败:", error);
throw error;

View File

@@ -413,136 +413,136 @@ export default function TrafficPoolPage() {
</header>
<div className="flex-1 overflow-y-auto">
<div className="p-4 space-y-6">
{/* 搜索和筛选区域 */}
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索用户"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value)
setCurrentPage(1)
}}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
<div className="p-4 space-y-6">
{/* 搜索和筛选区域 */}
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索用户"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value)
setCurrentPage(1)
}}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-2 gap-4">
<Card className="p-4">
<div className="text-sm text-gray-500"></div>
{/* 统计卡片 */}
<div className="grid grid-cols-2 gap-4">
<Card className="p-4">
<div className="text-sm text-gray-500"></div>
<div className="text-2xl font-bold text-blue-600">{stats.totalCount}</div>
</Card>
<Card className="p-4">
<div className="text-sm text-gray-500"></div>
</Card>
<Card className="p-4">
<div className="text-sm text-gray-500"></div>
<div className="text-2xl font-bold text-green-600">{stats.todayAddCount}</div>
</Card>
</div>
</Card>
</div>
{/* 分类标签页 */}
<Tabs
defaultValue="potential"
value={activeCategory}
<Tabs
defaultValue="potential"
value={activeCategory}
onValueChange={(value) => {
setActiveCategory(value)
setCurrentPage(1)
}}
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="potential"></TabsTrigger>
<TabsTrigger value="customer"></TabsTrigger>
</TabsList>
</Tabs>
{/* 筛选器 */}
<div className="flex space-x-2">
<Select
value={sourceFilter}
onValueChange={(value) => {
setActiveCategory(value)
setSourceFilter(value)
setCurrentPage(1)
}}
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="potential"></TabsTrigger>
<TabsTrigger value="customer"></TabsTrigger>
</TabsList>
</Tabs>
{/* 筛选器 */}
<div className="flex space-x-2">
<Select
value={sourceFilter}
onValueChange={(value) => {
setSourceFilter(value)
setCurrentPage(1)
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="来源" />
</SelectTrigger>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="来源" />
</SelectTrigger>
<SelectContent className="max-h-[480px]">
<SelectItem value="all"></SelectItem>
<SelectItem value="all"></SelectItem>
{sourceTypes.map((source) => (
<SelectItem key={source.id} value={source.id.toString()}>
{source.name}
</SelectItem>
))}
</SelectContent>
</Select>
</SelectContent>
</Select>
{activeCategory === "potential" && (
<Select
value={statusFilter}
onValueChange={(value) => {
setStatusFilter(value)
setCurrentPage(1)
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<Select
value={statusFilter}
onValueChange={(value) => {
setStatusFilter(value)
setCurrentPage(1)
}}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{statusTypes.map((status) => (
<SelectItem key={status.id} value={status.id.toString()}>
{status.name}
</SelectItem>
))}
</SelectContent>
</Select>
</SelectContent>
</Select>
)}
</div>
</div>
{/* 用户列表 */}
<div className="space-y-2">
<div className="space-y-2">
{loading && users.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12">
<RefreshCw className="h-8 w-8 text-blue-500 animate-spin mb-4" />
<div className="text-gray-500">...</div>
</div>
) : users.length === 0 ? (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<div className="text-gray-500"></div>
<div className="flex flex-col items-center justify-center py-12">
<RefreshCw className="h-8 w-8 text-blue-500 animate-spin mb-4" />
<div className="text-gray-500">...</div>
</div>
) : users.length === 0 ? (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<div className="text-gray-500"></div>
<Button variant="outline" className="mt-4" onClick={handleRefresh}>
</Button>
</div>
) : (
</Button>
</div>
) : (
<>
{users.map((user) => (
<Card
key={user.id}
className="p-3 cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleUserClick(user)}
>
<div className="flex items-center space-x-3">
<img src={user.avatar || "/placeholder.svg"} alt="" className="w-10 h-10 rounded-full bg-gray-100" />
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate">{user.nickname}</div>
<div
className={`text-xs px-2 py-1 rounded-full ${
<Card
key={user.id}
className="p-3 cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleUserClick(user)}
>
<div className="flex items-center space-x-3">
<img src={user.avatar || "/placeholder.svg"} alt="" className="w-10 h-10 rounded-full bg-gray-100" />
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium truncate">{user.nickname}</div>
<div
className={`text-xs px-2 py-1 rounded-full ${
activeCategory === "customer"
? "bg-green-100 text-green-800"
: user.status === 2
? "bg-green-100 text-green-800"
? "bg-green-100 text-green-800"
: user.status === 1
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}`}
>
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}`}
>
{activeCategory === "customer"
? "已通过"
: user.status === 2
@@ -550,28 +550,28 @@ export default function TrafficPoolPage() {
: user.status === 1
? "待处理"
: "已失败"}
</div>
</div>
<div className="text-sm text-gray-500">: {user.wechatId}</div>
<div className="text-sm text-gray-500">: {user.source}</div>
<div className="text-sm text-gray-500">: {user.addTime}</div>
{/* 标签展示 */}
<div className="flex flex-wrap gap-1 mt-2">
{user.tags.slice(0, 2).map((tag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{user.tags.length > 2 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{user.tags.length - 2}
</span>
)}
</div>
</div>
</div>
</Card>
<div className="text-sm text-gray-500">: {user.wechatId}</div>
<div className="text-sm text-gray-500">: {user.source}</div>
<div className="text-sm text-gray-500">: {user.addTime}</div>
{/* 标签展示 */}
<div className="flex flex-wrap gap-1 mt-2">
{user.tags.slice(0, 2).map((tag) => (
<span key={tag.id} className={`text-xs px-2 py-0.5 rounded-full ${tag.color}`}>
{tag.name}
</span>
))}
{user.tags.length > 2 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800">
+{user.tags.length - 2}
</span>
)}
</div>
</div>
</div>
</Card>
))}
{/* 加载状态显示 */}
@@ -618,17 +618,17 @@ export default function TrafficPoolPage() {
: selectedUser.status === 2
? "bg-green-100 text-green-800"
: selectedUser.status === 1
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}`}
>
{activeCategory === "customer"
? "已通过"
: selectedUser.status === 2
? "已添加"
? "已添加"
: selectedUser.status === 1
? "待处理"
: "已失败"}
? "待处理"
: "已失败"}
</Badge>
</div>
</div>

View File

@@ -103,6 +103,7 @@ interface WechatAccountDetail {
avatar: string
nickname: string
wechatId: string
wechatAccount: string
deviceId: string
deviceName: string
friendCount: number
@@ -199,6 +200,7 @@ export default function WechatAccountDetailPage() {
nickname: string;
status: "normal" | "abnormal";
wechatId: string;
wechatAccount: string;
deviceName: string;
deviceId?: string | number;
} | null>(null)
@@ -219,6 +221,7 @@ export default function WechatAccountDetailPage() {
mockData.status = decodedData.status;
mockData.wechatId = decodedData.wechatId;
mockData.deviceName = decodedData.deviceName;
mockData.wechatAccount = decodedData.wechatAccount;
}
setAccount(mockData);
setFriendsTotal(mockData.friendCount);
@@ -328,6 +331,7 @@ export default function WechatAccountDetailPage() {
"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)}`,
wechatAccount: initialData?.wechatAccount || "wxid_default",
deviceId: "device-1",
deviceName: "设备1",
friendCount: friends.length,
@@ -398,7 +402,7 @@ export default function WechatAccountDetailPage() {
setIsFetchingFriends(true);
setHasFriendLoadError(false);
const data = await api.get<ApiResponse<FriendsResponse>>(`/v1/wechats/${id}/friends?page=${page}&limit=30`, true);
const data = await api.get<ApiResponse<FriendsResponse>>(`/v1/wechats/${account?.wechatId}/friends?page=${page}&limit=30`, true);
if (data && data.code === 200) {
// 更新总数计数
@@ -526,7 +530,7 @@ export default function WechatAccountDetailPage() {
const fetchSummaryData = useCallback(async () => {
try {
setIsLoading(true);
const response = await fetchWechatAccountSummary(id);
const response = await fetchWechatAccountSummary(account?.wechatId || '');
if (response.code === 200) {
setAccountSummary(response.data);
} else {
@@ -546,7 +550,7 @@ export default function WechatAccountDetailPage() {
} finally {
setIsLoading(false);
}
}, [id]);
}, [account]);
// 在页面加载和切换到概览标签时获取数据
useEffect(() => {
@@ -623,7 +627,7 @@ export default function WechatAccountDetailPage() {
setFriendDetailError(null)
try {
const response = await fetchWechatFriendDetail(account?.wechatId || id, friend.id)
const response = await fetchWechatFriendDetail(friend.wechatId)
if (response.code === 200) {
setFriendDetail(response.data)
} else {
@@ -699,7 +703,7 @@ export default function WechatAccountDetailPage() {
{account.status === "normal" ? "正常" : "异常"}
</Badge>
</div>
<p className="text-sm text-gray-500 mt-1">{account.wechatId}</p>
<p className="text-sm text-gray-500 mt-1">{account.wechatAccount}</p>
<div className="flex gap-2 mt-2">
<Button
variant="outline"

View File

@@ -68,6 +68,7 @@ export default function WechatAccountsPage() {
const account: WechatAccount = {
id: item.id.toString(),
wechatId: item.wechatId,
wechatAccount: item.wechatAccount,
nickname: item.nickname,
avatar: item.avatar,
remainingAdds: item.times - item.addedCount,
@@ -264,6 +265,7 @@ export default function WechatAccountsPage() {
nickname: account.nickname,
status: account.status,
wechatId: account.wechatId,
wechatAccount: account.wechatAccount,
deviceName: account.deviceName,
deviceId: account.deviceId,
}));
@@ -299,7 +301,7 @@ export default function WechatAccountsPage() {
</Button>
</div>
<div className="mt-1 text-sm text-gray-500 space-y-1">
<div className="truncate">{account.wechatId}</div>
<div className="truncate">{account.wechatAccount}</div>
<div className="flex items-center justify-between flex-wrap gap-1">
<div>{account.friendCount}</div>
<div className="text-green-600">+{account.todayAdded}</div>

View File

@@ -2,6 +2,7 @@
export interface ServerWechatAccount {
id: number;
wechatId: string;
wechatAccount: string;
nickname: string;
accountNickname: string;
avatar: string;

View File

@@ -25,7 +25,7 @@ Route::group('v1/', function () {
Route::get('', 'app\cunkebao\controller\wechat\GetWechatsOnDevicesV1Controller@index');
Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index');
Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index');
Route::get(':id/friend/:aId', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendProfileV1Controller@index');
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count');
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量

View File

@@ -41,8 +41,8 @@ class GetWechatOnDeviceFriendsV1Controller extends BaseController
$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',
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
'f.memo', 'f.tags'
]
)
@@ -60,25 +60,6 @@ class GetWechatOnDeviceFriendsV1Controller extends BaseController
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;
}
/**
* 构建查询条件
*
@@ -92,7 +73,7 @@ class GetWechatOnDeviceFriendsV1Controller extends BaseController
$where[] = ['exp', "f.memo LIKE '%{$keyword}%' OR f.tags LIKE '%{$keyword}%'"];
}
$where['f.ownerWechatId'] = $this->getStringWechatIdByNumberId();
$where['f.ownerWechatId'] = $this->request->param('id/s') ?: 'x_x';
return array_merge($where, $params);
}

View File

@@ -233,25 +233,6 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
];
}
/**
* 获取原始的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;
}
/**
* 获取微信号详情
*
@@ -260,7 +241,7 @@ class GetWechatOnDeviceSummarizeV1Controller extends BaseController
public function index()
{
try {
$wechatId = $this->getStringWechatIdByNumberId();
$wechatId = $this->request->param('id/s');
// 以下内容依次加工数据
$accountAge = $this->getRegisterDate($wechatId);

View File

@@ -10,7 +10,7 @@ use library\ResponseHelper;
/**
* 设备微信控制器
*/
class GetWechatOnDeviceFriendProfileV1Controller extends BaseController
class GetWechatProfileV1Controller extends BaseController
{
/**
* 获取最近互动时间
@@ -63,11 +63,11 @@ class GetWechatOnDeviceFriendProfileV1Controller extends BaseController
/**
* 获取微信账号
*
* @param int $id
* @param string $wechatId
* @return array
* @throws \Exception
*/
protected function getWechatAccountProfileById(int $id): array
protected function getWechatAccountProfileByWechatId(string $wechatId): array
{
$account = WechatAccountModel::alias('w')
->field(
@@ -78,7 +78,8 @@ class GetWechatOnDeviceFriendProfileV1Controller extends BaseController
]
)
->join('wechat_friendship f', 'w.wechatId=f.wechatId')
->find($id);
->where('w.wechatId', $wechatId)
->find();
if (is_null($account)) {
throw new \Exception('未获取到微信账号数据', 404);
@@ -95,8 +96,8 @@ class GetWechatOnDeviceFriendProfileV1Controller extends BaseController
public function index()
{
try {
$results = $this->getWechatAccountProfileById(
$this->request->param('aId/d')
$results = $this->getWechatAccountProfileByWechatId(
$this->request->param('wechatId/s')
);
return ResponseHelper::success(

View File

@@ -198,8 +198,8 @@ class GetWechatsOnDevicesV1Controller extends BaseController
$query = WechatAccountModel::alias('w')
->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',
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
'l.deviceId'
]
)