feat:提交同步

This commit is contained in:
许永平
2025-07-07 11:00:58 +08:00
parent 9a49ef5e57
commit 3d1050db3d
2 changed files with 262 additions and 115 deletions

View File

@@ -1,4 +1,4 @@
import { get } from './request'; import { get, del } from './request';
import type { ApiResponse } from '@/types/common'; import type { ApiResponse } from '@/types/common';
// 服务器返回的场景数据类型 // 服务器返回的场景数据类型
@@ -34,6 +34,30 @@ export interface Plan {
acquisitionCount: number; acquisitionCount: number;
} }
// 任务类型
export interface Task {
id: string;
name: string;
status: "running" | "paused" | "completed";
stats: {
devices: number;
acquired: number;
added: number;
};
lastUpdated: string;
executionTime: string;
nextExecutionTime: string;
trend: { date: string; customers: number }[];
}
// 计划详情类型
export interface PlanDetail {
apiKey: string;
textUrl: {
fullUrl: string;
};
}
/** /**
* 获取获客场景列表 * 获取获客场景列表
* *
@@ -68,6 +92,128 @@ export const fetchScenes = async (params: {
} }
}; };
/**
* 获取场景详情
*
* @param id 场景ID
* @returns 场景详情
*/
export const fetchSceneDetail = async (id: string | number): Promise<ApiResponse<SceneItem>> => {
try {
return await get<ApiResponse<SceneItem>>(`/v1/plan/scenes/${id}`);
} catch (error) {
console.error("Error fetching scene detail:", error);
return {
code: 500,
message: "获取场景详情失败",
data: null
};
}
};
/**
* 获取场景名称
*
* @param channel 场景标识
* @returns 场景名称
*/
export const fetchSceneName = async (channel: string): Promise<ApiResponse<{ name: string }>> => {
try {
return await get<ApiResponse<{ name: string }>>(`/v1/plan/scenes-detail?id=${channel}`);
} catch (error) {
console.error("Error fetching scene name:", error);
return {
code: 500,
message: "获取场景名称失败",
data: { name: channel }
};
}
};
/**
* 获取计划列表
*
* @param channel 场景标识
* @param page 页码
* @param pageSize 每页数量
* @returns 计划列表
*/
export const fetchPlanList = async (
channel: string,
page: number = 1,
pageSize: number = 10
): Promise<ApiResponse<{ list: Task[]; total: number }>> => {
try {
return await get<ApiResponse<{ list: Task[]; total: number }>>(
`/v1/plan/list?sceneId=${channel}&page=${page}&pageSize=${pageSize}`
);
} catch (error) {
console.error("Error fetching plan list:", error);
return {
code: 500,
message: "获取计划列表失败",
data: { list: [], total: 0 }
};
}
};
/**
* 复制计划
*
* @param planId 计划ID
* @returns 复制结果
*/
export const copyPlan = async (planId: string): Promise<ApiResponse<any>> => {
try {
return await get<ApiResponse<any>>(`/v1/plan/copy?planId=${planId}`);
} catch (error) {
console.error("Error copying plan:", error);
return {
code: 500,
message: "复制计划失败",
data: null
};
}
};
/**
* 删除计划
*
* @param planId 计划ID
* @returns 删除结果
*/
export const deletePlan = async (planId: string): Promise<ApiResponse<any>> => {
try {
return await del<ApiResponse<any>>(`/v1/plan/delete?planId=${planId}`);
} catch (error) {
console.error("Error deleting plan:", error);
return {
code: 500,
message: "删除计划失败",
data: null
};
}
};
/**
* 获取计划详情
*
* @param planId 计划ID
* @returns 计划详情
*/
export const fetchPlanDetail = async (planId: string): Promise<ApiResponse<PlanDetail>> => {
try {
return await get<ApiResponse<PlanDetail>>(`/v1/plan/detail?planId=${planId}`);
} catch (error) {
console.error("Error fetching plan detail:", error);
return {
code: 500,
message: "获取计划详情失败",
data: null
};
}
};
/** /**
* 将服务器返回的场景数据转换为前端展示需要的格式 * 将服务器返回的场景数据转换为前端展示需要的格式
* *
@@ -92,22 +238,3 @@ export const transformSceneItem = (item: SceneItem): Channel => {
} }
}; };
}; };
/**
* 获取场景详情
*
* @param id 场景ID
* @returns 场景详情
*/
export const fetchSceneDetail = async (id: string | number): Promise<ApiResponse<SceneItem>> => {
try {
return await get<ApiResponse<SceneItem>>(`/v1/plan/scenes/${id}`);
} catch (error) {
console.error("Error fetching scene detail:", error);
return {
code: 500,
message: "获取场景详情失败",
data: null
};
}
};

