1、修复场景获客数据统计问题
2、修复默认头像破图问题 3、修复健康分展示问题 4、修复获客场景_好友迁移编辑会跳到其他类目问题
This commit is contained in:
@@ -692,13 +692,13 @@ const WechatAccountDetail: React.FC = () => {
|
||||
<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>
|
||||
<span className={style["status-time"]}>最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "-"}</span>
|
||||
</div>
|
||||
<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}
|
||||
{overviewData?.healthScoreAssessment?.score || 0}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
@@ -810,7 +810,7 @@ const WechatAccountDetail: React.FC = () => {
|
||||
<div className={style["score-circle-wrapper"]}>
|
||||
<div className={style["score-circle"]}>
|
||||
<div className={style["score-number"]}>
|
||||
{overviewData?.healthScoreAssessment?.score || 67}
|
||||
{overviewData?.healthScoreAssessment?.score || 0}
|
||||
</div>
|
||||
<div className={style["score-label"]}>SCORE</div>
|
||||
</div>
|
||||
@@ -869,7 +869,7 @@ const WechatAccountDetail: React.FC = () => {
|
||||
<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.name || record.description || "记录"}
|
||||
{record.statusTag && (
|
||||
<span className={style["health-item-tag"]}>
|
||||
{record.statusTag}
|
||||
|
||||
@@ -36,6 +36,13 @@ export function getUserList(planId: string, type: number) {
|
||||
}
|
||||
|
||||
//获客列表
|
||||
export function getFriendRequestTaskStats(taskId: string) {
|
||||
return request(`/v1/dashboard/friendRequestTaskStats`, { taskId }, "GET");
|
||||
export function getFriendRequestTaskStats(
|
||||
taskId: string,
|
||||
params?: { startTime?: string; endTime?: string },
|
||||
) {
|
||||
return request(
|
||||
`/v1/dashboard/friendRequestTaskStats`,
|
||||
{ taskId, ...params },
|
||||
"GET",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Popup, SpinLoading } from "antd-mobile";
|
||||
import { Button, message } from "antd";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { Popup, SpinLoading, DatePicker } from "antd-mobile";
|
||||
import { Button, message, Input } from "antd";
|
||||
import { CloseOutlined, CalendarOutlined } from "@ant-design/icons";
|
||||
import style from "./Popups.module.scss";
|
||||
import { getFriendRequestTaskStats } from "../api";
|
||||
import LineChart2 from "@/components/LineChart2";
|
||||
@@ -39,31 +39,73 @@ const PoolListModal: React.FC<PoolListModalProps> = ({
|
||||
const [xData, setXData] = useState<any[]>([]);
|
||||
const [yData, setYData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [startTime, setStartTime] = useState<Date | null>(null);
|
||||
const [endTime, setEndTime] = useState<Date | null>(null);
|
||||
const [showStartTimePicker, setShowStartTimePicker] = useState(false);
|
||||
const [showEndTimePicker, setShowEndTimePicker] = useState(false);
|
||||
|
||||
// 当弹窗打开且有ruleId时,获取数据
|
||||
// 格式化日期为 YYYY-MM-DD
|
||||
const formatDate = useCallback((date: Date | null): string => {
|
||||
if (!date) return "";
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(date.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
}, []);
|
||||
|
||||
// 初始化默认时间(近7天)
|
||||
useEffect(() => {
|
||||
if (visible && ruleId) {
|
||||
setLoading(true);
|
||||
getFriendRequestTaskStats(ruleId.toString())
|
||||
.then(res => {
|
||||
console.log(res);
|
||||
setXData(res.dateArray);
|
||||
setYData([
|
||||
res.allNumArray,
|
||||
res.errorNumArray,
|
||||
res.passNumArray,
|
||||
res.passRateArray,
|
||||
res.successNumArray,
|
||||
res.successRateArray,
|
||||
]);
|
||||
setStatistics(res.totalStats);
|
||||
setLoading(false);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
if (visible) {
|
||||
// 如果时间未设置,设置默认值为近7天
|
||||
if (!startTime || !endTime) {
|
||||
const today = new Date();
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(today.getDate() - 7);
|
||||
setStartTime(sevenDaysAgo);
|
||||
setEndTime(today);
|
||||
}
|
||||
} else {
|
||||
// 弹窗关闭时重置时间,下次打开时重新初始化
|
||||
setStartTime(null);
|
||||
setEndTime(null);
|
||||
}
|
||||
}, [visible, ruleId]);
|
||||
}, [visible]);
|
||||
|
||||
// 当弹窗打开或有ruleId或时间筛选变化时,获取数据
|
||||
useEffect(() => {
|
||||
if (!visible || !ruleId) return;
|
||||
|
||||
setLoading(true);
|
||||
const params: { startTime?: string; endTime?: string } = {};
|
||||
if (startTime) {
|
||||
params.startTime = formatDate(startTime);
|
||||
}
|
||||
if (endTime) {
|
||||
params.endTime = formatDate(endTime);
|
||||
}
|
||||
|
||||
getFriendRequestTaskStats(ruleId.toString(), params)
|
||||
.then(res => {
|
||||
console.log(res);
|
||||
setXData(res.dateArray);
|
||||
setYData([
|
||||
res.allNumArray,
|
||||
res.errorNumArray,
|
||||
res.passNumArray,
|
||||
res.passRateArray,
|
||||
res.successNumArray,
|
||||
res.successRateArray,
|
||||
]);
|
||||
setStatistics(res.totalStats);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取统计数据失败:", error);
|
||||
message.error("获取统计数据失败");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [visible, ruleId, startTime, endTime, formatDate]);
|
||||
|
||||
const title = ruleName ? `${ruleName} - 累计统计数据` : "累计统计数据";
|
||||
return (
|
||||
@@ -89,6 +131,55 @@ const PoolListModal: React.FC<PoolListModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 时间筛选 */}
|
||||
<div className={style.dateFilter}>
|
||||
<div className={style.dateFilterItem}>
|
||||
<label className={style.dateFilterLabel}>开始时间</label>
|
||||
<Input
|
||||
readOnly
|
||||
placeholder="请选择开始时间"
|
||||
value={startTime ? formatDate(startTime) : ""}
|
||||
onClick={() => setShowStartTimePicker(true)}
|
||||
prefix={<CalendarOutlined />}
|
||||
className={style.dateFilterInput}
|
||||
/>
|
||||
<DatePicker
|
||||
visible={showStartTimePicker}
|
||||
title="开始时间"
|
||||
value={startTime}
|
||||
max={endTime || new Date()}
|
||||
onClose={() => setShowStartTimePicker(false)}
|
||||
onConfirm={val => {
|
||||
setStartTime(val);
|
||||
setShowStartTimePicker(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.dateFilterItem}>
|
||||
<label className={style.dateFilterLabel}>结束时间</label>
|
||||
<Input
|
||||
readOnly
|
||||
placeholder="请选择结束时间"
|
||||
value={endTime ? formatDate(endTime) : ""}
|
||||
onClick={() => setShowEndTimePicker(true)}
|
||||
prefix={<CalendarOutlined />}
|
||||
className={style.dateFilterInput}
|
||||
/>
|
||||
<DatePicker
|
||||
visible={showEndTimePicker}
|
||||
title="结束时间"
|
||||
value={endTime}
|
||||
min={startTime || undefined}
|
||||
max={new Date()}
|
||||
onClose={() => setShowEndTimePicker(false)}
|
||||
onConfirm={val => {
|
||||
setEndTime(val);
|
||||
setShowEndTimePicker(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计数据表格 */}
|
||||
<div className={style.statisticsContent}>
|
||||
{loading ? (
|
||||
|
||||
@@ -663,6 +663,32 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
// 日期筛选样式
|
||||
.dateFilter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.dateFilterItem {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dateFilterLabel {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dateFilterInput {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 统计数据弹窗样式
|
||||
.statisticsContent {
|
||||
flex: 1;
|
||||
|
||||
@@ -90,7 +90,7 @@ export default function NewPlan() {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
name: detail.name ?? "",
|
||||
scenario: Number(detail.scenario) || 1,
|
||||
scenario: Number(detail.sceneId || detail.scenario) || 1,
|
||||
scenarioTags: detail.scenarioTags ?? [],
|
||||
customTags: detail.customTags ?? [],
|
||||
customTagsOptions: detail.customTags ?? [],
|
||||
@@ -102,7 +102,7 @@ export default function NewPlan() {
|
||||
startTime: detail.startTime ?? "09:00",
|
||||
endTime: detail.endTime ?? "18:00",
|
||||
enabled: detail.enabled ?? true,
|
||||
sceneId: Number(detail.scenario) || 1,
|
||||
sceneId: Number(detail.sceneId || detail.scenario) || 1,
|
||||
remarkFormat: detail.remarkFormat ?? "",
|
||||
addFriendInterval: detail.addFriendInterval ?? 1,
|
||||
tips: detail.tips ?? "",
|
||||
|
||||
@@ -40,6 +40,7 @@ const generatePosterMaterials = (): Material[] => {
|
||||
};
|
||||
|
||||
const BasicSettings: React.FC<BasicSettingsProps> = ({
|
||||
isEdit,
|
||||
formData,
|
||||
onChange,
|
||||
sceneList,
|
||||
@@ -249,16 +250,27 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
|
||||
) : (
|
||||
<div className={styles["basic-scene-grid"]}>
|
||||
{sceneList
|
||||
.filter(scene => scene.id !== 10)
|
||||
.filter(scene => {
|
||||
// 编辑模式下,如果当前选中的场景 id 是 10,则显示它
|
||||
if (isEdit && formData.scenario === 10 && scene.id === 10) {
|
||||
return true;
|
||||
}
|
||||
// 其他情况过滤掉 id 为 10 的场景
|
||||
return scene.id !== 10;
|
||||
})
|
||||
.map(scene => {
|
||||
const selected = formData.scenario === scene.id;
|
||||
// 编辑模式下,如果当前场景 id 是 10,则禁用所有场景选择
|
||||
const isDisabled = isEdit && formData.scenario === 10;
|
||||
return (
|
||||
<button
|
||||
key={scene.id}
|
||||
onClick={() => handleScenarioSelect(scene.id)}
|
||||
onClick={() => !isDisabled && handleScenarioSelect(scene.id)}
|
||||
disabled={isDisabled}
|
||||
className={
|
||||
styles["basic-scene-btn"] +
|
||||
(selected ? " " + styles.selected : "")
|
||||
(selected ? " " + styles.selected : "") +
|
||||
(isDisabled ? " " + styles.disabled : "")
|
||||
}
|
||||
>
|
||||
{scene.name.replace("获客", "")}
|
||||
|
||||
@@ -29,6 +29,17 @@
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.08);
|
||||
}
|
||||
.basic-scene-btn.disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
background: rgba(#1677ff, 0.1);
|
||||
color: #1677ff;
|
||||
}
|
||||
.basic-scene-btn.disabled.selected {
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.basic-label {
|
||||
margin-bottom: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -308,7 +308,7 @@ class StatsController extends Controller
|
||||
->field("FROM_UNIXTIME(addTime, '%m-%d') AS d, COUNT(*) AS c")
|
||||
->where(['task_id' => $taskId])
|
||||
->where('addTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->whereIn('status', [1, 2, 4])
|
||||
->whereIn('status', [1, 2, 4, 5])
|
||||
->group('d')
|
||||
->select();
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class StoreAccountController extends BaseController
|
||||
'phone' => $phone,
|
||||
'passwordMd5' => md5($password),
|
||||
'passwordLocal' => localEncrypt($password),
|
||||
'avatar' => 'https://img.icons8.com/color/512/circled-user-male-skin-type-7.png',
|
||||
'avatar' => '',
|
||||
'isAdmin' => 0,
|
||||
'companyId' => $companyId,
|
||||
'typeId' => 2, // 门店端固定为2
|
||||
|
||||
@@ -405,8 +405,8 @@ class PlanSceneV1Controller extends BaseController
|
||||
->field([
|
||||
'task_id as taskId',
|
||||
'COUNT(1) as acquiredCount',
|
||||
"SUM(CASE WHEN status IN (1,2,3,4) THEN 1 ELSE 0 END) as addedCount",
|
||||
"SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) as passCount",
|
||||
"SUM(CASE WHEN status IN (1,2,3,4,5) THEN 1 ELSE 0 END) as addedCount",
|
||||
"SUM(CASE WHEN status IN (4,5) THEN 1 ELSE 0 END) as passCount",
|
||||
'MAX(updateTime) as lastUpdated'
|
||||
])
|
||||
->group('task_id')
|
||||
@@ -511,7 +511,7 @@ class PlanSceneV1Controller extends BaseController
|
||||
$query = Db::name('task_customer')->where(['task_id' => $task['id']]);
|
||||
|
||||
if ($type == 2){
|
||||
$query = $query->where('status',4);
|
||||
$query = $query->whereIn('status',[4,5]);
|
||||
}
|
||||
|
||||
if (!empty($keyword)) {
|
||||
|
||||
@@ -179,7 +179,7 @@ class GetWechatController extends BaseController
|
||||
'activityLevel' => $this->getActivityLevel($wechatId),
|
||||
'accountWeight' => $this->getAccountWeight($wechatId),
|
||||
'statistics' => $this->getStatistics($wechatId),
|
||||
'restrictions' => $this->getRestrict($wechatId),
|
||||
// 'restrictions' => $this->getRestrict($wechatId),
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class Adapter implements WeChatServiceInterface
|
||||
// 根据健康分判断24h内加的好友数量限制
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
|
||||
|
||||
|
||||
// 如果健康分记录不存在,先计算一次
|
||||
if (empty($healthScoreInfo)) {
|
||||
try {
|
||||
@@ -228,10 +228,10 @@ class Adapter implements WeChatServiceInterface
|
||||
$maxAddFriendPerDay = 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取每日最大加人次数(基于健康分)
|
||||
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 5;
|
||||
|
||||
|
||||
// 如果健康分为0或很低,不允许添加好友
|
||||
if ($maxAddFriendPerDay <= 0) {
|
||||
Log::info("账号健康分过低,不允许添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")");
|
||||
@@ -367,6 +367,10 @@ class Adapter implements WeChatServiceInterface
|
||||
if (!empty($wechatId)) {
|
||||
$isFriend = $this->checkIfIsWeChatFriendByPhone($wechatId, $task['phone']);
|
||||
if ($isFriend) {
|
||||
// 更新状态为5(已通过未发消息)
|
||||
Db::name('task_customer')
|
||||
->where('id', $task['id'])
|
||||
->update(['status' => 5,'passTime' => time(), 'updateTime' => time()]);
|
||||
$passedWeChatId = $wechatId;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user