diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx index 18ef6ce4..74cec1e1 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx @@ -23,12 +23,16 @@ import { SendOutlined, } from "@ant-design/icons"; import styles from "./PushTaskModal.module.scss"; -import { - useCustomerStore, -} from "@/store/module/weChat/customer"; +import { useCustomerStore } from "@/store/module/weChat/customer"; import { getContactList, getGroupList } from "@/pages/pc/ckbox/weChat/api"; -export type PushType = "friend-message" | "group-message" | "group-announcement"; +const DEFAULT_FRIEND_INTERVAL: [number, number] = [3, 10]; +const DEFAULT_MESSAGE_INTERVAL: [number, number] = [1, 3]; + +export type PushType = + | "friend-message" + | "group-message" + | "group-announcement"; interface PushTaskModalProps { visible: boolean; @@ -67,8 +71,12 @@ const PushTaskModal: React.FC = ({ const [selectedAccounts, setSelectedAccounts] = useState([]); const [selectedContacts, setSelectedContacts] = useState([]); const [messageContent, setMessageContent] = useState(""); - const [friendInterval, setFriendInterval] = useState(10); - const [messageInterval, setMessageInterval] = useState(1); + const [friendInterval, setFriendInterval] = useState<[number, number]>([ + ...DEFAULT_FRIEND_INTERVAL, + ]); + const [messageInterval, setMessageInterval] = useState<[number, number]>([ + ...DEFAULT_MESSAGE_INTERVAL, + ]); const [selectedTag, setSelectedTag] = useState(""); const [aiRewriteEnabled, setAiRewriteEnabled] = useState(false); const [aiPrompt, setAiPrompt] = useState(""); @@ -120,8 +128,8 @@ const PushTaskModal: React.FC = ({ setSelectedAccounts([]); setSelectedContacts([]); setMessageContent(""); - setFriendInterval(10); - setMessageInterval(1); + setFriendInterval([...DEFAULT_FRIEND_INTERVAL]); + setMessageInterval([...DEFAULT_MESSAGE_INTERVAL]); setSelectedTag(""); setAiRewriteEnabled(false); setAiPrompt(""); @@ -270,7 +278,9 @@ const PushTaskModal: React.FC = ({ setCurrentStep(2); } else if (currentStep === 2) { if (selectedContacts.length === 0) { - message.warning(`请至少选择一个${pushType === "friend-message" ? "好友" : "群"}`); + message.warning( + `请至少选择一个${pushType === "friend-message" ? "好友" : "群"}`, + ); return; } setCurrentStep(3); @@ -343,7 +353,9 @@ const PushTaskModal: React.FC = ({
{filteredAccounts.length > 0 ? ( filteredAccounts.map(account => { - const isSelected = selectedAccounts.some(a => a.id === account.id); + const isSelected = selectedAccounts.some( + a => a.id === account.id, + ); return (
= ({ size={48} style={{ backgroundColor: "#1890ff" }} > - {!account.avatar && (account.nickname || account.name || "").charAt(0)} + {!account.avatar && + (account.nickname || account.name || "").charAt(0)}
{account.nickname || account.name || "未知"} @@ -570,10 +583,7 @@ const PushTaskModal: React.FC = ({
- + AI智能话术改写 {aiRewriteEnabled && ( = ({
间隔时间(秒) setFriendInterval(value as [number, number])} style={{ flex: 1, margin: "0 16px" }} /> - {friendInterval} - 20 + + {friendInterval[0]} - {friendInterval[1]} +
@@ -612,13 +625,18 @@ const PushTaskModal: React.FC = ({
间隔时间(秒) + setMessageInterval(value as [number, number]) + } style={{ flex: 1, margin: "0 16px" }} /> - {messageInterval} - 12 + + {messageInterval[0]} - {messageInterval[1]} +
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts new file mode 100644 index 00000000..34fa4bb4 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts @@ -0,0 +1,6 @@ +import request from "@/api/request"; + +// 获取客服列表 +export function queryWorkbenchCreate(params) { + return request("/v1/workbench/create", params, "POST"); +} diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx index 8de9fce1..93bc0d31 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx @@ -24,6 +24,8 @@ import { getContactList, getGroupList } from "@/pages/pc/ckbox/weChat/api"; import styles from "../../index.module.scss"; import { ContactItem, PushType } from "../../types"; +import PoolSelection from "@/components/PoolSelection"; +import type { PoolSelectionItem } from "@/components/PoolSelection/data"; interface ContactFilterValues { includeTags: string[]; @@ -61,6 +63,8 @@ interface StepSelectContactsProps { selectedAccounts: any[]; selectedContacts: ContactItem[]; onChange: (contacts: ContactItem[]) => void; + selectedTrafficPools: PoolSelectionItem[]; + onTrafficPoolsChange: (pools: PoolSelectionItem[]) => void; } const StepSelectContacts: React.FC = ({ @@ -68,6 +72,8 @@ const StepSelectContacts: React.FC = ({ selectedAccounts, selectedContacts, onChange, + selectedTrafficPools, + onTrafficPoolsChange, }) => { const [contactsData, setContactsData] = useState([]); const [loadingContacts, setLoadingContacts] = useState(false); @@ -415,6 +421,13 @@ const StepSelectContacts: React.FC = ({ allowClear />
+
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/api.ts new file mode 100644 index 00000000..f12513c0 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/api.ts @@ -0,0 +1,31 @@ +import request from "@/api/request"; +// 创建内容库参数 +export interface CreateContentLibraryParams { + name: string; + sourceType: number; + sourceFriends?: string[]; + sourceGroups?: string[]; + keywordInclude?: string[]; + keywordExclude?: string[]; + aiPrompt?: string; + timeEnabled?: number; + timeStart?: string; + timeEnd?: string; +} + +// 创建内容库 +export function createContentLibrary( + params: CreateContentLibraryParams, +): Promise { + return request("/v1/content/library/create", params, "POST"); +} + +// 删除内容库 +export function deleteContentLibrary(params: { id: number }) { + return request(`/v1/content/library/update`, params, "DELETE"); +} + +// 智能话术改写 +export function aiEditContent(params: { aiPrompt: string; content: string }) { + return request(`/v1/content/library/aiEditContent`, params, "GET"); +} diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss index 191204f6..12ee6679 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss @@ -254,6 +254,7 @@ .aiRewriteSection { display: flex; + justify-content: space-between; align-items: center; margin-bottom: 8px; gap: 12px; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx index 3a400aec..8a19ad74 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import { Button, Checkbox, @@ -18,6 +18,11 @@ import { ContactItem, ScriptGroup } from "../../types"; import InputMessage from "./InputMessage/InputMessage"; import ContentLibrarySelector from "./ContentLibrarySelector"; import type { ContentItem } from "@/components/ContentSelection/data"; +import { + createContentLibrary, + deleteContentLibrary, + type CreateContentLibraryParams, +} from "./api"; interface StepSendMessageProps { selectedAccounts: any[]; @@ -25,10 +30,10 @@ interface StepSendMessageProps { targetLabel: string; messageContent: string; onMessageContentChange: (value: string) => void; - friendInterval: number; - onFriendIntervalChange: (value: number) => void; - messageInterval: number; - onMessageIntervalChange: (value: number) => void; + friendInterval: [number, number]; + onFriendIntervalChange: (value: [number, number]) => void; + messageInterval: [number, number]; + onMessageIntervalChange: (value: [number, number]) => void; selectedTag: string; onSelectedTagChange: (value: string) => void; aiRewriteEnabled: boolean; @@ -74,6 +79,9 @@ const StepSendMessage: React.FC = ({ selectedContentLibraries, onSelectedContentLibrariesChange, }) => { + const [savingScriptGroup, setSavingScriptGroup] = useState(false); + const [deletingGroupIds, setDeletingGroupIds] = useState([]); + const handleAddMessage = useCallback( (content?: string, showSuccess?: boolean) => { const finalContent = (content ?? messageContent).trim(); @@ -103,24 +111,57 @@ const StepSendMessage: React.FC = ({ [currentScriptMessages, onCurrentScriptMessagesChange], ); - const handleSaveScriptGroup = useCallback(() => { + const handleSaveScriptGroup = useCallback(async () => { + if (savingScriptGroup) { + return; + } if (currentScriptMessages.length === 0) { antdMessage.warning("请先添加消息内容"); return; } const groupName = currentScriptName.trim() || `话术组${savedScriptGroups.length + 1}`; - const newGroup: ScriptGroup = { - id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + const messages = [...currentScriptMessages]; + const params: CreateContentLibraryParams = { name: groupName, - messages: currentScriptMessages, + sourceType: 1, + keywordInclude: messages, }; - onSavedScriptGroupsChange([...savedScriptGroups, newGroup]); - onCurrentScriptMessagesChange([]); - onCurrentScriptNameChange(""); - onMessageContentChange(""); - antdMessage.success("已保存为话术组"); + const trimmedPrompt = aiPrompt.trim(); + if (aiRewriteEnabled && trimmedPrompt) { + params.aiPrompt = trimmedPrompt; + } + let hideLoading: ReturnType | undefined; + try { + setSavingScriptGroup(true); + hideLoading = antdMessage.loading("正在保存话术组...", 0); + const response = await createContentLibrary(params); + hideLoading?.(); + const responseId = + response?.id ?? response?.data?.id ?? response?.libraryId; + const newGroup: ScriptGroup = { + id: + responseId !== undefined + ? String(responseId) + : `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + name: groupName, + messages, + }; + onSavedScriptGroupsChange([...savedScriptGroups, newGroup]); + onCurrentScriptMessagesChange([]); + onCurrentScriptNameChange(""); + onMessageContentChange(""); + antdMessage.success("已保存为话术组"); + } catch (error) { + hideLoading?.(); + console.error("保存话术组失败:", error); + antdMessage.error("保存失败,请稍后重试"); + } finally { + setSavingScriptGroup(false); + } }, [ + aiPrompt, + aiRewriteEnabled, currentScriptMessages, currentScriptName, onCurrentScriptMessagesChange, @@ -128,6 +169,7 @@ const StepSendMessage: React.FC = ({ onMessageContentChange, onSavedScriptGroupsChange, savedScriptGroups, + savingScriptGroup, ]); const handleApplyGroup = useCallback( @@ -145,23 +187,47 @@ const StepSendMessage: React.FC = ({ ); const handleDeleteGroup = useCallback( - (groupId: string) => { - const nextGroups = savedScriptGroups.filter( - group => group.id !== groupId, - ); - onSavedScriptGroupsChange(nextGroups); - if (selectedScriptGroupIds.includes(groupId)) { - const nextSelected = selectedScriptGroupIds.filter( - id => id !== groupId, - ); - onSelectedScriptGroupIdsChange(nextSelected); + async (groupId: string) => { + if (deletingGroupIds.includes(groupId)) { + return; + } + const numericGroupId = Number(groupId); + if (Number.isNaN(numericGroupId)) { + antdMessage.error("无法删除:缺少有效的内容库ID"); + return; + } + let hideLoading: ReturnType | undefined; + try { + setDeletingGroupIds(prev => [...prev, groupId]); + hideLoading = antdMessage.loading("正在删除话术组...", 0); + await deleteContentLibrary({ id: numericGroupId }); + hideLoading?.(); + const nextGroups = savedScriptGroups.filter( + group => group.id !== groupId, + ); + onSavedScriptGroupsChange(nextGroups); + if (selectedScriptGroupIds.includes(groupId)) { + const nextSelected = selectedScriptGroupIds.filter( + id => id !== groupId, + ); + onSelectedScriptGroupIdsChange(nextSelected); + } + antdMessage.success("已删除话术组"); + } catch (error) { + hideLoading?.(); + console.error("删除话术组失败:", error); + antdMessage.error("删除失败,请稍后重试"); + } finally { + setDeletingGroupIds(prev => + prev.filter(deletingId => deletingId !== groupId), + ); } - antdMessage.success("已删除话术组"); }, [ + deletingGroupIds, onSavedScriptGroupsChange, - savedScriptGroups, onSelectedScriptGroupIdsChange, + savedScriptGroups, selectedScriptGroupIds, ], ); @@ -192,7 +258,8 @@ const StepSendMessage: React.FC = ({ type="primary" icon={} onClick={handleSaveScriptGroup} - disabled={currentScriptMessages.length === 0} + disabled={currentScriptMessages.length === 0 || savingScriptGroup} + loading={savingScriptGroup} > 保存为话术组 @@ -279,6 +346,8 @@ const StepSendMessage: React.FC = ({ icon={} className={styles.actionButton} onClick={() => handleDeleteGroup(group.id)} + loading={deletingGroupIds.includes(group.id)} + disabled={deletingGroupIds.includes(group.id)} />
@@ -307,16 +376,19 @@ const StepSendMessage: React.FC = ({ checked={aiRewriteEnabled} onChange={onAiRewriteToggle} /> - AI智能话术改写 +
AI智能话术改写
+
+ {aiRewriteEnabled && ( + onAiPromptChange(event.target.value)} + className={styles.aiRewriteInput} + /> + )} +
- {aiRewriteEnabled && ( - onAiPromptChange(event.target.value)} - className={styles.aiRewriteInput} - /> - )} +
@@ -350,13 +427,18 @@ const StepSendMessage: React.FC = ({
间隔时间(秒) onMessageIntervalChange(value as number)} + onChange={value => + onMessageIntervalChange(value as [number, number]) + } style={{ flex: 1, margin: "0 16px" }} /> - {messageInterval} - 12 + + {messageInterval[0]} - {messageInterval[1]} +
diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx index 620e7523..d682b46c 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx @@ -17,6 +17,47 @@ import StepSendMessage from "./components/StepSendMessage"; import { ContactItem, PushType, ScriptGroup } from "./types"; import StepIndicator from "@/components/StepIndicator"; import type { ContentItem } from "@/components/ContentSelection/data"; +import type { PoolSelectionItem } from "@/components/PoolSelection/data"; +import { queryWorkbenchCreate } from "./api"; + +const DEFAULT_FRIEND_INTERVAL: [number, number] = [3, 10]; +const DEFAULT_MESSAGE_INTERVAL: [number, number] = [1, 3]; + +const DEFAULT_TIME_RANGE: Record< + PushType, + { startTime: string; endTime: string } +> = { + "friend-message": { startTime: "10:00", endTime: "22:00" }, + "group-message": { startTime: "09:00", endTime: "20:00" }, + "group-announcement": { startTime: "08:30", endTime: "18:30" }, +}; + +const DEFAULT_PUSH_ORDER: Record = { + "friend-message": 1, + "group-message": 1, + "group-announcement": 2, +}; + +const DEFAULT_MAX_PER_DAY: Record = { + "friend-message": 150, + "group-message": 200, + "group-announcement": 80, +}; + +const DEFAULT_AUTO_START: Record = { + "friend-message": 1, + "group-message": 1, + "group-announcement": 0, +}; + +const DEFAULT_PUSH_TYPE: Record = { + "friend-message": 0, + "group-message": 0, + "group-announcement": 1, +}; + +const isValidNumber = (value: unknown): value is number => + typeof value === "number" && Number.isFinite(value); const CreatePushTask: React.FC = () => { const navigate = useNavigate(); @@ -44,11 +85,19 @@ const CreatePushTask: React.FC = () => { const [selectedContentLibraries, setSelectedContentLibraries] = useState< ContentItem[] >([]); - const [friendInterval, setFriendInterval] = useState(10); - const [messageInterval, setMessageInterval] = useState(1); + const [friendInterval, setFriendInterval] = useState<[number, number]>([ + ...DEFAULT_FRIEND_INTERVAL, + ]); + const [messageInterval, setMessageInterval] = useState<[number, number]>([ + ...DEFAULT_MESSAGE_INTERVAL, + ]); const [selectedTag, setSelectedTag] = useState(""); const [aiRewriteEnabled, setAiRewriteEnabled] = useState(false); const [aiPrompt, setAiPrompt] = useState(""); + const [creatingTask, setCreatingTask] = useState(false); + const [selectedTrafficPools, setSelectedTrafficPools] = useState< + PoolSelectionItem[] + >([]); const customerList = useCustomerStore(state => state.customerList); @@ -131,33 +180,206 @@ const CreatePushTask: React.FC = () => { setSelectedAccounts([]); }; - const handleSend = () => { + const handleSend = async () => { + if (creatingTask) { + return; + } const selectedGroups = savedScriptGroups.filter(group => selectedScriptGroupIds.includes(group.id), ); - if (currentScriptMessages.length === 0 && selectedGroups.length === 0) { - message.warning("请先添加话术内容或选择话术组"); + if ( + currentScriptMessages.length === 0 && + selectedGroups.length === 0 && + selectedContentLibraries.length === 0 + ) { + message.warning("请添加话术内容、选择话术组或内容库"); return; } - // TODO: 实现发送逻辑 - console.log("发送推送", { - pushType: validPushType, - accounts: selectedAccounts, - contacts: selectedContacts, - currentScript: { - name: currentScriptName, - messages: currentScriptMessages, - }, - selectedScriptGroups: selectedGroups, - friendInterval, - messageInterval, - selectedTag, - aiRewriteEnabled, - aiPrompt, - selectedContentLibraries, - }); - message.success("推送任务已创建"); - navigate("/pc/powerCenter/message-push-assistant"); + const manualMessages = currentScriptMessages + .map(item => item.trim()) + .filter(Boolean); + if (validPushType === "group-announcement" && manualMessages.length === 0) { + message.warning("请先填写公告内容"); + return; + } + const toNumberId = (value: unknown) => { + const numeric = Number(value); + return Number.isFinite(numeric) && !Number.isNaN(numeric) + ? numeric + : null; + }; + const contentGroupIds = Array.from( + new Set( + [ + ...selectedContentLibraries + .map(item => toNumberId(item?.id)) + .filter((id): id is number => id !== null), + ...selectedScriptGroupIds + .map(id => toNumberId(id)) + .filter((id): id is number => id !== null), + ].filter((id): id is number => id !== null), + ), + ); + if ( + manualMessages.length === 0 && + selectedGroups.length === 0 && + contentGroupIds.length === 0 + ) { + message.warning("缺少有效的话术内容,请重新检查"); + return; + } + const ownerWechatIds = Array.from( + new Set( + selectedAccounts + .map(account => toNumberId(account?.id)) + .filter((id): id is number => id !== null), + ), + ); + if (ownerWechatIds.length === 0) { + message.error("缺少有效的推送账号信息"); + return; + } + const selectedContactIds = Array.from( + new Set( + selectedContacts.map(contact => contact?.id).filter(isValidNumber), + ), + ); + if (selectedContactIds.length === 0) { + message.error("缺少有效的推送对象"); + return; + } + const friendIntervalMin = friendInterval[0]; + const friendIntervalMax = friendInterval[1]; + const messageIntervalMin = messageInterval[0]; + const messageIntervalMax = messageInterval[1]; + const trafficPoolIds = selectedTrafficPools + .map(pool => pool.id) + .filter( + id => id !== undefined && id !== null && String(id).trim() !== "", + ); + const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType]; + const maxPerDay = + selectedContacts.length > 0 + ? selectedContacts.length + : DEFAULT_MAX_PER_DAY[validPushType]; + const pushOrder = DEFAULT_PUSH_ORDER[validPushType]; + const normalizedPostPushTags = + selectedTag.trim().length > 0 + ? [ + toNumberId(selectedTag) !== null + ? (toNumberId(selectedTag) as number) + : selectedTag, + ] + : []; + const taskName = + currentScriptName.trim() || + selectedGroups[0]?.name || + (manualMessages[0] ? manualMessages[0].slice(0, 20) : "") || + `推送任务-${Date.now()}`; + const deviceGroupIds = Array.from( + new Set( + selectedAccounts + .map(account => toNumberId(account?.currentDeviceId)) + .filter((id): id is number => id !== null), + ), + ); + if (validPushType === "friend-message" && deviceGroupIds.length === 0) { + message.error("缺少有效的推送设备分组"); + return; + } + + const basePayload: Record = { + name: taskName, + type: 3, + autoStart: DEFAULT_AUTO_START[validPushType], + status: 1, + pushType: DEFAULT_PUSH_TYPE[validPushType], + targetType: validPushType === "friend-message" ? 2 : 1, + groupPushSubType: validPushType === "group-announcement" ? 2 : 1, + startTime, + endTime, + maxPerDay, + pushOrder, + friendIntervalMin, + friendIntervalMax, + messageIntervalMin, + messageIntervalMax, + isRandomTemplate: selectedScriptGroupIds.length > 1 ? 1 : 0, + contentGroups: contentGroupIds, + postPushTags: normalizedPostPushTags, + ownerWechatIds, + enableAiRewrite: aiRewriteEnabled ? 1 : 0, + }; + if (trafficPoolIds.length > 0) { + basePayload.trafficPools = trafficPoolIds; + } + if (validPushType === "friend-message") { + basePayload.isLoop = 0; + basePayload.deviceGroups = deviceGroupIds; + } + if (manualMessages.length > 0) { + basePayload.manualMessages = manualMessages; + if (currentScriptName.trim()) { + basePayload.manualScriptName = currentScriptName.trim(); + } + } + if (selectedScriptGroupIds.length > 0) { + basePayload.selectedScriptGroupIds = selectedScriptGroupIds; + } + if (aiRewriteEnabled && aiPrompt.trim()) { + basePayload.aiRewritePrompt = aiPrompt.trim(); + } + if (selectedGroups.length > 0) { + basePayload.scriptGroups = selectedGroups.map(group => ({ + id: group.id, + name: group.name, + messages: group.messages, + })); + } + if (validPushType === "friend-message") { + basePayload.wechatFriends = Array.from( + new Set( + selectedContacts + .map(contact => toNumberId(contact?.id)) + .filter((id): id is number => id !== null), + ), + ); + basePayload.targetType = 2; + } else { + const groupIds = Array.from( + new Set( + selectedContacts + .map(contact => toNumberId(contact.groupId ?? contact.id)) + .filter((id): id is number => id !== null), + ), + ); + basePayload.wechatGroups = groupIds; + basePayload.groupPushSubType = + validPushType === "group-announcement" ? 2 : 1; + basePayload.targetType = 1; + if (validPushType === "group-announcement") { + basePayload.announcementContent = manualMessages.join("\n"); + } + } + let hideLoading: ReturnType | undefined; + try { + setCreatingTask(true); + hideLoading = message.loading("正在创建推送任务...", 0); + await queryWorkbenchCreate(basePayload); + hideLoading?.(); + message.success("推送任务已创建"); + navigate("/pc/powerCenter/message-push-assistant"); + } catch (error) { + hideLoading?.(); + console.error("创建推送任务失败:", error); + const errorMessage = + (error as any)?.message || + (error as any)?.response?.data?.message || + "创建推送任务失败,请稍后重试"; + message.error(errorMessage); + } finally { + setCreatingTask(false); + } }; return ( @@ -242,6 +464,8 @@ const CreatePushTask: React.FC = () => { type="primary" icon={} onClick={handleSend} + loading={creatingTask} + disabled={creatingTask} > 一键发送 @@ -266,6 +490,8 @@ const CreatePushTask: React.FC = () => { selectedAccounts={selectedAccounts} selectedContacts={selectedContacts} onChange={setSelectedContacts} + selectedTrafficPools={selectedTrafficPools} + onTrafficPoolsChange={setSelectedTrafficPools} /> )} {currentStep === 3 && ( diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt new file mode 100644 index 00000000..df1db7a1 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt @@ -0,0 +1,79 @@ +帮我对接数据,以下是传参实例,三种模式都是同一界面的。 + +群发助手传参实例 +{ + "name": "群群发-新品宣传", // 任务名称 + "type": 3, // 工作台类型:3=群消息推送 + "autoStart": 1, // 保存后自动启动 + "status": 1, // 是否启用 + "pushType": 0, // 推送方式:0=定时,1=立即 + "targetType": 1, // 目标类型:1=群推送 + "groupPushSubType": 1, // 群推送子类型:1=群群发,2=群公告 + "startTime": "09:00", // 推送起始时间 + "endTime": "20:00", // 推送结束时间 + "maxPerDay": 200, // 每日最大推送群数 + "pushOrder": 1, // 推送顺序:1=最早优先,2=最新优先 + "wechatGroups": [102, 205, 318], // 选择的微信群 ID 列表 + "contentGroups": [11, 12], // 关联内容库 ID 列表 + "friendIntervalMin": 10, // 群间最小间隔(秒) + "friendIntervalMax": 25, // 群间最大间隔(秒) + "messageIntervalMin": 2, // 同一群消息间最小间隔(秒) + "messageIntervalMax": 6, // 同一群消息间最大间隔(秒) + "isRandomTemplate": 1, // 是否随机选择话术模板 + "postPushTags": [301, 302], // 推送完成后打的标签 + ownerWechatIds:[123123,1231231] //客服id +} + +//群公告传参实例 +{ + "name": "群公告-双11活动", // 任务名称 + "type": 3, // 群消息推送 + "autoStart": 0, // 不自动启动 + "status": 1, // 启用 + "pushType": 1, // 立即推送 + "targetType": 1, // 群推送 + "groupPushSubType": 2, // 群公告 + "startTime": "08:30", // 开始时间 + "endTime": "18:30", // 结束时间 + "maxPerDay": 80, // 每日最大公告数 + "pushOrder": 2, // 最新优先 + "wechatGroups": [5021, 5026], // 公告目标群 + "announcementContent": "…", // 公告正文 + "enableAiRewrite": 1, // 启用 AI 改写 + "aiRewritePrompt": "保持活泼口吻…", // AI 改写提示词 + "contentGroups": [21], // 关联内容库 + "friendIntervalMin": 15, // 群间最小间隔 + "friendIntervalMax": 30, // 群间最大间隔 + "messageIntervalMin": 3, // 消息间最小间隔 + "messageIntervalMax": 9, // 消息间最大间隔 + "isRandomTemplate": 0, // 不随机模板 + "postPushTags": [], // 推送后标签 + ownerWechatIds:[123123,1231231] //客服id +} + +//好友传参实例 +{ + "name": "好友私聊-新客转化", // 任务名称 + "type": 3, // 群消息推送 + "autoStart": 1, // 自动启动 + "status": 1, // 启用 + "pushType": 0, // 定时推送 + "targetType": 2, // 目标类型:2=好友推送 + "groupPushSubType": 1, // 固定为群群发(好友推送不支持公告) + "startTime": "10:00", // 开始时间 + "endTime": "22:00", // 结束时间 + "maxPerDay": 150, // 每日最大推送好友数 + "pushOrder": 1, // 最早优先 + "wechatFriends": ["12312"], // 指定好友列表(可为空数组) + "deviceGroups": [9001, 9002], // 必选:推送设备分组 ID + "contentGroups": [41, 42], // 话术内容库 + "friendIntervalMin": 12, // 好友间最小间隔 + "friendIntervalMax": 28, // 好友间最大间隔 + "messageIntervalMin": 4, // 消息间最小间隔 + "messageIntervalMax": 10, // 消息间最大间隔 + "isRandomTemplate": 1, // 随机话术 + "postPushTags": [501], // 推送后标签 + ownerWechatIds:[123123,1231231] //客服id +} + +请求接口是 queryWorkbenchCreate diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index e1dfa5ea..f6deae3d 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -7,6 +7,7 @@ * 4. 提供回调机制通知组件更新 */ +import Dexie from "dexie"; import { db, chatSessionService, ChatSession } from "../db"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; @@ -243,7 +244,9 @@ export class MessageManager { "userId", userId, )) as ChatSession[]; - const localSessionMap = new Map(localSessions.map(s => [s.id, s])); + const localSessionMap = new Map( + localSessions.map(session => [session.serverId, session]), + ); // 2. 转换服务器数据为统一格式 const serverSessions: ChatSession[] = []; @@ -264,16 +267,18 @@ export class MessageManager { serverSessions.push(...groups); } - const serverSessionMap = new Map(serverSessions.map(s => [s.id, s])); + const serverSessionMap = new Map( + serverSessions.map(session => [session.serverId, session]), + ); // 3. 计算差异 const toAdd: ChatSession[] = []; const toUpdate: ChatSession[] = []; - const toDelete: number[] = []; + const toDelete: string[] = []; // 检查新增和更新 for (const serverSession of serverSessions) { - const localSession = localSessionMap.get(serverSession.id); + const localSession = localSessionMap.get(serverSession.serverId); if (!localSession) { toAdd.push(serverSession); @@ -286,8 +291,8 @@ export class MessageManager { // 检查删除 for (const localSession of localSessions) { - if (!serverSessionMap.has(localSession.id)) { - toDelete.push(localSession.id); + if (!serverSessionMap.has(localSession.serverId)) { + toDelete.push(localSession.serverId); } } @@ -334,7 +339,19 @@ export class MessageManager { serverId: `${session.type}_${session.id}`, })); - await db.chatSessions.bulkAdd(dataToInsert); + try { + await db.chatSessions.bulkAdd(dataToInsert); + } catch (error) { + if (error instanceof Dexie.BulkError) { + console.warn( + `批量新增会话时检测到重复主键,切换为 bulkPut 以覆盖更新。错误详情:`, + error, + ); + await db.chatSessions.bulkPut(dataToInsert); + } else { + throw error; + } + } } /** @@ -357,14 +374,16 @@ export class MessageManager { */ private static async batchDeleteSessions( userId: number, - sessionIds: number[], + serverIds: string[], ) { - if (sessionIds.length === 0) return; + if (serverIds.length === 0) return; + + const serverIdSet = new Set(serverIds); await db.chatSessions .where("userId") .equals(userId) - .and(session => sessionIds.includes(session.id)) + .and(session => serverIdSet.has(session.serverId)) .delete(); }