View File

@@ -16,33 +16,21 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import PageHeader from '@/components/PageHeader'; import PageHeader from '@/components/PageHeader';
import {
interface Task { fetchSceneName,
id: string; fetchPlanList,
name: string; copyPlan,
status: "running" | "paused" | "completed"; deletePlan,
stats: { fetchPlanDetail,
devices: number; type Task
acquired: number; } from '@/api/scenarios';
added: number;
};
lastUpdated: string;
executionTime: string;
nextExecutionTime: string;
trend: { date: string; customers: number }[];
reqConf?: {
device?: string[];
selectedDevices?: string[];
};
acquiredCount?: number;
addedCount?: number;
passRate?: number;
}
interface DeviceStats { interface DeviceStats {
active: number; active: number;
} }
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://yishi.com';
// API文档提示组件 // API文档提示组件
function ApiDocumentationTooltip() { function ApiDocumentationTooltip() {
return ( return (
@@ -65,7 +53,7 @@ export default function GongzhonghaoScenario() {
const navigate = useNavigate(); const navigate = useNavigate();
const { toast } = useToast(); const { toast } = useToast();
const channel = "gongzhonghao"; const channel = "gongzhonghao";
const [channelName, setChannelName] = useState<string>("公众号获客"); const [channelName, setChannelName] = useState<string>("");
// 1. tasks 初始值设为 [] // 1. tasks 初始值设为 []
const [tasks, setTasks] = useState<Task[]>([]); const [tasks, setTasks] = useState<Task[]>([]);
@@ -76,45 +64,32 @@ export default function GongzhonghaoScenario() {
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
useEffect(() => { useEffect(() => {
// 模拟API调用获取场景名称 fetchSceneName(channel)
setChannelName("公众号获客"); .then((res) => {
if (res.code === 200 && res.data?.name) {
setChannelName(res.data.name);
} else {
setChannelName(channel);
}
})
.catch(() => setChannelName(channel));
}, [channel]); }, [channel]);
// 抽出请求列表的函数 // 抽出请求列表的函数
const fetchTasks = () => { const fetchTasks = () => {
setLoading(true); setLoading(true);
setError(""); setError("");
// 模拟API调用 fetchPlanList(channel, page, pageSize)
setTimeout(() => { .then((res) => {
const mockTasks: Task[] = [ if (res.code === 200 && Array.isArray(res.data?.list)) {
{ setTasks(res.data.list);
id: "1", setTotal(res.data.total || 0);
name: "公众号私域引流计划", } else {
status: "running", setError(res.message || "接口返回异常");
stats: {
devices: 4,
acquired: 67,
added: 45,
},
lastUpdated: "2024-03-18 16:15",
executionTime: "2024-03-18 16:15",
nextExecutionTime: "预计20分钟后",
trend: Array.from({ length: 7 }, (_, i) => ({
date: `3月${String(i + 12)}`,
customers: Math.floor(Math.random() * 15) + 8,
})),
acquiredCount: 67,
addedCount: 45,
passRate: 67,
reqConf: {
selectedDevices: ["device1", "device2", "device3", "device4"]
} }
}, })
]; .catch((err: any) => setError(err?.message || "接口请求失败"))
setTasks(mockTasks); .finally(() => setLoading(false));
setTotal(1);
setLoading(false);
}, 1000);
}; };
useEffect(() => { useEffect(() => {
@@ -140,35 +115,60 @@ export default function GongzhonghaoScenario() {
const handleCopyPlan = (taskId: string) => { const handleCopyPlan = (taskId: string) => {
const taskToCopy = tasks.find((task) => task.id === taskId); const taskToCopy = tasks.find((task) => task.id === taskId);
if (!taskToCopy) return; if (!taskToCopy) return;
copyPlan(taskId)
// 模拟API调用 .then((res) => {
setTimeout(() => { if (res.code === 200) {
const newTask = {
...taskToCopy,
id: `${Date.now()}`,
name: `${taskToCopy.name} (副本)`,
status: "paused" as const,
};
setTasks([...tasks, newTask]);
toast({ toast({
title: "计划已复制", title: "计划已复制",
description: `已成功复制"${taskToCopy.name}"`, description: `已成功复制"${taskToCopy.name}"`,
variant: "default",
});
setPage(1);
fetchTasks();
} else {
toast({
title: "复制失败",
description: res.message || "复制计划失败,请重试",
variant: "destructive",
});
}
})
.catch((err: any) => {
toast({
title: "复制失败",
description: err?.message || "复制计划失败,请重试",
variant: "destructive",
});
}); });
}, 500);
}; };
const handleDeletePlan = (taskId: string) => { const handleDeletePlan = (taskId: string) => {
const taskToDelete = tasks.find((t) => t.id === taskId); const taskToDelete = tasks.find((t) => t.id === taskId);
if (!taskToDelete) return; if (!taskToDelete) return;
deletePlan(taskId)
// 模拟API调用 .then((res) => {
setTimeout(() => { if (res.code === 200) {
setTasks(tasks.filter((t) => t.id !== taskId)); setTasks(tasks.filter((t) => t.id !== taskId));
toast({ toast({
title: "计划已删除", title: "计划已删除",
description: `已成功删除"${taskToDelete.name}"`, description: `已成功删除"${taskToDelete.name}"`,
variant: "default",
});
} else {
toast({
title: "删除失败",
description: res.message || "删除计划失败,请重试",
variant: "destructive",
});
}
})
.catch((err: any) => {
toast({
title: "删除失败",
description: err?.message || "删除计划失败,请重试",
variant: "destructive",
});
}); });
}, 500);
}; };
const handleStatusChange = (taskId: string, newStatus: "running" | "paused") => { const handleStatusChange = (taskId: string, newStatus: "running" | "paused") => {
@@ -177,31 +177,50 @@ export default function GongzhonghaoScenario() {
toast({ toast({
title: newStatus === "running" ? "计划已启动" : "计划已暂停", title: newStatus === "running" ? "计划已启动" : "计划已暂停",
description: `${newStatus === "running" ? "启动" : "暂停"}获客计划`, description: `${newStatus === "running" ? "启动" : "暂停"}获客计划`,
variant: "default",
}); });
}; };
const handleOpenApiSettings = async (taskId: string) => { const handleOpenApiSettings = async (taskId: string) => {
const task = tasks.find((t) => t.id === taskId); const task = tasks.find((t) => t.id === taskId);
if (!task) return; if (task) {
try {
// 模拟API调用获取接口设置 const res = await fetchPlanDetail(taskId);
const mockApiSettings = { if (res.code === 200 && res.data) {
apiKey: `api_key_${taskId}_${Date.now()}`, setCurrentApiSettings({
webhookUrl: `https://api.example.com/webhook/${taskId}`, apiKey: res.data.apiKey || '', // 使用接口返回的 API 密钥
taskId: taskId, webhookUrl: `${API_BASE_URL}/v1/api/scenarios`,
fullUrl: `name=测试用户&phone=13800138000&source=公众号&remark=测试备注` fullUrl: res.data.textUrl.fullUrl || '',
}; taskId,
});
setCurrentApiSettings(mockApiSettings);
setShowApiDialog(true); setShowApiDialog(true);
} else {
toast({
title: "获取 API 密钥失败",
description: res.message || "请重试",
variant: "destructive",
});
}
} catch (err: any) {
toast({
title: "获取 API 密钥失败",
description: err?.message || "请重试",
variant: "destructive",
});
}
}
}; };
const handleCopyApiUrl = (url: string, withParams = false) => { const handleCopyApiUrl = (url: string, withParams = false) => {
const textToCopy = withParams ? `${url}?${currentApiSettings.fullUrl}` : url; let copyUrl = url;
navigator.clipboard.writeText(textToCopy); if (withParams) {
copyUrl = `${url}?name=张三&phone=13800138000&source=外部系统&remark=测试数据`;
}
navigator.clipboard.writeText(copyUrl);
toast({ toast({
title: "已复制", title: "已复制",
description: "接口地址已复制到剪贴板", description: withParams ? "接口地址(含示例参数)已复制到剪贴板" : "接口地址已复制到剪贴板",
variant: "default",
}); });
}; };
@@ -235,7 +254,7 @@ export default function GongzhonghaoScenario() {
<ScenarioAcquisitionCard <ScenarioAcquisitionCard
task={task} task={task}
channel={channel} channel={channel}
onEdit={handleEditPlan} onEdit={() => handleEditPlan(task.id)}
onCopy={handleCopyPlan} onCopy={handleCopyPlan}
onDelete={handleDeletePlan} onDelete={handleDeletePlan}
onStatusChange={handleStatusChange} onStatusChange={handleStatusChange}
@@ -308,6 +327,7 @@ export default function GongzhonghaoScenario() {
toast({ toast({
title: "已复制", title: "已复制",
description: "API密钥已复制到剪贴板", description: "API密钥已复制到剪贴板",
variant: "default",
}); });
}} }}
> >