feat:提交同步
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user