健康分功能提交 + 微信客服页面改版
This commit is contained in:
@@ -5,6 +5,20 @@ export function getWechatAccountDetail(id: string) {
|
||||
return request("/v1/wechats/getWechatInfo", { wechatId: id }, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号概览数据
|
||||
export function getWechatAccountOverview(id: string) {
|
||||
return request("/v1/wechats/overview", { wechatId: id }, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号朋友圈列表
|
||||
export function getWechatMoments(params: {
|
||||
wechatId: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
return request("/v1/wechats/moments", params, "GET");
|
||||
}
|
||||
|
||||
// 获取微信号好友列表
|
||||
export function getWechatFriends(params: {
|
||||
wechatAccount: string;
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
// 概览数据接口
|
||||
export interface WechatAccountOverview {
|
||||
healthScoreAssessment: {
|
||||
score: number;
|
||||
dailyLimit: number;
|
||||
todayAdded: number;
|
||||
lastAddTime: string;
|
||||
statusTag: string;
|
||||
baseComposition?: Array<{
|
||||
name: string;
|
||||
score: number;
|
||||
formatted: string;
|
||||
friendCount?: number;
|
||||
}>;
|
||||
dynamicRecords?: Array<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
time?: string;
|
||||
score?: number;
|
||||
formatted?: string;
|
||||
statusTag?: string;
|
||||
}>;
|
||||
};
|
||||
accountValue: {
|
||||
value: number;
|
||||
formatted: string;
|
||||
};
|
||||
todayValueChange: {
|
||||
change: number;
|
||||
formatted: string;
|
||||
isPositive: boolean;
|
||||
};
|
||||
totalFriends: number;
|
||||
todayNewFriends: number;
|
||||
highValueChatrooms: number;
|
||||
todayNewChatrooms: number;
|
||||
}
|
||||
|
||||
export interface WechatAccountSummary {
|
||||
accountAge: string;
|
||||
activityLevel: {
|
||||
@@ -15,12 +53,51 @@ export interface WechatAccountSummary {
|
||||
todayAdded: number;
|
||||
addLimit: number;
|
||||
};
|
||||
healthScore?: {
|
||||
score: number;
|
||||
lastUpdate?: string;
|
||||
lastAddTime?: string;
|
||||
baseScore?: number;
|
||||
verifiedScore?: number;
|
||||
friendsScore?: number;
|
||||
activities?: {
|
||||
type: string;
|
||||
time?: string;
|
||||
score: number;
|
||||
description?: string;
|
||||
status?: string;
|
||||
}[];
|
||||
};
|
||||
moments?: {
|
||||
id: string;
|
||||
date: string;
|
||||
month: string;
|
||||
day: string;
|
||||
content: string;
|
||||
images?: string[];
|
||||
timeAgo?: string;
|
||||
hasEmoji?: boolean;
|
||||
}[];
|
||||
accountValue?: {
|
||||
value: number;
|
||||
todayChange?: number;
|
||||
};
|
||||
friendsCount?: {
|
||||
total: number;
|
||||
todayAdded?: number;
|
||||
};
|
||||
groupsCount?: {
|
||||
total: number;
|
||||
todayAdded?: number;
|
||||
};
|
||||
restrictions: {
|
||||
id: number;
|
||||
level: number;
|
||||
reason: string;
|
||||
date: string;
|
||||
}[];
|
||||
// 新增概览数据
|
||||
overview?: WechatAccountOverview;
|
||||
}
|
||||
|
||||
export interface Friend {
|
||||
@@ -39,6 +116,27 @@ export interface Friend {
|
||||
region: string;
|
||||
source: string;
|
||||
notes: string;
|
||||
value?: number;
|
||||
valueFormatted?: string;
|
||||
statusTags?: string[];
|
||||
}
|
||||
|
||||
export interface MomentItem {
|
||||
id: string;
|
||||
snsId: string;
|
||||
type: number;
|
||||
content: string;
|
||||
resUrls: string[];
|
||||
commentList?: any[];
|
||||
likeList?: any[];
|
||||
createTime: string;
|
||||
momentEntity?: {
|
||||
lat?: string;
|
||||
lng?: string;
|
||||
location?: string;
|
||||
picSize?: number;
|
||||
userName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WechatFriendDetail {
|
||||
|
||||
@@ -143,67 +143,235 @@
|
||||
}
|
||||
|
||||
.overview-content {
|
||||
.info-grid {
|
||||
// 健康分评估区域
|
||||
.health-score-section {
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.health-score-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.health-score-info {
|
||||
.health-score-status {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.status-tag {
|
||||
background: #ffebeb;
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.health-score-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.score-circle-wrapper {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
|
||||
.score-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 8px solid #ff4d4f;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.score-number {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #ff4d4f;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.health-score-stats {
|
||||
flex: 1;
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.stats-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 账号统计卡片网格
|
||||
.account-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.info-card {
|
||||
background: linear-gradient(135deg, #e6f7ff, #f0f8ff);
|
||||
.stat-card {
|
||||
background: #ffffff;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #bae7ff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.info-header {
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.info-icon {
|
||||
font-size: 16px;
|
||||
color: #1677ff;
|
||||
padding: 6px;
|
||||
background: #e6f7ff;
|
||||
border-radius: 8px;
|
||||
.stat-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
flex: 1;
|
||||
.stat-icon-up {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
.title-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
margin-bottom: 2px;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top: 2px solid #722ed1;
|
||||
border-right: 2px solid #722ed1;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon-plus {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.title-sub {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
background: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon-people {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 7px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
left: 5px;
|
||||
width: 10px;
|
||||
height: 5px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
background: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon-chat {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
background: #fa8c16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-value {
|
||||
text-align: right;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1677ff;
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.value-unit {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.stat-value-positive {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,6 +617,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
.friends-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #f5f9ff;
|
||||
border: 1px solid #e0edff;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.summary-value-highlight {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #fa541c;
|
||||
}
|
||||
|
||||
.summary-divider {
|
||||
width: 1px;
|
||||
height: 32px;
|
||||
background: #e6e6e6;
|
||||
margin: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.friends-list {
|
||||
.empty {
|
||||
text-align: center;
|
||||
@@ -467,83 +676,100 @@
|
||||
}
|
||||
}
|
||||
|
||||
.friend-item {
|
||||
.friend-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
padding: 14px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 10px;
|
||||
gap: 12px;
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
|
||||
.friend-item-static {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
&:hover {
|
||||
border-color: #cfe2ff;
|
||||
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
.adm-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.friend-info {
|
||||
.friend-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.friend-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.friend-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.friend-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.friend-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.friend-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.friend-id-row {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.friend-status-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.friend-status-chip {
|
||||
background: #f0f7ff;
|
||||
color: #1677ff;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.friend-value {
|
||||
text-align: right;
|
||||
|
||||
.value-label {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.friend-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.friend-remark {
|
||||
color: #666;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.friend-arrow {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.friend-wechat-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.friend-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
|
||||
.friend-tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.value-amount {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #fa541c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,6 +995,416 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.health-content {
|
||||
padding: 16px 0;
|
||||
height: 500px;
|
||||
overflow-y: auto;
|
||||
|
||||
.health-score-card {
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.health-score-status {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.status-tag {
|
||||
background: #ffebeb;
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.health-score-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.score-circle-wrapper {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
|
||||
.score-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 8px solid #ff4d4f;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.score-number {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #ff4d4f;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.health-score-stats {
|
||||
flex: 1;
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.stats-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.health-section {
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.health-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #ff8800;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: #ff8800;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.health-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.health-item-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.health-item-icon-warning {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #ffebeb;
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '!';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.health-item-tag {
|
||||
background: #fff7e6;
|
||||
color: #fa8c16;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.health-item-value-positive {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.health-item-value-negative {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.health-item-value-empty {
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.health-empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.moments-content {
|
||||
padding: 16px 0;
|
||||
height: 500px;
|
||||
overflow-y: auto;
|
||||
background: #f5f5f5;
|
||||
|
||||
.moments-action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px 16px;
|
||||
|
||||
.action-button, .action-button-dark {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: #1677ff;
|
||||
|
||||
.action-icon-text, .action-icon-image, .action-icon-video, .action-icon-export {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon-image::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.action-icon-video::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 0 5px 8px;
|
||||
border-color: transparent transparent transparent white;
|
||||
}
|
||||
|
||||
.action-text, .action-text-light {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button-dark {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.moments-list {
|
||||
padding: 0 16px;
|
||||
|
||||
.moment-item {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.moment-date {
|
||||
margin-right: 12px;
|
||||
text-align: center;
|
||||
|
||||
.date-day {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.date-month {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.moment-content {
|
||||
flex: 1;
|
||||
|
||||
.moment-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
white-space: pre-wrap; // 保留换行和空格,确保文本完整显示
|
||||
word-wrap: break-word; // 长单词自动换行
|
||||
|
||||
.moment-emoji {
|
||||
display: inline;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.moment-images {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
// 1张图片:宽度拉伸,高度自适应
|
||||
&.single {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 2张图片:左右并列
|
||||
&.double {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 3张图片:三张并列
|
||||
&.triple {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 4张图片:2x2网格布局
|
||||
&.quad {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 5张及以上:网格布局(9宫格)
|
||||
&.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.image-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.moment-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.moment-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.risk-content {
|
||||
padding: 16px 0;
|
||||
height: 500px;
|
||||
|
||||
@@ -21,11 +21,17 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./detail.module.scss";
|
||||
import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api";
|
||||
import {
|
||||
getWechatAccountDetail,
|
||||
getWechatFriends,
|
||||
transferWechatFriends,
|
||||
getWechatAccountOverview,
|
||||
getWechatMoments,
|
||||
} from "./api";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
import { WechatAccountSummary, Friend } from "./data";
|
||||
import { WechatAccountSummary, Friend, MomentItem } from "./data";
|
||||
|
||||
const WechatAccountDetail: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -34,6 +40,7 @@ const WechatAccountDetail: React.FC = () => {
|
||||
const [accountSummary, setAccountSummary] =
|
||||
useState<WechatAccountSummary | null>(null);
|
||||
const [accountInfo, setAccountInfo] = useState<any>(null);
|
||||
const [overviewData, setOverviewData] = useState<any>(null);
|
||||
const [showRestrictions, setShowRestrictions] = useState(false);
|
||||
const [showTransferConfirm, setShowTransferConfirm] = useState(false);
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>([]);
|
||||
@@ -50,6 +57,12 @@ const WechatAccountDetail: React.FC = () => {
|
||||
const [isFetchingFriends, setIsFetchingFriends] = useState(false);
|
||||
const [hasFriendLoadError, setHasFriendLoadError] = useState(false);
|
||||
const [isFriendsEmpty, setIsFriendsEmpty] = useState(false);
|
||||
const [moments, setMoments] = useState<MomentItem[]>([]);
|
||||
const [momentsPage, setMomentsPage] = useState(1);
|
||||
const [momentsTotal, setMomentsTotal] = useState(0);
|
||||
const [isFetchingMoments, setIsFetchingMoments] = useState(false);
|
||||
const [momentsError, setMomentsError] = useState<string | null>(null);
|
||||
const MOMENTS_LIMIT = 10;
|
||||
|
||||
// 获取基础信息
|
||||
const fetchAccountInfo = useCallback(async () => {
|
||||
@@ -80,6 +93,19 @@ const WechatAccountDetail: React.FC = () => {
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
// 获取概览数据
|
||||
const fetchOverviewData = useCallback(async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const response = await getWechatAccountOverview(id);
|
||||
if (response) {
|
||||
setOverviewData(response);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取概览数据失败:", e);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
// 获取好友列表 - 封装为独立函数
|
||||
const fetchFriendsList = useCallback(
|
||||
async (page: number = 1, keyword: string = "") => {
|
||||
@@ -96,26 +122,44 @@ const WechatAccountDetail: React.FC = () => {
|
||||
keyword: keyword,
|
||||
});
|
||||
|
||||
const newFriends = response.list.map((friend: any) => ({
|
||||
id: friend.id.toString(),
|
||||
avatar: friend.avatar || "/placeholder.svg",
|
||||
nickname: friend.nickname || "未知用户",
|
||||
wechatId: friend.wechatId || "",
|
||||
remark: friend.memo || "",
|
||||
addTime: friend.createTime || new Date().toISOString().split("T")[0],
|
||||
lastInteraction:
|
||||
friend.lastInteraction || new Date().toISOString().split("T")[0],
|
||||
tags: friend.tags
|
||||
? friend.tags.map((tag: string, index: number) => ({
|
||||
id: `tag-${index}`,
|
||||
name: tag,
|
||||
color: getRandomTagColor(),
|
||||
}))
|
||||
: [],
|
||||
region: friend.region || "未知",
|
||||
source: friend.source || "未知",
|
||||
notes: friend.notes || "",
|
||||
}));
|
||||
const newFriends = response.list.map((friend: any) => {
|
||||
const memoTags = Array.isArray(friend.memo)
|
||||
? friend.memo
|
||||
: friend.memo
|
||||
? String(friend.memo)
|
||||
.split(/[,\s,、]+/)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const tagList = Array.isArray(friend.tags)
|
||||
? friend.tags
|
||||
: friend.tags
|
||||
? [friend.tags]
|
||||
: [];
|
||||
|
||||
return {
|
||||
id: friend.id.toString(),
|
||||
avatar: friend.avatar || "/placeholder.svg",
|
||||
nickname: friend.nickname || "未知用户",
|
||||
wechatId: friend.wechatId || "",
|
||||
remark: friend.notes || "",
|
||||
addTime:
|
||||
friend.createTime || new Date().toISOString().split("T")[0],
|
||||
lastInteraction:
|
||||
friend.lastInteraction || new Date().toISOString().split("T")[0],
|
||||
tags: memoTags.map((tag: string, index: number) => ({
|
||||
id: `tag-${index}`,
|
||||
name: tag,
|
||||
color: getRandomTagColor(),
|
||||
})),
|
||||
statusTags: tagList,
|
||||
region: friend.region || "未知",
|
||||
source: friend.source || "未知",
|
||||
notes: friend.notes || "",
|
||||
value: friend.value,
|
||||
valueFormatted: friend.valueFormatted,
|
||||
};
|
||||
});
|
||||
|
||||
setFriends(newFriends);
|
||||
setFriendsTotal(response.total);
|
||||
@@ -137,6 +181,46 @@ const WechatAccountDetail: React.FC = () => {
|
||||
[id],
|
||||
);
|
||||
|
||||
const fetchMomentsList = useCallback(
|
||||
async (page: number = 1, append: boolean = false) => {
|
||||
if (!id) return;
|
||||
setIsFetchingMoments(true);
|
||||
setMomentsError(null);
|
||||
try {
|
||||
const response = await getWechatMoments({
|
||||
wechatId: id,
|
||||
page,
|
||||
limit: MOMENTS_LIMIT,
|
||||
});
|
||||
|
||||
const list: MomentItem[] = (response.list || []).map((moment: any) => ({
|
||||
id: moment.id?.toString() || Math.random().toString(),
|
||||
snsId: moment.snsId,
|
||||
type: moment.type,
|
||||
content: moment.content || "",
|
||||
resUrls: moment.resUrls || [],
|
||||
commentList: moment.commentList || [],
|
||||
likeList: moment.likeList || [],
|
||||
createTime: moment.createTime || "",
|
||||
momentEntity: moment.momentEntity || {},
|
||||
}));
|
||||
|
||||
setMoments(prev => (append ? [...prev, ...list] : list));
|
||||
setMomentsTotal(response.total || list.length);
|
||||
setMomentsPage(page);
|
||||
} catch (error) {
|
||||
console.error("获取朋友圈数据失败:", error);
|
||||
setMomentsError("获取朋友圈数据失败");
|
||||
if (!append) {
|
||||
setMoments([]);
|
||||
}
|
||||
} finally {
|
||||
setIsFetchingMoments(false);
|
||||
}
|
||||
},
|
||||
[id],
|
||||
);
|
||||
|
||||
// 搜索好友
|
||||
const handleSearch = useCallback(() => {
|
||||
setFriendsPage(1);
|
||||
@@ -161,8 +245,9 @@ const WechatAccountDetail: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchAccountInfo();
|
||||
fetchOverviewData();
|
||||
}
|
||||
}, [id, fetchAccountInfo]);
|
||||
}, [id, fetchAccountInfo, fetchOverviewData]);
|
||||
|
||||
// 监听标签切换 - 只在切换到好友列表时请求一次
|
||||
useEffect(() => {
|
||||
@@ -173,6 +258,14 @@ const WechatAccountDetail: React.FC = () => {
|
||||
}
|
||||
}, [activeTab, id, fetchFriendsList, searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === "moments" && id) {
|
||||
if (moments.length === 0) {
|
||||
fetchMomentsList(1, false);
|
||||
}
|
||||
}
|
||||
}, [activeTab, id, fetchMomentsList, moments.length]);
|
||||
|
||||
// 工具函数
|
||||
const getRandomTagColor = (): string => {
|
||||
const colors = [
|
||||
@@ -271,6 +364,41 @@ const WechatAccountDetail: React.FC = () => {
|
||||
navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`);
|
||||
};
|
||||
|
||||
const handleLoadMoreMoments = () => {
|
||||
if (isFetchingMoments) return;
|
||||
if (moments.length >= momentsTotal) return;
|
||||
fetchMomentsList(momentsPage + 1, true);
|
||||
};
|
||||
|
||||
const formatMomentDateParts = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return { day: "--", month: "--" };
|
||||
}
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = `${date.getMonth() + 1}月`;
|
||||
return { day, month };
|
||||
};
|
||||
|
||||
const formatMomentTimeAgo = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return dateString || "--";
|
||||
}
|
||||
const diff = Date.now() - date.getTime();
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
if (minutes < 1) return "刚刚";
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 7) return `${days}天前`;
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout header={<NavCommon title="微信号详情" />} loading={loadingInfo}>
|
||||
<div className={style["wechat-account-detail-page"]}>
|
||||
@@ -313,73 +441,220 @@ const WechatAccountDetail: React.FC = () => {
|
||||
onChange={handleTabChange}
|
||||
className={style["tabs"]}
|
||||
>
|
||||
<Tabs.Tab title="账号概览" key="overview">
|
||||
<Tabs.Tab title="概览" key="overview">
|
||||
<div className={style["overview-content"]}>
|
||||
<div className={style["summary-grid"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value"]}>
|
||||
{accountInfo?.friendShip?.totalFriend ?? "-"}
|
||||
{/* 健康分评估区域 */}
|
||||
<div className={style["health-score-section"]}>
|
||||
<div className={style["health-score-title"]}>健康分评估</div>
|
||||
<div className={style["health-score-info"]}>
|
||||
<div className={style["health-score-status"]}>
|
||||
<span className={style["status-tag"]}>{overviewData?.healthScoreAssessment?.statusTag || "已添加加人"}</span>
|
||||
<span className={style["status-time"]}>最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"}</span>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>好友数量</div>
|
||||
</div>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-green"]}>
|
||||
+{accountSummary?.statistics.todayAdded ?? "-"}
|
||||
<div className={style["health-score-display"]}>
|
||||
<div className={style["score-circle-wrapper"]}>
|
||||
<div className={style["score-circle"]}>
|
||||
<div className={style["score-number"]}>
|
||||
{overviewData?.healthScoreAssessment?.score || 67}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["health-score-stats"]}>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>每日限额</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.dailyLimit || 0} 人</div>
|
||||
</div>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>今日已加</div>
|
||||
<div className={style["stats-value"]}>{overviewData?.healthScoreAssessment?.todayAdded || 0} 人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>今日新增</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-progress-row"]}>
|
||||
<span>今日可添加:</span>
|
||||
<span className={style["summary-progress-text"]}>
|
||||
{accountSummary?.statistics.todayAdded ?? 0}/
|
||||
{accountSummary?.statistics.addLimit ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style["summary-progress-bar"]}>
|
||||
<div className={style["progress-bg"]}>
|
||||
<div
|
||||
className={style["progress-fill"]}
|
||||
style={{
|
||||
width: `${Math.min(((accountSummary?.statistics.todayAdded ?? 0) / (accountSummary?.statistics.addLimit || 1)) * 100, 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-grid"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-blue"]}>
|
||||
{accountInfo?.friendShip?.groupNumber ?? "-"}
|
||||
|
||||
{/* 账号价值和好友数量区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 账号价值 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>账号价值</div>
|
||||
<div className={style["stat-icon-up"]}></div>
|
||||
</div>
|
||||
<div className={style["summary-label"]}>群聊数量</div>
|
||||
</div>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-value-green"]}>
|
||||
{accountInfo?.activity?.yesterdayMsgCount ?? "-"}
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.accountValue?.formatted || `¥${overviewData?.accountValue?.value || "29,800"}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 今日价值变化 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日价值变化</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
{overviewData?.todayValueChange?.formatted || `+${overviewData?.todayValueChange?.change || "500"}`}
|
||||
</div>
|
||||
<div className={style["summary-label"]}>今日消息</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-card"]}>
|
||||
<div className={style["device-title"]}>设备信息</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>设备名称:</span>
|
||||
<span>{accountInfo?.deviceName ?? "-"}</span>
|
||||
|
||||
{/* 好友数量和今日新增好友区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 好友总数 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>好友总数</div>
|
||||
<div className={style["stat-icon-people"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.totalFriends || accountInfo?.friendShip?.totalFriend || "0"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>系统类型:</span>
|
||||
<span>{accountInfo?.deviceType ?? "-"}</span>
|
||||
|
||||
{/* 今日新增好友 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日新增好友</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
+{overviewData?.todayNewFriends || accountSummary?.statistics.todayAdded || "0"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-row"]}>
|
||||
<span className={style["device-label"]}>系统版本:</span>
|
||||
<span>{accountInfo?.deviceVersion ?? "-"}</span>
|
||||
</div>
|
||||
|
||||
{/* 高价群聊区域 */}
|
||||
<div className={style["account-stats-grid"]}>
|
||||
{/* 高价群聊 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>高价群聊</div>
|
||||
<div className={style["stat-icon-chat"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value"]}>
|
||||
{overviewData?.highValueChatrooms || accountInfo?.friendShip?.groupNumber || "0"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 今日新增群聊 */}
|
||||
<div className={style["stat-card"]}>
|
||||
<div className={style["stat-header"]}>
|
||||
<div className={style["stat-title"]}>今日新增群聊</div>
|
||||
<div className={style["stat-icon-plus"]}></div>
|
||||
</div>
|
||||
<div className={style["stat-value-positive"]}>
|
||||
+{overviewData?.todayNewChatrooms || "0"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="健康分" key="health">
|
||||
<div className={style["health-content"]}>
|
||||
{/* 健康分数圆环 */}
|
||||
<div className={style["health-score-card"]}>
|
||||
<div className={style["health-score-status"]}>
|
||||
<span className={style["status-tag"]}>已添加加人</span>
|
||||
<span className={style["status-time"]}>最后添加时间: {accountSummary?.healthScore?.lastAddTime || "18:36:06"}</span>
|
||||
</div>
|
||||
<div className={style["health-score-display"]}>
|
||||
<div className={style["score-circle-wrapper"]}>
|
||||
<div className={style["score-circle"]}>
|
||||
<div className={style["score-number"]}>
|
||||
{accountSummary?.healthScore?.score || 67}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["health-score-stats"]}>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>每日限额</div>
|
||||
<div className={style["stats-value"]}>{accountSummary?.statistics.addLimit || 0} 人</div>
|
||||
</div>
|
||||
<div className={style["stats-row"]}>
|
||||
<div className={style["stats-label"]}>今日已加</div>
|
||||
<div className={style["stats-value"]}>{accountSummary?.statistics.todayAdded || 0} 人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 基础构成 */}
|
||||
<div className={style["health-section"]}>
|
||||
<div className={style["health-section-title"]}>基础构成</div>
|
||||
{(overviewData?.healthScoreAssessment?.baseComposition &&
|
||||
overviewData.healthScoreAssessment.baseComposition.length > 0
|
||||
? overviewData.healthScoreAssessment.baseComposition
|
||||
: [
|
||||
{ name: "账号基础分", formatted: "+60" },
|
||||
{ name: "已修改微信号", formatted: "+10" },
|
||||
{ name: "好友数量加成", formatted: "+12", friendCount: 5595 },
|
||||
]
|
||||
).map((item, index) => (
|
||||
<div className={style["health-item"]} key={`${item.name}-${index}`}>
|
||||
<div className={style["health-item-label"]}>
|
||||
{item.name}
|
||||
{item.friendCount ? ` (${item.friendCount})` : ""}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
(item.score ?? 0) >= 0
|
||||
? style["health-item-value-positive"]
|
||||
: style["health-item-value-negative"]
|
||||
}
|
||||
>
|
||||
{item.formatted || `${item.score ?? 0}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 动态记录 */}
|
||||
<div className={style["health-section"]}>
|
||||
<div className={style["health-section-title"]}>动态记录</div>
|
||||
{overviewData?.healthScoreAssessment?.dynamicRecords &&
|
||||
overviewData.healthScoreAssessment.dynamicRecords.length > 0 ? (
|
||||
overviewData.healthScoreAssessment.dynamicRecords.map(
|
||||
(record, index) => (
|
||||
<div className={style["health-item"]} key={`record-${index}`}>
|
||||
<div className={style["health-item-label"]}>
|
||||
<span className={style["health-item-icon-warning"]}></span>
|
||||
{record.title || record.description || "记录"}
|
||||
{record.statusTag && (
|
||||
<span className={style["health-item-tag"]}>
|
||||
{record.statusTag}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
(record.score ?? 0) >= 0
|
||||
? style["health-item-value-positive"]
|
||||
: style["health-item-value-negative"]
|
||||
}
|
||||
>
|
||||
{record.formatted ||
|
||||
(record.score && record.score > 0
|
||||
? `+${record.score}`
|
||||
: record.score || "-")}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<div className={style["health-empty"]}>暂无动态记录</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
|
||||
<Tabs.Tab
|
||||
title={`好友列表${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`}
|
||||
title={`好友${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`}
|
||||
key="friends"
|
||||
>
|
||||
<div className={style["friends-content"]}>
|
||||
@@ -406,6 +681,23 @@ const WechatAccountDetail: React.FC = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 好友概要 */}
|
||||
<div className={style["friends-summary"]}>
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-label"]}>好友总数</div>
|
||||
<div className={style["summary-value"]}>
|
||||
{friendsTotal || overviewData?.totalFriends || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["summary-divider"]} />
|
||||
<div className={style["summary-item"]}>
|
||||
<div className={style["summary-label"]}>好友总估值</div>
|
||||
<div className={style["summary-value-highlight"]}>
|
||||
{overviewData?.accountValue?.formatted || "¥1,500,000"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 好友列表 */}
|
||||
<div className={style["friends-list"]}>
|
||||
{isFetchingFriends && friends.length === 0 ? (
|
||||
@@ -431,36 +723,53 @@ const WechatAccountDetail: React.FC = () => {
|
||||
{friends.map(friend => (
|
||||
<div
|
||||
key={friend.id}
|
||||
className={style["friend-item"]}
|
||||
className={style["friend-card"]}
|
||||
onClick={() => handleFriendClick(friend)}
|
||||
>
|
||||
<Avatar
|
||||
src={friend.avatar}
|
||||
className={style["friend-avatar"]}
|
||||
/>
|
||||
<div className={style["friend-info"]}>
|
||||
<div className={style["friend-header"]}>
|
||||
<div className={style["friend-avatar"]}>
|
||||
<Avatar src={friend.avatar} />
|
||||
</div>
|
||||
<div className={style["friend-main"]}>
|
||||
<div className={style["friend-name-row"]}>
|
||||
<div className={style["friend-name"]}>
|
||||
{friend.nickname}
|
||||
{friend.remark && (
|
||||
<span className={style["friend-remark"]}>
|
||||
({friend.remark})
|
||||
{friend.nickname || "未知好友"}
|
||||
</div>
|
||||
<div className={style["friend-tags"]}>
|
||||
{friend.tags?.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={style["friend-tag"]}
|
||||
>
|
||||
{typeof tag === "string" ? tag : tag.name}
|
||||
</span>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["friend-wechat-id"]}>
|
||||
{friend.wechatId}
|
||||
<div className={style["friend-id-row"]}>
|
||||
ID: {friend.wechatId || "-"}
|
||||
</div>
|
||||
<div className={style["friend-tags"]}>
|
||||
{friend.tags?.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
className={style["friend-tag"]}
|
||||
<div className={style["friend-status-row"]}>
|
||||
{friend.statusTags?.map((tag, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className={style["friend-status-chip"]}
|
||||
>
|
||||
{typeof tag === "string" ? tag : tag.name}
|
||||
</Tag>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{friend.remark && (
|
||||
<span className={style["friend-status-chip"]}>
|
||||
{friend.remark}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["friend-value"]}>
|
||||
<div className={style["value-amount"]}>
|
||||
{friend.valueFormatted
|
||||
|| (typeof friend.value === "number"
|
||||
? `¥${friend.value.toLocaleString()}`
|
||||
: "估值 -")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -484,45 +793,115 @@ const WechatAccountDetail: React.FC = () => {
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="风险评估" key="risk">
|
||||
<div className={style["risk-content"]}>
|
||||
{accountSummary?.restrictions &&
|
||||
accountSummary.restrictions.length > 0 ? (
|
||||
<div className={style["restrictions-list"]}>
|
||||
{accountSummary.restrictions.map(restriction => (
|
||||
<div
|
||||
key={restriction.id}
|
||||
className={style["restriction-item"]}
|
||||
>
|
||||
<div className={style["restriction-info"]}>
|
||||
<div className={style["restriction-reason"]}>
|
||||
{restriction.reason}
|
||||
</div>
|
||||
<div className={style["restriction-date"]}>
|
||||
{restriction.date
|
||||
? formatDateTime(restriction.date)
|
||||
: "暂无时间"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["restriction-level"]}>
|
||||
<span
|
||||
className={`${style["level-badge"]} ${style[`level-${restriction.level}`]}`}
|
||||
>
|
||||
{restriction.level === 1
|
||||
? "低风险"
|
||||
: restriction.level === 2
|
||||
? "中风险"
|
||||
: "高风险"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Tabs.Tab title="朋友圈" key="moments">
|
||||
<div className={style["moments-content"]}>
|
||||
{/* 功能按钮栏 */}
|
||||
<div className={style["moments-action-bar"]}>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-text"]}></span>
|
||||
<span className={style["action-text"]}>文本</span>
|
||||
</div>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-image"]}></span>
|
||||
<span className={style["action-text"]}>图片</span>
|
||||
</div>
|
||||
<div className={style["action-button"]}>
|
||||
<span className={style["action-icon-video"]}></span>
|
||||
<span className={style["action-text"]}>视频</span>
|
||||
</div>
|
||||
<div className={style["action-button-dark"]}>
|
||||
<span className={style["action-icon-export"]}></span>
|
||||
<span className={style["action-text-light"]}>导出</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 朋友圈列表 */}
|
||||
<div className={style["moments-list"]}>
|
||||
{isFetchingMoments && moments.length === 0 ? (
|
||||
<div className={style["loading"]}>
|
||||
<SpinLoading color="primary" style={{ fontSize: 32 }} />
|
||||
</div>
|
||||
) : momentsError ? (
|
||||
<div className={style["error"]}>{momentsError}</div>
|
||||
) : moments.length === 0 ? (
|
||||
<div className={style["empty"]}>暂无朋友圈内容</div>
|
||||
) : (
|
||||
moments.map(moment => {
|
||||
const { day, month } = formatMomentDateParts(
|
||||
moment.createTime,
|
||||
);
|
||||
const timeAgo = formatMomentTimeAgo(moment.createTime);
|
||||
const imageCount = moment.resUrls?.length || 0;
|
||||
// 根据图片数量选择对应的grid类,参考素材管理的实现
|
||||
let gridClass = "";
|
||||
if (imageCount === 1) gridClass = style["single"];
|
||||
else if (imageCount === 2) gridClass = style["double"];
|
||||
else if (imageCount === 3) gridClass = style["triple"];
|
||||
else if (imageCount === 4) gridClass = style["quad"];
|
||||
else if (imageCount > 4) gridClass = style["grid"];
|
||||
|
||||
return (
|
||||
<div className={style["moment-item"]} key={moment.id}>
|
||||
<div className={style["moment-date"]}>
|
||||
<div className={style["date-day"]}>{day}</div>
|
||||
<div className={style["date-month"]}>{month}</div>
|
||||
</div>
|
||||
<div className={style["moment-content"]}>
|
||||
{moment.content && (
|
||||
<div className={style["moment-text"]}>
|
||||
{moment.content}
|
||||
</div>
|
||||
)}
|
||||
{imageCount > 0 && (
|
||||
<div className={style["moment-images"]}>
|
||||
<div
|
||||
className={`${style["image-grid"]} ${gridClass}`}
|
||||
>
|
||||
{moment.resUrls
|
||||
.slice(0, 9)
|
||||
.map((url, index) => (
|
||||
<img
|
||||
key={`${moment.id}-img-${index}`}
|
||||
src={url}
|
||||
alt="朋友圈图片"
|
||||
/>
|
||||
))}
|
||||
{imageCount > 9 && (
|
||||
<div className={style["image-more"]}>
|
||||
+{imageCount - 9}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={style["moment-footer"]}>
|
||||
<span className={style["moment-time"]}>
|
||||
{timeAgo}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
{moments.length < momentsTotal && (
|
||||
<div className={style["moments-load-more"]}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleLoadMoreMoments}
|
||||
loading={isFetchingMoments}
|
||||
disabled={isFetchingMoments}
|
||||
>
|
||||
加载更多
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style["empty"]}>暂无风险记录</div>
|
||||
)}
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,39 @@
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.filter-button {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1677ff;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
&.filter-button-active {
|
||||
background: #1677ff;
|
||||
border-color: #1677ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -33,11 +33,12 @@ const WechatAccounts: React.FC = () => {
|
||||
const [totalAccounts, setTotalAccounts] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [statusFilter, setStatusFilter] = useState<"all" | "online" | "offline">("all");
|
||||
|
||||
// 获取路由参数 wechatStatus
|
||||
const wechatStatus = searchParams.get("wechatStatus");
|
||||
|
||||
const fetchAccounts = async (page = 1, keyword = "") => {
|
||||
const fetchAccounts = async (page = 1, keyword = "", status?: "all" | "online" | "offline") => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
@@ -46,8 +47,12 @@ const WechatAccounts: React.FC = () => {
|
||||
keyword,
|
||||
};
|
||||
|
||||
// 如果有 wechatStatus 参数,添加到请求参数中
|
||||
if (wechatStatus) {
|
||||
// 优先使用传入的status参数,否则使用路由参数,最后使用状态中的筛选
|
||||
const filterStatus = status || wechatStatus || statusFilter;
|
||||
|
||||
if (filterStatus && filterStatus !== "all") {
|
||||
params.wechatStatus = filterStatus === "online" ? "1" : "0";
|
||||
} else if (wechatStatus) {
|
||||
params.wechatStatus = wechatStatus;
|
||||
}
|
||||
|
||||
@@ -60,7 +65,7 @@ const WechatAccounts: React.FC = () => {
|
||||
setTotalAccounts(0);
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.show({ content: "获取微信号失败", position: "top" });
|
||||
|
||||
setAccounts([]);
|
||||
setTotalAccounts(0);
|
||||
} finally {
|
||||
@@ -69,18 +74,24 @@ const WechatAccounts: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccounts(currentPage, searchTerm);
|
||||
fetchAccounts(currentPage, searchTerm, statusFilter);
|
||||
// eslint-disable-next-line
|
||||
}, [currentPage]);
|
||||
}, [currentPage, statusFilter]);
|
||||
|
||||
const handleSearch = () => {
|
||||
setCurrentPage(1);
|
||||
fetchAccounts(1, searchTerm);
|
||||
fetchAccounts(1, searchTerm, statusFilter);
|
||||
};
|
||||
|
||||
const handleStatusFilterChange = (status: "all" | "online" | "offline") => {
|
||||
setStatusFilter(status);
|
||||
setCurrentPage(1);
|
||||
fetchAccounts(1, searchTerm, status);
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
await fetchAccounts(currentPage, searchTerm);
|
||||
await fetchAccounts(currentPage, searchTerm, statusFilter);
|
||||
setIsRefreshing(false);
|
||||
Toast.show({ content: "刷新成功", position: "top" });
|
||||
};
|
||||
@@ -122,6 +133,31 @@ const WechatAccounts: React.FC = () => {
|
||||
<ReloadOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
<div className={style["filter-bar"]}>
|
||||
<div className={style["filter-buttons"]}>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "all" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("all")}
|
||||
>
|
||||
全部
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "online" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("online")}
|
||||
>
|
||||
在线
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
className={`${style["filter-button"]} ${statusFilter === "offline" ? style["filter-button-active"] : ""}`}
|
||||
onClick={() => handleStatusFilterChange("offline")}
|
||||
>
|
||||
离线
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,13 +36,13 @@ Route::group('v1/', function () {
|
||||
Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index');
|
||||
Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index');
|
||||
Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo');
|
||||
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
|
||||
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
|
||||
|
||||
Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据
|
||||
Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈
|
||||
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'); // 刷新设备微信状态
|
||||
|
||||
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
|
||||
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
|
||||
});
|
||||
|
||||
// 获客场景相关
|
||||
|
||||
@@ -1061,6 +1061,7 @@ class ContentLibraryController extends Controller
|
||||
$where = [
|
||||
['isDel', '=', 0], // 未删除
|
||||
['status', '=', 1], // 已开启
|
||||
['id', '=', 99], // 已开启
|
||||
];
|
||||
|
||||
// 查询符合条件的内容库
|
||||
@@ -1225,7 +1226,7 @@ class ContentLibraryController extends Controller
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
$processedFriends++;
|
||||
|
||||
|
||||
// 如果配置了API并且需要主动获取朋友圈
|
||||
if ($needFetch) {
|
||||
try {
|
||||
@@ -1264,9 +1265,9 @@ class ContentLibraryController extends Controller
|
||||
}
|
||||
|
||||
// 如果指定了采集类型,进行过滤
|
||||
if (!empty($catchTypes)) {
|
||||
/*if (!empty($catchTypes)) {
|
||||
$query->whereIn('type', $catchTypes);
|
||||
}
|
||||
}*/
|
||||
|
||||
// 获取最近20条朋友圈
|
||||
$moments = $query->page(1, 20)->select();
|
||||
@@ -1289,7 +1290,7 @@ class ContentLibraryController extends Controller
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果启用了AI处理
|
||||
/* // 如果启用了AI处理
|
||||
if (!empty($library['aiEnabled']) && !empty($content)) {
|
||||
try {
|
||||
$contentAi = $this->aiRewrite($library, $content);
|
||||
@@ -1300,7 +1301,7 @@ class ContentLibraryController extends Controller
|
||||
\think\facade\Log::error('AI处理失败: ' . $e->getMessage() . ' [朋友圈ID: ' . ($moment['id'] ?? 'unknown') . ']');
|
||||
$moment['contentAi'] = '';
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// 保存到内容库的content_item表
|
||||
if ($this->saveMomentToContentItem($moment, $library['id'], $friend, $nickname)) {
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 查看微信朋友圈列表(仅限当前操盘手可访问的微信)
|
||||
*/
|
||||
class GetWechatMomentsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 主操盘手获取项目下所有设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCompanyDevicesId(): array
|
||||
{
|
||||
return DeviceModel::where('companyId', $this->getUserInfo('companyId'))
|
||||
->column('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主操盘手仅可查看分配到的设备
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserDevicesId(): array
|
||||
{
|
||||
return DeviceUserModel::where([
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
])->column('deviceId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可访问的设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDevicesId(): array
|
||||
{
|
||||
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
|
||||
? $this->getCompanyDevicesId()
|
||||
: $this->getUserDevicesId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可访问的微信ID集合
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAccessibleWechatIds(): array
|
||||
{
|
||||
$deviceIds = $this->getDevicesId();
|
||||
if (empty($deviceIds)) {
|
||||
throw new \Exception('暂无可用设备', 200);
|
||||
}
|
||||
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->whereIn('deviceId', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看朋友圈列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId/s', '');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('wechatId不能为空');
|
||||
}
|
||||
|
||||
// 权限校验:只能查看当前账号可访问的微信
|
||||
$accessibleWechatIds = $this->getAccessibleWechatIds();
|
||||
if (!in_array($wechatId, $accessibleWechatIds, true)) {
|
||||
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
|
||||
}
|
||||
|
||||
// 获取对应的微信账号ID
|
||||
$accountId = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('id');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
|
||||
}
|
||||
|
||||
$query = Db::table('s2_wechat_moments')
|
||||
->where('wechatAccountId', $accountId);
|
||||
|
||||
// 关键词搜索
|
||||
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
|
||||
$query->whereLike('content', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
$type = $this->request->param('type', '');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', (int)$type);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
if ($startTime || $endTime) {
|
||||
$start = $startTime ? strtotime($startTime) : 0;
|
||||
$end = $endTime ? strtotime($endTime) : time();
|
||||
if ($start && $end && $end < $start) {
|
||||
return ResponseHelper::error('结束时间不能早于开始时间');
|
||||
}
|
||||
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
|
||||
}
|
||||
|
||||
$page = (int)$this->request->param('page', 1);
|
||||
$limit = (int)$this->request->param('limit', 10);
|
||||
|
||||
$paginator = $query->order('createTime', 'desc')
|
||||
->paginate($limit, false, ['page' => $page]);
|
||||
|
||||
$list = array_map(function ($item) {
|
||||
return $this->formatMomentRow($item);
|
||||
}, $paginator->items());
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $list,
|
||||
'total' => $paginator->total(),
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化朋友圈数据
|
||||
*
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function formatMomentRow(array $row): array
|
||||
{
|
||||
$formatTime = function ($timestamp) {
|
||||
if (empty($timestamp)) {
|
||||
return '';
|
||||
}
|
||||
return is_numeric($timestamp)
|
||||
? date('Y-m-d H:i:s', $timestamp)
|
||||
: date('Y-m-d H:i:s', strtotime($timestamp));
|
||||
};
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'snsId' => $row['snsId'] ?? '',
|
||||
'type' => (int)($row['type'] ?? 0),
|
||||
'content' => $row['content'] ?? '',
|
||||
'commentList' => $this->decodeJson($row['commentList'] ?? null),
|
||||
'likeList' => $this->decodeJson($row['likeList'] ?? null),
|
||||
'resUrls' => $this->decodeJson($row['resUrls'] ?? null),
|
||||
'createTime' => $formatTime($row['createTime'] ?? null),
|
||||
'momentEntity' => [
|
||||
'lat' => $row['lat'] ?? 0,
|
||||
'lng' => $row['lng'] ?? 0,
|
||||
'location' => $row['location'] ?? '',
|
||||
'picSize' => $row['picSize'] ?? 0,
|
||||
'userName' => $row['userName'] ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字段解析
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function decodeJson($value): array
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return $decoded ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信账号概览控制器
|
||||
* 提供账号概览页面的所有数据接口
|
||||
*/
|
||||
class GetWechatOverviewV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信账号概览数据
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId', '');
|
||||
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('微信ID不能为空');
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 获取微信账号ID(accountId)
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->find();
|
||||
|
||||
if (empty($account)) {
|
||||
return ResponseHelper::error('微信账号不存在');
|
||||
}
|
||||
|
||||
$accountId = $account['id'];
|
||||
|
||||
// 1. 健康分评估
|
||||
$healthScoreData = $this->getHealthScoreAssessment($accountId, $wechatId);
|
||||
|
||||
// 2. 账号价值(模拟数据)
|
||||
$accountValue = $this->getAccountValue($accountId);
|
||||
|
||||
// 3. 今日价值变化(模拟数据)
|
||||
$todayValueChange = $this->getTodayValueChange($accountId);
|
||||
|
||||
// 4. 好友总数
|
||||
$totalFriends = $this->getTotalFriends($wechatId, $companyId);
|
||||
|
||||
// 5. 今日新增好友
|
||||
$todayNewFriends = $this->getTodayNewFriends($wechatId);
|
||||
|
||||
// 6. 高价群聊
|
||||
$highValueChatrooms = $this->getHighValueChatrooms($wechatId, $companyId);
|
||||
|
||||
// 7. 今日新增群聊
|
||||
$todayNewChatrooms = $this->getTodayNewChatrooms($wechatId, $companyId);
|
||||
|
||||
$result = [
|
||||
'healthScoreAssessment' => $healthScoreData,
|
||||
'accountValue' => $accountValue,
|
||||
'todayValueChange' => $todayValueChange,
|
||||
'totalFriends' => $totalFriends,
|
||||
'todayNewFriends' => $todayNewFriends,
|
||||
'highValueChatrooms' => $highValueChatrooms,
|
||||
'todayNewChatrooms' => $todayNewChatrooms,
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康分评估数据
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param string $wechatId 微信ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getHealthScoreAssessment($accountId, $wechatId)
|
||||
{
|
||||
// 获取健康分信息
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
|
||||
|
||||
$healthScore = $healthScoreInfo['healthScore'] ?? 0;
|
||||
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0;
|
||||
|
||||
// 获取今日已加好友数
|
||||
$todayAdded = $this->getTodayAddedCount($wechatId);
|
||||
|
||||
// 获取最后添加时间
|
||||
$lastAddTime = $this->getLastAddTime($wechatId);
|
||||
|
||||
// 判断状态标签
|
||||
$statusTag = $todayAdded > 0 ? '已添加加人' : '';
|
||||
|
||||
// 获取基础构成
|
||||
$baseComposition = $this->getBaseComposition($healthScoreInfo);
|
||||
|
||||
// 获取动态记录
|
||||
$dynamicRecords = $this->getDynamicRecords($healthScoreInfo);
|
||||
|
||||
return [
|
||||
'score' => $healthScore,
|
||||
'dailyLimit' => $maxAddFriendPerDay,
|
||||
'todayAdded' => $todayAdded,
|
||||
'lastAddTime' => $lastAddTime,
|
||||
'statusTag' => $statusTag,
|
||||
'baseComposition' => $baseComposition,
|
||||
'dynamicRecords' => $dynamicRecords,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础构成数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getBaseComposition($healthScoreInfo)
|
||||
{
|
||||
$baseScore = $healthScoreInfo['baseScore'] ?? 0;
|
||||
$baseInfoScore = $healthScoreInfo['baseInfoScore'] ?? 0;
|
||||
$friendCountScore = $healthScoreInfo['friendCountScore'] ?? 0;
|
||||
$friendCount = $healthScoreInfo['friendCount'] ?? 0;
|
||||
|
||||
// 账号基础分(默认60分)
|
||||
$accountBaseScore = 60;
|
||||
|
||||
// 已修改微信号(如果baseInfoScore > 0,说明已修改)
|
||||
$isModifiedAlias = $baseInfoScore > 0;
|
||||
|
||||
$composition = [
|
||||
[
|
||||
'name' => '账号基础分',
|
||||
'score' => $accountBaseScore,
|
||||
'formatted' => '+' . $accountBaseScore,
|
||||
]
|
||||
];
|
||||
|
||||
// 如果已修改微信号,添加基础信息分
|
||||
if ($isModifiedAlias) {
|
||||
$composition[] = [
|
||||
'name' => '已修改微信号',
|
||||
'score' => $baseInfoScore,
|
||||
'formatted' => '+' . $baseInfoScore,
|
||||
];
|
||||
}
|
||||
|
||||
// 好友数量加成
|
||||
if ($friendCountScore > 0) {
|
||||
$composition[] = [
|
||||
'name' => '好友数量加成',
|
||||
'score' => $friendCountScore,
|
||||
'formatted' => '+' . $friendCountScore,
|
||||
'friendCount' => $friendCount, // 显示好友总数
|
||||
];
|
||||
}
|
||||
|
||||
return $composition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态记录数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getDynamicRecords($healthScoreInfo)
|
||||
{
|
||||
$records = [];
|
||||
|
||||
$frequentPenalty = $healthScoreInfo['frequentPenalty'] ?? 0;
|
||||
$frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
|
||||
$banPenalty = $healthScoreInfo['banPenalty'] ?? 0;
|
||||
$isBanned = $healthScoreInfo['isBanned'] ?? 0;
|
||||
$noFrequentBonus = $healthScoreInfo['noFrequentBonus'] ?? 0;
|
||||
$consecutiveNoFrequentDays = $healthScoreInfo['consecutiveNoFrequentDays'] ?? 0;
|
||||
$lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
|
||||
|
||||
// 频繁扣分记录
|
||||
// 根据frequentCount判断是首次还是再次
|
||||
// frequentPenalty存储的是当前状态的扣分(-15或-25),不是累计值
|
||||
if ($frequentCount > 0 && $frequentPenalty < 0) {
|
||||
if ($frequentCount == 1) {
|
||||
// 首次频繁:-15分
|
||||
$records[] = [
|
||||
'name' => '首次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
} else {
|
||||
// 再次频繁:-25分
|
||||
$records[] = [
|
||||
'name' => '再次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 封号扣分记录
|
||||
if ($isBanned && $banPenalty < 0) {
|
||||
$lastBanTime = $healthScoreInfo['lastBanTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '封号',
|
||||
'score' => $banPenalty,
|
||||
'formatted' => (string)$banPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastBanTime ? date('Y-m-d H:i:s', $lastBanTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
// 不频繁加分记录
|
||||
if ($noFrequentBonus > 0 && $consecutiveNoFrequentDays >= 3) {
|
||||
$lastNoFrequentTime = $healthScoreInfo['lastNoFrequentTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '连续' . $consecutiveNoFrequentDays . '天不触发频繁',
|
||||
'score' => $noFrequentBonus,
|
||||
'formatted' => '+' . $noFrequentBonus,
|
||||
'type' => 'bonus',
|
||||
'time' => $lastNoFrequentTime ? date('Y-m-d H:i:s', $lastNoFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已加好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayAddedCount($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后添加时间
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return string
|
||||
*/
|
||||
protected function getLastAddTime($wechatId)
|
||||
{
|
||||
$lastTask = Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->order('createTime', 'desc')
|
||||
->find();
|
||||
|
||||
if (empty($lastTask) || empty($lastTask['createTime'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date('H:i:s', $lastTask['createTime']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号价值(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccountValue($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:¥29,800
|
||||
$value = 29800;
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'formatted' => '¥' . number_format($value, 0, '.', ','),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日价值变化(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getTodayValueChange($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:+500
|
||||
$change = 500;
|
||||
|
||||
return [
|
||||
'change' => $change,
|
||||
'formatted' => $change > 0 ? '+' . $change : (string)$change,
|
||||
'isPositive' => $change > 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友总数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTotalFriends($wechatId, $companyId)
|
||||
{
|
||||
// 优先从 s2_wechat_account 表获取
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('totalFriend')
|
||||
->find();
|
||||
|
||||
if (!empty($account) && isset($account['totalFriend'])) {
|
||||
return (int)$account['totalFriend'];
|
||||
}
|
||||
|
||||
// 如果 totalFriend 为空,则从 s2_wechat_friend 表统计
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriends($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 从 s2_wechat_friend 表统计今日新增
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高价群聊数量
|
||||
* 高价群聊定义:群成员数 >= 50 的群聊
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getHighValueChatrooms($wechatId, $companyId)
|
||||
{
|
||||
// 高价群聊定义:群成员数 >= 50
|
||||
$minMemberCount = 50;
|
||||
|
||||
// 查询该微信账号下的高价群聊
|
||||
// 使用子查询统计每个群的成员数
|
||||
$result = Db::query("
|
||||
SELECT COUNT(DISTINCT c.chatroomId) as count
|
||||
FROM s2_wechat_chatroom c
|
||||
INNER JOIN (
|
||||
SELECT chatroomId, COUNT(*) as memberCount
|
||||
FROM s2_wechat_chatroom_member
|
||||
GROUP BY chatroomId
|
||||
HAVING memberCount >= ?
|
||||
) m ON c.chatroomId = m.chatroomId
|
||||
WHERE c.wechatAccountWechatId = ?
|
||||
AND c.isDeleted = 0
|
||||
", [$minMemberCount, $wechatId]);
|
||||
|
||||
return !empty($result) ? (int)$result[0]['count'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增群聊数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewChatrooms($wechatId, $companyId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_wechat_chatroom')
|
||||
->where('wechatAccountWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,25 @@ 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\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
// 不再使用WechatFriendShipModel和WechatCustomerModel,改为直接查询s2_wechat_friend和s2_wechat_account_score表
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信控制器
|
||||
*
|
||||
* 性能优化建议:
|
||||
* 1. 为以下字段添加索引以提高查询性能:
|
||||
* - device_wechat_login表: (companyId, wechatId), (deviceId)
|
||||
* - wechat_account表: (wechatId)
|
||||
* - wechat_customer表: (companyId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId), (createTime)
|
||||
* - s2_wechat_message表: (wechatAccountId, wechatTime)
|
||||
*
|
||||
* 2. 考虑创建以下复合索引:
|
||||
* - device_wechat_login表: (companyId, deviceId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId, createTime)
|
||||
*/
|
||||
class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
{
|
||||
@@ -66,6 +77,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 获取有登录设备的微信id
|
||||
* 优化:使用索引字段,减少数据查询量
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -76,12 +88,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
throw new \Exception('暂无设备数据', 200);
|
||||
}
|
||||
|
||||
return DeviceWechatLoginModel::where(
|
||||
[
|
||||
// 优化:直接使用DISTINCT减少数据传输量
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where([
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
// 'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
|
||||
]
|
||||
)
|
||||
])
|
||||
->where('deviceId', 'in', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
@@ -110,24 +122,50 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:减少查询字段,使用索引,优化JOIN条件
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getOnlineWechatList(array $where): \think\Paginator
|
||||
{
|
||||
// 获取微信在线状态筛选参数(1=在线,0=离线,不传=全部)
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 优化:只查询必要字段,使用FORCE INDEX提示数据库使用索引
|
||||
$query = WechatAccountModel::alias('w')
|
||||
->field(
|
||||
[
|
||||
'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','l.alive'
|
||||
'MAX(l.deviceId) as deviceId', 'MAX(l.alive) as alive' // 使用MAX确保GROUP BY时获取正确的在线状态
|
||||
]
|
||||
)
|
||||
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'))
|
||||
->order('w.id desc')
|
||||
->group('w.wechatId');
|
||||
// 优化:使用INNER JOIN代替LEFT JOIN,并添加索引提示
|
||||
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'), 'INNER')
|
||||
// 添加s2_wechat_account表的LEFT JOIN,用于筛选微信在线状态
|
||||
->join(['s2_wechat_account' => 'sa'], 'w.wechatId = sa.wechatId', 'LEFT')
|
||||
->group('w.wechatId')
|
||||
// 优化:在线状态优先排序(alive=1的排在前面),然后按wechatId排序
|
||||
// 注意:ORDER BY使用SELECT中定义的别名alive,而不是聚合函数
|
||||
->order('alive desc, w.wechatId desc');
|
||||
|
||||
// 根据wechatStatus参数筛选(1=在线,0=离线,不传=全部)
|
||||
if ($wechatStatus !== null && $wechatStatus !== '') {
|
||||
$wechatStatus = (int)$wechatStatus;
|
||||
if ($wechatStatus === 1) {
|
||||
// 筛选在线:wechatAlive = 1
|
||||
$query->where('sa.wechatAlive', 1);
|
||||
} elseif ($wechatStatus === 0) {
|
||||
// 筛选离线:wechatAlive = 0 或 NULL
|
||||
$query->where(function($query) {
|
||||
$query->where('sa.wechatAlive', 0)
|
||||
->whereOr('sa.wechatAlive', 'exp', 'IS NULL');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 应用查询条件
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
@@ -142,7 +180,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
// 优化:使用简单计数查询
|
||||
return $query->paginate(
|
||||
$this->request->param('limit/d', 10),
|
||||
false,
|
||||
['page' => $this->request->param('page/d', 1)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +210,15 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
$metrics = $this->collectWechatMetrics($wechatIds);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$addLimit = $metrics['addLimit'][$item->wechatId] ?? 0;
|
||||
$todayAdded = $metrics['todayAdded'][$item->wechatId] ?? 0;
|
||||
// 计算今日可添加数量 = 可添加额度 - 今日已添加
|
||||
$todayCanAdd = max(0, $addLimit - $todayAdded);
|
||||
|
||||
$sections = $item->toArray() + [
|
||||
'times' => $metrics['addLimit'][$item->wechatId] ?? 0,
|
||||
'addedCount' => $metrics['todayAdded'][$item->wechatId] ?? 0,
|
||||
'times' => $addLimit,
|
||||
'addedCount' => $todayAdded,
|
||||
'todayCanAdd' => $todayCanAdd, // 今日可添加数量
|
||||
'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0,
|
||||
'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0,
|
||||
'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '',
|
||||
@@ -184,6 +233,8 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
|
||||
/**
|
||||
* 批量收集微信账号的统计信息
|
||||
* 优化:合并查询,减少数据库访问次数,使用缓存
|
||||
*
|
||||
* @param array $wechatIds
|
||||
* @return array
|
||||
*/
|
||||
@@ -203,106 +254,167 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 可添加好友额度
|
||||
$weightRows = WechatCustomerModel::where('companyId', $companyId)
|
||||
|
||||
// 使用缓存键,避免短时间内重复查询
|
||||
$cacheKey = 'wechat_metrics_' . md5(implode(',', $wechatIds) . '_' . $companyId);
|
||||
|
||||
// 尝试从缓存获取数据(缓存5分钟)
|
||||
$cachedMetrics = cache($cacheKey);
|
||||
if ($cachedMetrics) {
|
||||
return $cachedMetrics;
|
||||
}
|
||||
|
||||
// 优化1:可添加好友额度 - 从s2_wechat_account_score表获取maxAddFriendPerDay
|
||||
$scoreRows = Db::table('s2_wechat_account_score')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('weight', 'wechatId');
|
||||
foreach ($weightRows as $wechatId => $weight) {
|
||||
$decoded = json_decode($weight, true);
|
||||
$metrics['addLimit'][$wechatId] = $decoded['addLimit'] ?? 0;
|
||||
->column('maxAddFriendPerDay', 'wechatId');
|
||||
foreach ($scoreRows as $wechatId => $maxAddFriendPerDay) {
|
||||
$metrics['addLimit'][$wechatId] = (int)($maxAddFriendPerDay ?? 0);
|
||||
}
|
||||
|
||||
// 今日新增好友
|
||||
// 优化2:今日新增好友 - 使用索引字段和预计算
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
$todayRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
->select();
|
||||
foreach ($todayRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
|
||||
// 使用单次查询获取所有wechatIds的今日新增和总好友数
|
||||
// 根据数据库结构使用s2_wechat_friend表而不是wechat_friend_ship
|
||||
$friendshipStats = Db::query("
|
||||
SELECT
|
||||
ownerWechatId,
|
||||
SUM(IF(createTime BETWEEN {$start} AND {$end}, 1, 0)) as today_added,
|
||||
COUNT(*) as total_friend
|
||||
FROM
|
||||
s2_wechat_friend
|
||||
WHERE
|
||||
ownerWechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
AND isDeleted = 0
|
||||
GROUP BY
|
||||
ownerWechatId
|
||||
");
|
||||
|
||||
// 处理结果
|
||||
foreach ($friendshipStats as $row) {
|
||||
$wechatId = $row['ownerWechatId'] ?? '';
|
||||
if ($wechatId) {
|
||||
$metrics['todayAdded'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
$metrics['todayAdded'][$wechatId] = (int)($row['today_added'] ?? 0);
|
||||
$metrics['totalFriend'][$wechatId] = (int)($row['total_friend'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 总好友
|
||||
$friendRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
// 优化3:微信在线状态 - 从s2_wechat_account表获取wechatAlive
|
||||
$wechatAccountRows = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->field('wechatId, wechatAlive')
|
||||
->select();
|
||||
foreach ($friendRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
if ($wechatId) {
|
||||
$metrics['totalFriend'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
|
||||
foreach ($wechatAccountRows as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
if (!empty($wechatId)) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['wechatAlive'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 设备状态与备注
|
||||
// 优化4:设备状态与备注 - 使用INNER JOIN和索引
|
||||
$loginRows = Db::name('device_wechat_login')
|
||||
->alias('l')
|
||||
->leftJoin('device d', 'd.id = l.deviceId')
|
||||
->field('l.wechatId,l.alive,d.memo')
|
||||
->join('device d', 'd.id = l.deviceId', 'LEFT')
|
||||
->field('l.wechatId, l.alive, d.memo')
|
||||
->where('l.companyId', $companyId)
|
||||
->whereIn('l.wechatId', $wechatIds)
|
||||
->order('l.id', 'desc')
|
||||
->select();
|
||||
|
||||
// 使用临时数组避免重复处理
|
||||
$processedWechatIds = [];
|
||||
foreach ($loginRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['wechatId'] ?? '') : ($row->wechatId ?? '');
|
||||
if (empty($wechatId) || isset($metrics['wechatStatus'][$wechatId])) {
|
||||
continue;
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
// 只处理每个wechatId的第一条记录(最新的)
|
||||
if (!empty($wechatId) && !in_array($wechatId, $processedWechatIds)) {
|
||||
// 如果s2_wechat_account表中没有wechatAlive,则使用device_wechat_login的alive作为备用
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['alive'] ?? 0);
|
||||
}
|
||||
$metrics['deviceMemo'][$wechatId] = $row['memo'] ?? '';
|
||||
$processedWechatIds[] = $wechatId;
|
||||
}
|
||||
$metrics['wechatStatus'][$wechatId] = (int)(is_array($row) ? ($row['alive'] ?? 0) : ($row->alive ?? 0));
|
||||
$metrics['deviceMemo'][$wechatId] = is_array($row) ? ($row['memo'] ?? '') : ($row->memo ?? '');
|
||||
}
|
||||
|
||||
// 活跃时间
|
||||
$accountMap = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('id', 'wechatId');
|
||||
if (!empty($accountMap)) {
|
||||
$accountRows = Db::table('s2_wechat_message')
|
||||
->whereIn('wechatAccountId', array_values($accountMap))
|
||||
->field('wechatAccountId, MAX(wechatTime) as lastTime')
|
||||
->group('wechatAccountId')
|
||||
->select();
|
||||
$accountLastTime = [];
|
||||
foreach ($accountRows as $row) {
|
||||
$accountId = is_array($row) ? ($row['wechatAccountId'] ?? 0) : ($row->wechatAccountId ?? 0);
|
||||
if ($accountId) {
|
||||
$accountLastTime[$accountId] = (int)(is_array($row) ? ($row['lastTime'] ?? 0) : ($row->lastTime ?? 0));
|
||||
}
|
||||
}
|
||||
foreach ($accountMap as $wechatId => $accountId) {
|
||||
if (isset($accountLastTime[$accountId]) && $accountLastTime[$accountId] > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $accountLastTime[$accountId]);
|
||||
}
|
||||
// 优化5:活跃时间 - 使用JOIN减少查询次数
|
||||
$activeTimeResults = Db::query("
|
||||
SELECT
|
||||
a.wechatId,
|
||||
MAX(m.wechatTime) as lastTime
|
||||
FROM
|
||||
s2_wechat_account a
|
||||
LEFT JOIN
|
||||
s2_wechat_message m ON a.id = m.wechatAccountId
|
||||
WHERE
|
||||
a.wechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
GROUP BY
|
||||
a.wechatId
|
||||
");
|
||||
|
||||
foreach ($activeTimeResults as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
$lastTime = (int)($row['lastTime'] ?? 0);
|
||||
if (!empty($wechatId) && $lastTime > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $lastTime);
|
||||
} else {
|
||||
$metrics['activeTime'][$wechatId] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 确保所有wechatId都有wechatStatus值(默认0)
|
||||
foreach ($wechatIds as $wechatId) {
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 存入缓存,有效期5分钟
|
||||
cache($cacheKey, $metrics, 300);
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:添加缓存,优化分页逻辑
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page/d', 1);
|
||||
$limit = $this->request->param('limit/d', 10);
|
||||
$keyword = $this->request->param('keyword');
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 创建缓存键(基于用户、分页、搜索条件和在线状态筛选)
|
||||
$cacheKey = 'wechat_list_' . $this->getUserInfo('id') . '_' . $page . '_' . $limit . '_' . md5($keyword ?? '') . '_' . ($wechatStatus ?? 'all');
|
||||
|
||||
// 尝试从缓存获取数据(缓存2分钟)
|
||||
$cachedData = cache($cacheKey);
|
||||
if ($cachedData) {
|
||||
return ResponseHelper::success($cachedData);
|
||||
}
|
||||
|
||||
// 如果没有缓存,执行查询
|
||||
$result = $this->getOnlineWechatList(
|
||||
$this->makeWhere()
|
||||
);
|
||||
|
||||
$responseData = [
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
];
|
||||
|
||||
// 存入缓存,有效期2分钟
|
||||
cache($cacheKey, $responseData, 120);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
return ResponseHelper::success($responseData);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
171
Server/sql.sql
171
Server/sql.sql
@@ -11,7 +11,7 @@
|
||||
Target Server Version : 50736
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 12/11/2025 11:05:39
|
||||
Date: 24/11/2025 16:50:43
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
@@ -123,7 +123,7 @@ CREATE TABLE `ck_app_version` (
|
||||
`updateContent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
`createTime` int(11) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_attachments
|
||||
@@ -145,7 +145,7 @@ CREATE TABLE `ck_attachments` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_hash_key`(`hash_key`) USING BTREE,
|
||||
INDEX `idx_server`(`server`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 481 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 505 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_call_recording
|
||||
@@ -222,7 +222,7 @@ CREATE TABLE `ck_content_item` (
|
||||
INDEX `idx_wechatid`(`wechatId`) USING BTREE,
|
||||
INDEX `idx_friendid`(`friendId`) USING BTREE,
|
||||
INDEX `idx_create_time`(`createTime`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5876 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5993 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_content_library
|
||||
@@ -252,7 +252,7 @@ CREATE TABLE `ck_content_library` (
|
||||
`isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
|
||||
`deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_coze_conversation
|
||||
@@ -272,7 +272,7 @@ CREATE TABLE `ck_coze_conversation` (
|
||||
UNIQUE INDEX `idx_conversation_id`(`conversation_id`) USING BTREE,
|
||||
INDEX `idx_bot_id`(`bot_id`) USING BTREE,
|
||||
INDEX `idx_create_time`(`create_time`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_coze_message
|
||||
@@ -331,7 +331,7 @@ CREATE TABLE `ck_customer_acquisition_task` (
|
||||
`deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
|
||||
`apiKey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 168 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_device
|
||||
@@ -372,7 +372,7 @@ CREATE TABLE `ck_device_handle_log` (
|
||||
`companyId` int(11) NULL DEFAULT NULL COMMENT '租户id',
|
||||
`createTime` int(11) NULL DEFAULT NULL COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 304 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 339 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_device_taskconf
|
||||
@@ -395,7 +395,7 @@ CREATE TABLE `ck_device_taskconf` (
|
||||
`updateTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_device_user
|
||||
@@ -425,7 +425,7 @@ CREATE TABLE `ck_device_wechat_login` (
|
||||
`isTips` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否提示迁移',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `wechatId`(`wechatId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 309 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 312 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_flow_package
|
||||
@@ -653,7 +653,7 @@ CREATE TABLE `ck_kf_follow_up` (
|
||||
INDEX `idx_level`(`type`) USING BTREE,
|
||||
INDEX `idx_isRemind`(`isRemind`) USING BTREE,
|
||||
INDEX `idx_isProcess`(`isProcess`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_kf_friend_settings
|
||||
@@ -675,7 +675,7 @@ CREATE TABLE `ck_kf_friend_settings` (
|
||||
INDEX `idx_userId`(`userId`) USING BTREE,
|
||||
INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
|
||||
INDEX `idx_friendId`(`friendId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_kf_keywords
|
||||
@@ -769,7 +769,7 @@ CREATE TABLE `ck_kf_notice` (
|
||||
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`readTime` int(12) NULL DEFAULT NULL COMMENT '读取时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 246 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 247 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_kf_questions
|
||||
@@ -810,7 +810,7 @@ CREATE TABLE `ck_kf_reply` (
|
||||
`isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
|
||||
`delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 130746 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 130751 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_kf_reply_group
|
||||
@@ -977,7 +977,7 @@ CREATE TABLE `ck_task_customer` (
|
||||
INDEX `addTime`(`addTime`) USING BTREE,
|
||||
INDEX `passTime`(`passTime`) USING BTREE,
|
||||
INDEX `updateTime`(`updateTime`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 24192 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 28204 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_tokens_company
|
||||
@@ -990,7 +990,7 @@ CREATE TABLE `ck_tokens_company` (
|
||||
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_tokens_package
|
||||
@@ -1033,7 +1033,7 @@ CREATE TABLE `ck_tokens_record` (
|
||||
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 236 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_traffic_order
|
||||
@@ -1070,8 +1070,9 @@ CREATE TABLE `ck_traffic_pool` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE,
|
||||
INDEX `idx_wechatId`(`wechatId`) USING BTREE,
|
||||
INDEX `idx_mobile`(`mobile`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 959687 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic;
|
||||
INDEX `idx_mobile`(`mobile`) USING BTREE,
|
||||
INDEX `idx_create_time`(`createTime`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1063510 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_traffic_profile
|
||||
@@ -1114,8 +1115,9 @@ CREATE TABLE `ck_traffic_source` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_identifier_sourceId_sceneId`(`identifier`, `sourceId`, `sceneId`) USING BTREE,
|
||||
INDEX `idx_identifier`(`identifier`) USING BTREE,
|
||||
INDEX `idx_companyId`(`companyId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 564508 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic;
|
||||
INDEX `idx_companyId`(`companyId`) USING BTREE,
|
||||
INDEX `idx_company_status_time`(`companyId`, `status`, `updateTime`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 573831 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_traffic_source_package
|
||||
@@ -1242,7 +1244,7 @@ CREATE TABLE `ck_user_portrait` (
|
||||
`createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 17718 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 19014 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_users
|
||||
@@ -1267,7 +1269,7 @@ CREATE TABLE `ck_users` (
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
|
||||
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1652 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1658 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_vendor_order
|
||||
@@ -1360,7 +1362,7 @@ CREATE TABLE `ck_wechat_account` (
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3097959 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3614968 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_wechat_customer
|
||||
@@ -1378,7 +1380,7 @@ CREATE TABLE `ck_wechat_customer` (
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_wechat_friendship
|
||||
@@ -1434,7 +1436,7 @@ CREATE TABLE `ck_wechat_group_member` (
|
||||
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_identifier_chatroomId_groupId`(`identifier`, `chatroomId`, `groupId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 549847 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 554147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_wechat_restricts
|
||||
@@ -1451,7 +1453,7 @@ CREATE TABLE `ck_wechat_restricts` (
|
||||
`restrictTime` int(11) NULL DEFAULT NULL COMMENT '限制日期',
|
||||
`recoveryTime` int(11) NULL DEFAULT NULL COMMENT '恢复日期',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1319 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_wechat_tag
|
||||
@@ -1489,7 +1491,7 @@ CREATE TABLE `ck_workbench` (
|
||||
INDEX `idx_user_id`(`userId`) USING BTREE,
|
||||
INDEX `idx_type`(`type`) USING BTREE,
|
||||
INDEX `idx_status`(`status`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 282 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_auto_like
|
||||
@@ -1534,7 +1536,7 @@ CREATE TABLE `ck_workbench_auto_like_item` (
|
||||
INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
|
||||
INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE,
|
||||
INDEX `momentsId`(`momentsId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4639 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4653 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_group_create
|
||||
@@ -1604,9 +1606,14 @@ CREATE TABLE `ck_workbench_group_push` (
|
||||
`promotionSiteId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东广告位',
|
||||
`trafficPools` json NULL COMMENT '流量池',
|
||||
`devices` json NULL,
|
||||
`groupPushSubType` tinyint(2) NULL DEFAULT 1 COMMENT '群推送子类型 1=群群发,2=群公告',
|
||||
`announcementContent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
`enableAiRewrite` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||
`aiRewritePrompt` tinyint(2) NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic;
|
||||
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE,
|
||||
INDEX `idx_status_targetType`(`status`, `targetType`, `workbenchId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_group_push_item
|
||||
@@ -1622,7 +1629,9 @@ CREATE TABLE `ck_workbench_group_push_item` (
|
||||
`wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
|
||||
`isLoop` tinyint(2) NULL DEFAULT 0 COMMENT '是否循环完成',
|
||||
`createTime` int(11) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_workbench_target_time`(`workbenchId`, `targetType`, `createTime`) USING BTREE,
|
||||
INDEX `idx_workbench_target_friend`(`workbenchId`, `targetType`, `friendId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -1677,7 +1686,7 @@ CREATE TABLE `ck_workbench_moments_sync` (
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_moments_sync_item
|
||||
@@ -1691,8 +1700,10 @@ CREATE TABLE `ck_workbench_moments_sync_item` (
|
||||
`wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
|
||||
`createTime` int(11) NOT NULL COMMENT '创建时间',
|
||||
`isLoop` tinyint(2) NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1650 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_workbench_time`(`workbenchId`, `createTime`) USING BTREE,
|
||||
INDEX `idx_workbench_content`(`workbenchId`, `contentId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_traffic_config
|
||||
@@ -1714,7 +1725,7 @@ CREATE TABLE `ck_workbench_traffic_config` (
|
||||
`updateTime` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uniq_workbench`(`workbenchId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ck_workbench_traffic_config_item
|
||||
@@ -1736,7 +1747,7 @@ CREATE TABLE `ck_workbench_traffic_config_item` (
|
||||
INDEX `deviceId`(`deviceId`) USING BTREE,
|
||||
INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
|
||||
INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 49898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 54241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for s2_allot_rule
|
||||
@@ -1869,6 +1880,7 @@ CREATE TABLE `s2_device` (
|
||||
`groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组名称',
|
||||
`wechatAccounts` json NULL COMMENT '微信账号列表JSON',
|
||||
`alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线',
|
||||
`aliveTime` int(11) NULL DEFAULT 0,
|
||||
`lastAliveTime` int(11) NULL DEFAULT NULL COMMENT '最后在线时间',
|
||||
`tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID',
|
||||
`groupId` int(11) NULL DEFAULT NULL COMMENT '分组ID',
|
||||
@@ -1938,6 +1950,7 @@ CREATE TABLE `s2_friend_task` (
|
||||
`accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
|
||||
`accountUsername` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间戳',
|
||||
`is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)',
|
||||
UNIQUE INDEX `uk_task_id`(`id`) USING BTREE,
|
||||
INDEX `idx_tenant_id`(`tenantId`) USING BTREE,
|
||||
INDEX `idx_operator_account_id`(`operatorAccountId`) USING BTREE,
|
||||
@@ -2064,6 +2077,7 @@ CREATE TABLE `s2_wechat_account` (
|
||||
`keFuAlive` tinyint(1) NULL DEFAULT 0 COMMENT '客服是否在线',
|
||||
`deviceAlive` tinyint(1) NULL DEFAULT 0 COMMENT '设备是否在线',
|
||||
`wechatAlive` tinyint(1) NULL DEFAULT 0 COMMENT '微信是否在线',
|
||||
`wechatAliveTime` int(11) NULL DEFAULT 0 COMMENT '在线时间',
|
||||
`yesterdayMsgCount` int(11) NULL DEFAULT 0 COMMENT '昨日消息数',
|
||||
`sevenDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '7天消息数',
|
||||
`thirtyDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '30天消息数',
|
||||
@@ -2092,9 +2106,82 @@ CREATE TABLE `s2_wechat_account` (
|
||||
`createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`status` tinyint(3) NULL DEFAULT 1 COMMENT '状态值',
|
||||
INDEX `idx_wechat_id`(`wechatId`) USING BTREE
|
||||
`healthScore` int(11) NULL DEFAULT 60 COMMENT '健康分总分(基础分+动态分)',
|
||||
`baseScore` int(11) NULL DEFAULT 60 COMMENT '基础分(60-100分)',
|
||||
`dynamicScore` int(11) NULL DEFAULT 0 COMMENT '动态分(扣分和加分)',
|
||||
`isModifiedAlias` tinyint(1) NULL DEFAULT 0 COMMENT '是否已修改微信号(0=未修改,1=已修改)',
|
||||
`lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)',
|
||||
`frequentCount` int(11) NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)',
|
||||
`lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)',
|
||||
`consecutiveNoFrequentDays` int(11) NULL DEFAULT 0 COMMENT '连续不频繁天数(用于加分)',
|
||||
`scoreUpdateTime` int(11) NULL DEFAULT NULL COMMENT '评分更新时间',
|
||||
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
|
||||
INDEX `idx_health_score`(`healthScore`) USING BTREE,
|
||||
INDEX `idx_is_modified_alias`(`isModifiedAlias`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for s2_wechat_account_score
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `s2_wechat_account_score`;
|
||||
CREATE TABLE `s2_wechat_account_score` (
|
||||
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`accountId` int(11) NOT NULL COMMENT '微信账号ID(s2_wechat_account.id)',
|
||||
`wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
|
||||
`baseScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础分(60-100分)',
|
||||
`baseScoreCalculated` tinyint(1) NOT NULL DEFAULT 0 COMMENT '基础分是否已计算(0=未计算,1=已计算)',
|
||||
`baseScoreCalcTime` int(11) NULL DEFAULT NULL COMMENT '基础分计算时间',
|
||||
`baseInfoScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础信息分(0-10分)',
|
||||
`isModifiedAlias` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已修改微信号(0=未修改,1=已修改)',
|
||||
`friendCountScore` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量分(0-30分)',
|
||||
`friendCount` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量(评分时的快照)',
|
||||
`friendCountSource` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友数量来源(manual=手动,sync=同步)',
|
||||
`dynamicScore` int(11) NOT NULL DEFAULT 0 COMMENT '动态分(扣分和加分)',
|
||||
`lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)',
|
||||
`frequentCount` int(11) NOT NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)',
|
||||
`frequentPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '频繁扣分(累计)',
|
||||
`lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)',
|
||||
`consecutiveNoFrequentDays` int(11) NOT NULL DEFAULT 0 COMMENT '连续不频繁天数',
|
||||
`noFrequentBonus` int(11) NOT NULL DEFAULT 0 COMMENT '不频繁加分(累计)',
|
||||
`isBanned` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否封号(0=否,1=是)',
|
||||
`banPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '封号扣分',
|
||||
`healthScore` int(11) NOT NULL DEFAULT 0 COMMENT '健康分总分(基础分+动态分)',
|
||||
`maxAddFriendPerDay` int(11) NOT NULL DEFAULT 0 COMMENT '每日最大加人次数',
|
||||
`createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||
`updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
`lastBanTime` int(11) NULL DEFAULT NULL COMMENT '最后一次封号时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_account_id`(`accountId`) USING BTREE,
|
||||
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
|
||||
INDEX `idx_health_score`(`healthScore`) USING BTREE,
|
||||
INDEX `idx_base_score_calculated`(`baseScoreCalculated`) USING BTREE,
|
||||
INDEX `idx_update_time`(`updateTime`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 363 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号评分记录表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for s2_wechat_account_score_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `s2_wechat_account_score_log`;
|
||||
CREATE TABLE `s2_wechat_account_score_log` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`accountId` int(11) NOT NULL COMMENT '微信账号ID',
|
||||
`wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
|
||||
`field` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '变动字段(如frequentPenalty)',
|
||||
`changeValue` int(11) NOT NULL DEFAULT 0 COMMENT '变动值(正加负减)',
|
||||
`valueBefore` int(11) NULL DEFAULT NULL COMMENT '变更前的字段值',
|
||||
`valueAfter` int(11) NULL DEFAULT NULL COMMENT '变更后的字段值',
|
||||
`category` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类:penalty/bonus/dynamic_total/health_total等',
|
||||
`source` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '触发来源 friend_task/wechat_message/system',
|
||||
`sourceId` bigint(20) NULL DEFAULT NULL COMMENT '关联记录ID(如任务/消息ID)',
|
||||
`extra` json NULL COMMENT '附加信息(JSON)',
|
||||
`totalScoreBefore` int(11) NULL DEFAULT NULL COMMENT '变更前健康总分',
|
||||
`totalScoreAfter` int(11) NULL DEFAULT NULL COMMENT '变更后健康总分',
|
||||
`createTime` int(11) NOT NULL COMMENT '记录时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_account_field`(`accountId`, `field`) USING BTREE,
|
||||
INDEX `idx_wechat_id`(`wechatId`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号健康分加减分日志' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for s2_wechat_chatroom
|
||||
-- ----------------------------
|
||||
@@ -2150,7 +2237,7 @@ CREATE TABLE `s2_wechat_chatroom_member` (
|
||||
UNIQUE INDEX `uk_chatroom_wechat`(`chatroomId`, `wechatId`) USING BTREE,
|
||||
INDEX `chatroomId`(`chatroomId`) USING BTREE,
|
||||
INDEX `wechatId`(`wechatId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 495043 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 495174 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for s2_wechat_friend
|
||||
@@ -2198,6 +2285,9 @@ CREATE TABLE `s2_wechat_friend` (
|
||||
`R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
|
||||
`F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
|
||||
`M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
|
||||
`realName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
|
||||
`company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '公司',
|
||||
`position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职位',
|
||||
UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`, `wechatAccountId`) USING BTREE,
|
||||
INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE,
|
||||
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
|
||||
@@ -2257,6 +2347,7 @@ CREATE TABLE `s2_wechat_message` (
|
||||
`msgId` bigint(20) NULL DEFAULT NULL COMMENT '消息ID',
|
||||
`recallId` tinyint(1) NULL DEFAULT 0 COMMENT '撤回ID',
|
||||
`isRead` tinyint(1) NULL DEFAULT 0 COMMENT '是否读取',
|
||||
`is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_wechatChatroomId`(`wechatChatroomId`) USING BTREE,
|
||||
INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
|
||||
@@ -2296,6 +2387,6 @@ CREATE TABLE `s2_wechat_moments` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `idx_sns_account`(`snsId`, `wechatAccountId`) USING BTREE,
|
||||
INDEX `idx_account_friend`(`wechatAccountId`, `wechatFriendId`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 39669 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 40130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
Reference in New Issue
Block a user