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';
// 服务器返回的场景数据类型
@@ -34,6 +34,30 @@ export interface Plan {
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
};
}
};
/**
* 将服务器返回的场景数据转换为前端展示需要的格式
*
@@ -91,23 +237,4 @@ export const transformSceneItem = (item: SceneItem): Channel => {
growth: growthPercent
}
};
};
/**
* 获取场景详情
*
* @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 { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import PageHeader from '@/components/PageHeader';
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 }[];
reqConf?: {
device?: string[];
selectedDevices?: string[];
};
acquiredCount?: number;
addedCount?: number;
passRate?: number;
}
import {
fetchSceneName,
fetchPlanList,
copyPlan,
deletePlan,
fetchPlanDetail,
type Task
} from '@/api/scenarios';
interface DeviceStats {
active: number;
}
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://yishi.com';
// API文档提示组件
function ApiDocumentationTooltip() {
return (
@@ -65,7 +53,7 @@ export default function GongzhonghaoScenario() {
const navigate = useNavigate();
const { toast } = useToast();
const channel = "gongzhonghao";
const [channelName, setChannelName] = useState<string>("公众号获客");
const [channelName, setChannelName] = useState<string>("");
// 1. tasks 初始值设为 []
const [tasks, setTasks] = useState<Task[]>([]);
@@ -76,45 +64,32 @@ export default function GongzhonghaoScenario() {
const [total, setTotal] = useState(0);
useEffect(() => {
// 模拟API调用获取场景名称
setChannelName("公众号获客");
fetchSceneName(channel)
.then((res) => {
if (res.code === 200 && res.data?.name) {
setChannelName(res.data.name);
} else {
setChannelName(channel);
}
})
.catch(() => setChannelName(channel));
}, [channel]);
// 抽出请求列表的函数
const fetchTasks = () => {
setLoading(true);
setError("");
// 模拟API调用
setTimeout(() => {
const mockTasks: Task[] = [
{
id: "1",
name: "公众号私域引流计划",
status: "running",
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"]
}
},
];
setTasks(mockTasks);
setTotal(1);
setLoading(false);
}, 1000);
fetchPlanList(channel, page, pageSize)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data?.list)) {
setTasks(res.data.list);
setTotal(res.data.total || 0);
} else {
setError(res.message || "接口返回异常");
}
})
.catch((err: any) => setError(err?.message || "接口请求失败"))
.finally(() => setLoading(false));
};
useEffect(() => {
@@ -140,35 +115,60 @@ export default function GongzhonghaoScenario() {
const handleCopyPlan = (taskId: string) => {
const taskToCopy = tasks.find((task) => task.id === taskId);
if (!taskToCopy) return;
// 模拟API调用
setTimeout(() => {
const newTask = {
...taskToCopy,
id: `${Date.now()}`,
name: `${taskToCopy.name} (副本)`,
status: "paused" as const,
};
setTasks([...tasks, newTask]);
toast({
title: "计划已复制",
description: `已成功复制"${taskToCopy.name}"`,
copyPlan(taskId)
.then((res) => {
if (res.code === 200) {
toast({
title: "计划已复制",
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 taskToDelete = tasks.find((t) => t.id === taskId);
if (!taskToDelete) return;
// 模拟API调用
setTimeout(() => {
setTasks(tasks.filter((t) => t.id !== taskId));
toast({
title: "计划已删除",
description: `已成功删除"${taskToDelete.name}"`,
deletePlan(taskId)
.then((res) => {
if (res.code === 200) {
setTasks(tasks.filter((t) => t.id !== taskId));
toast({
title: "计划已删除",
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") => {
@@ -177,31 +177,50 @@ export default function GongzhonghaoScenario() {
toast({
title: newStatus === "running" ? "计划已启动" : "计划已暂停",
description: `${newStatus === "running" ? "启动" : "暂停"}获客计划`,
variant: "default",
});
};
const handleOpenApiSettings = async (taskId: string) => {
const task = tasks.find((t) => t.id === taskId);
if (!task) return;
// 模拟API调用获取接口设置
const mockApiSettings = {
apiKey: `api_key_${taskId}_${Date.now()}`,
webhookUrl: `https://api.example.com/webhook/${taskId}`,
taskId: taskId,
fullUrl: `name=测试用户&phone=13800138000&source=公众号&remark=测试备注`
};
setCurrentApiSettings(mockApiSettings);
setShowApiDialog(true);
if (task) {
try {
const res = await fetchPlanDetail(taskId);
if (res.code === 200 && res.data) {
setCurrentApiSettings({
apiKey: res.data.apiKey || '', // 使用接口返回的 API 密钥
webhookUrl: `${API_BASE_URL}/v1/api/scenarios`,
fullUrl: res.data.textUrl.fullUrl || '',
taskId,
});
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 textToCopy = withParams ? `${url}?${currentApiSettings.fullUrl}` : url;
navigator.clipboard.writeText(textToCopy);
let copyUrl = url;
if (withParams) {
copyUrl = `${url}?name=张三&phone=13800138000&source=外部系统&remark=测试数据`;
}
navigator.clipboard.writeText(copyUrl);
toast({
title: "已复制",
description: "接口地址已复制到剪贴板",
description: withParams ? "接口地址(含示例参数)已复制到剪贴板" : "接口地址已复制到剪贴板",
variant: "default",
});
};
@@ -235,7 +254,7 @@ export default function GongzhonghaoScenario() {
<ScenarioAcquisitionCard
task={task}
channel={channel}
onEdit={handleEditPlan}
onEdit={() => handleEditPlan(task.id)}
onCopy={handleCopyPlan}
onDelete={handleDeletePlan}
onStatusChange={handleStatusChange}
@@ -308,6 +327,7 @@ export default function GongzhonghaoScenario() {
toast({
title: "已复制",
description: "API密钥已复制到剪贴板",
variant: "default",
});
}}
>
@@ -430,4 +450,4 @@ export default function GongzhonghaoScenario() {
</Dialog>
</div>
);
}
}