编辑功能提交
This commit is contained in:
@@ -123,9 +123,9 @@ export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onS
|
||||
<div className="p-6 pt-4">
|
||||
{/* 搜索和筛选 */}
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Input
|
||||
placeholder="搜索设备IMEI/备注/微信号"
|
||||
value={searchQuery}
|
||||
<Input
|
||||
placeholder="搜索设备IMEI/备注/微信号"
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
className="flex-1 rounded-lg border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100"
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useState, useEffect, use } from "react"
|
||||
import { ChevronLeft } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { BasicSettings } from "../../../new/steps/BasicSettings"
|
||||
import { FriendRequestSettings } from "../../../new/steps/FriendRequestSettings"
|
||||
import { MessageSettings } from "../../../new/steps/MessageSettings"
|
||||
import { TagSettings } from "@/scenarios/new/steps/TagSettings"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { api, ApiResponse } from "@/lib/api"
|
||||
@@ -16,40 +15,47 @@ const steps = [
|
||||
{ id: 1, title: "步骤一", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
|
||||
{ id: 3, title: "步骤三", subtitle: "消息设置" },
|
||||
{ id: 4, title: "步骤四", subtitle: "流量标签设置" },
|
||||
]
|
||||
|
||||
export default function EditAcquisitionPlan({ params }: { params: { channel: string; id: string } }) {
|
||||
export default function EditAcquisitionPlan({ params }: { params: Promise<{ channel: string; id: string }> }) {
|
||||
const router = useRouter()
|
||||
const [currentStep, setCurrentStep] = useState(1)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [scenes, setScenes] = useState<any[]>([])
|
||||
const [formData, setFormData] = useState({
|
||||
planName: "",
|
||||
accounts: [],
|
||||
dailyLimit: 10,
|
||||
enabled: true,
|
||||
remarkType: "phone",
|
||||
remarkKeyword: "",
|
||||
posters: [],
|
||||
device: [],
|
||||
remarkType: "default",
|
||||
greeting: "",
|
||||
addFriendTimeStart: "09:00",
|
||||
addFriendTimeEnd: "18:00",
|
||||
addFriendInterval: 1,
|
||||
maxDailyFriends: 20,
|
||||
messageInterval: 1,
|
||||
messageContent: "",
|
||||
addInterval: 60,
|
||||
startTime: "09:00",
|
||||
endTime: "18:00",
|
||||
enabled: true,
|
||||
sceneId: "",
|
||||
scenario: "",
|
||||
planNameEdited: false
|
||||
})
|
||||
const [planNameEdited, setPlanNameEdited] = useState(false);
|
||||
|
||||
const resolvedParams = use(params);
|
||||
const { id, channel } = resolvedParams;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPlanData = async () => {
|
||||
try {
|
||||
const [planRes, scenesRes] = await Promise.all([
|
||||
api.get<ApiResponse>(`/v1/plan/detail?id=${params.id}`),
|
||||
api.get<ApiResponse>(`/v1/plan/detail?planId=${id}`),
|
||||
api.get<ApiResponse>("/v1/plan/scenes")
|
||||
])
|
||||
|
||||
if (planRes.code === 200 && planRes.data) {
|
||||
setFormData(planRes.data)
|
||||
setFormData({
|
||||
...planRes.data,
|
||||
device: planRes.data.device || [],
|
||||
selectedDevices: planRes.data.device || [],
|
||||
planNameEdited: false
|
||||
})
|
||||
}
|
||||
|
||||
if (scenesRes.code === 200 && Array.isArray(scenesRes.data)) {
|
||||
@@ -68,18 +74,23 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
}
|
||||
|
||||
fetchPlanData()
|
||||
}, [params.id])
|
||||
}, [id])
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const res = await api.put<ApiResponse>(`/v1/plan/update?id=${params.id}`, formData)
|
||||
|
||||
const submitData = {
|
||||
...formData,
|
||||
device: formData.selectedDevices || formData.device,
|
||||
posters: formData.materials || formData.posters,
|
||||
};
|
||||
const { selectedDevices, materials, ...finalData } = submitData;
|
||||
const res = await api.put<ApiResponse>(`/v1/plan/update?planId=${id}`, finalData);
|
||||
if (res.code === 200) {
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "获客计划已更新",
|
||||
})
|
||||
router.push(`/scenarios/${params.channel}`)
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "获客计划已更新",
|
||||
})
|
||||
router.push(`/scenarios/${channel}`)
|
||||
} else {
|
||||
toast({
|
||||
title: "保存失败",
|
||||
@@ -113,7 +124,7 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
const isStepValid = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
if (!formData.planName.trim() || formData.accounts.length === 0) {
|
||||
if (!formData.planName.trim() || formData.posters.length === 0) {
|
||||
toast({
|
||||
title: "请完善信息",
|
||||
description: "请填写计划名称并选择至少一个账号",
|
||||
@@ -133,7 +144,7 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
}
|
||||
return true
|
||||
case 3:
|
||||
if (!formData.messageContent.trim()) {
|
||||
if (!formData.messageContent || !formData.messageContent.trim()) {
|
||||
toast({
|
||||
title: "请完善信息",
|
||||
description: "请填写消息内容",
|
||||
@@ -142,13 +153,16 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case 4:
|
||||
return true
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const onChange = (data: any) => {
|
||||
if ('planName' in data) setPlanNameEdited(true);
|
||||
setFormData(prev => ({ ...prev, ...data }))
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
@@ -163,15 +177,11 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return <BasicSettings formData={formData} onChange={setFormData} onNext={handleNext} scenarios={scenes} isEdit />
|
||||
return <BasicSettings formData={formData} onChange={onChange} onNext={handleNext} scenarios={scenes} loadingScenes={loading} planNameEdited={planNameEdited} />
|
||||
case 2:
|
||||
return (
|
||||
<FriendRequestSettings formData={formData} onChange={setFormData} onNext={handleNext} onPrev={handlePrev} />
|
||||
)
|
||||
return <FriendRequestSettings formData={formData} onChange={onChange} onNext={handleNext} onPrev={handlePrev} />
|
||||
case 3:
|
||||
return <MessageSettings formData={formData} onChange={setFormData} onNext={handleNext} onPrev={handlePrev} />
|
||||
case 4:
|
||||
return <TagSettings formData={formData} onChange={setFormData} onNext={handleSave} onPrev={handlePrev} />
|
||||
return <MessageSettings formData={formData} onChange={onChange} onNext={handleSave} onPrev={handlePrev} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -182,7 +192,7 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
<div className="max-w-[390px] mx-auto bg-white min-h-screen flex flex-col">
|
||||
<header className="sticky top-0 z-10 bg-white border-b">
|
||||
<div className="flex items-center h-14 px-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.push(`/scenarios/${params.channel}`)}>
|
||||
<Button variant="ghost" size="icon" onClick={() => router.push(`/scenarios/${channel}`)}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="ml-2 text-lg font-medium">编辑获客计划</h1>
|
||||
@@ -225,18 +235,7 @@ export default function EditAcquisitionPlan({ params }: { params: { channel: str
|
||||
|
||||
<div className="flex-1 px-4 pb-20">{renderStepContent()}</div>
|
||||
|
||||
<div className="sticky bottom-0 left-0 right-0 bg-white border-t p-4">
|
||||
<div className="flex justify-between max-w-[390px] mx-auto">
|
||||
{currentStep > 1 && (
|
||||
<Button variant="outline" onClick={handlePrev}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
<Button className={cn("min-w-[120px]", currentStep === 1 ? "w-full" : "ml-auto")} onClick={handleNext}>
|
||||
{currentStep === steps.length ? "保存" : "下一步"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { ChevronLeft, Settings } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
@@ -20,17 +20,21 @@ const steps = [
|
||||
|
||||
export default function NewPlan() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const [currentStep, setCurrentStep] = useState(1)
|
||||
const [formData, setFormData] = useState({
|
||||
planName: "",
|
||||
posters: [],
|
||||
device: [],
|
||||
remarkType: "phone",
|
||||
greeting: "你好,请通过",
|
||||
addInterval: 1,
|
||||
remarkType: "default",
|
||||
greeting: "",
|
||||
addInterval: 60,
|
||||
startTime: "09:00",
|
||||
endTime: "18:00",
|
||||
enabled: true,
|
||||
sceneId: searchParams.get("type") || "",
|
||||
scenario: searchParams.get("type") || "",
|
||||
planNameEdited: false
|
||||
})
|
||||
|
||||
// 场景数据
|
||||
@@ -49,7 +53,8 @@ export default function NewPlan() {
|
||||
|
||||
// 更新表单数据
|
||||
const onChange = (data: any) => {
|
||||
setFormData((prev) => ({ ...prev, ...data }))
|
||||
if ('planName' in data) setFormData(prev => ({ ...prev, planNameEdited: true }))
|
||||
setFormData(prev => ({ ...prev, ...data }))
|
||||
}
|
||||
|
||||
// 处理保存
|
||||
@@ -103,7 +108,7 @@ export default function NewPlan() {
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return <BasicSettings formData={formData} onChange={onChange} onNext={handleNext} scenarios={scenes} />
|
||||
return <BasicSettings formData={formData} onChange={onChange} onNext={handleNext} scenarios={scenes} loadingScenes={loadingScenes} planNameEdited={formData.planNameEdited} />
|
||||
case 2:
|
||||
return <FriendRequestSettings formData={formData} onChange={onChange} onNext={handleNext} onPrev={handlePrev} />
|
||||
case 3:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect, useRef } from "react"
|
||||
import { useState, useEffect, useRef, useMemo } from "react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
@@ -262,7 +262,7 @@ const PlaceholderSection = ({ title }) => (
|
||||
<div className="p-8 text-center text-gray-400 border rounded-lg mt-4">{title}功能区待开发</div>
|
||||
)
|
||||
|
||||
export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSettingsProps) {
|
||||
export function BasicSettings({ formData, onChange, onNext, scenarios, loadingScenes, planNameEdited }: BasicSettingsProps & { loadingScenes?: boolean, planNameEdited?: boolean }) {
|
||||
const [isAccountDialogOpen, setIsAccountDialogOpen] = useState(false)
|
||||
const [isMaterialDialogOpen, setIsMaterialDialogOpen] = useState(false)
|
||||
const [isQRCodeOpen, setIsQRCodeOpen] = useState(false)
|
||||
@@ -325,29 +325,21 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const type = searchParams.get("type")
|
||||
// 类型映射表
|
||||
const typeMap: Record<string, string> = {
|
||||
haibao: "poster",
|
||||
douyin: "douyin",
|
||||
kuaishou: "kuaishou",
|
||||
xiaohongshu: "xiaohongshu",
|
||||
weibo: "weibo",
|
||||
phone: "phone",
|
||||
gongzhonghao: "gongzhonghao",
|
||||
weixinqun: "weixinqun",
|
||||
payment: "payment",
|
||||
api: "api",
|
||||
order: "order"
|
||||
}
|
||||
const realType = typeMap[type] || type
|
||||
const filteredScenarios = scenarios.filter(scene => scene.type === realType)
|
||||
|
||||
// 只在有唯一匹配时自动选中,否则不自动选中
|
||||
// 初始化时,如果有type参数且scenarios已加载,立即设置选中状态
|
||||
useEffect(() => {
|
||||
if (filteredScenarios.length === 1 && formData.sceneId !== filteredScenarios[0].id) {
|
||||
onChange({ sceneId: filteredScenarios[0].id })
|
||||
if (!loadingScenes && scenarios.length > 0 && type && !planNameEdited) {
|
||||
const targetScenario = scenarios.find(scene => String(scene.id) === String(type));
|
||||
if (targetScenario) {
|
||||
onChange({
|
||||
sceneId: String(type),
|
||||
scenario: String(type),
|
||||
planName: targetScenario.name.includes('电话')
|
||||
? `电话获客${new Date().toLocaleDateString("zh-CN").replace(/\//g, "")}`
|
||||
: targetScenario.name + new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [filteredScenarios, formData.sceneId, onChange])
|
||||
}, [loadingScenes, scenarios, type, planNameEdited]);
|
||||
|
||||
// 展示所有场景
|
||||
const displayedScenarios = scenarios
|
||||
@@ -382,11 +374,22 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
}, [formData, onChange])
|
||||
|
||||
const handleScenarioSelect = (scenarioId: string) => {
|
||||
const targetScenario = scenarios.find(s => String(s.id) === String(scenarioId));
|
||||
if (targetScenario) {
|
||||
if (scenarioId === "phone") {
|
||||
const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
|
||||
onChange({ ...formData, scenario: scenarioId, planName: `电话获客${today}` })
|
||||
const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, "");
|
||||
onChange({
|
||||
sceneId: String(scenarioId),
|
||||
scenario: String(scenarioId),
|
||||
planName: `电话获客${today}`
|
||||
});
|
||||
} else {
|
||||
onChange({ ...formData, scenario: scenarioId })
|
||||
onChange({
|
||||
sceneId: String(scenarioId),
|
||||
scenario: String(scenarioId),
|
||||
planName: targetScenario.name + new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +501,15 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
setIsPhoneSettingsOpen(false)
|
||||
}
|
||||
|
||||
const currentScenario = scenarios.find((s: any) => s.id === formData.scenario);
|
||||
const currentScenario = useMemo(
|
||||
() => scenarios.find((s: any) => String(s.id) === String(formData.scenario)),
|
||||
[scenarios, formData.scenario]
|
||||
)
|
||||
|
||||
// 调试日志
|
||||
useEffect(() => {
|
||||
console.log('formData:', formData, 'currentScenario:', currentScenario);
|
||||
}, [formData, currentScenario]);
|
||||
|
||||
const handleUploadPoster = () => {
|
||||
fileInputRef.current?.click()
|
||||
@@ -778,12 +789,12 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
type="button"
|
||||
className={
|
||||
"h-10 rounded-lg text-base transition-all w-full " +
|
||||
(formData.sceneId === scenario.id
|
||||
(String(formData.sceneId) === String(scenario.id)
|
||||
? "bg-blue-100 font-bold"
|
||||
: "bg-gray-50 text-gray-800 font-medium hover:bg-blue-50")
|
||||
}
|
||||
style={formData.sceneId === scenario.id ? { color: "#1677ff" } : {}}
|
||||
onClick={() => handleScenarioSelect(scenario.id)}
|
||||
style={String(formData.sceneId) === String(scenario.id) ? { color: "#1677ff" } : {}}
|
||||
onClick={() => handleScenarioSelect(String(scenario.id))}
|
||||
>
|
||||
{scenario.name.replace("获客", "")}
|
||||
</button>
|
||||
@@ -809,20 +820,20 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formData.scenario && (
|
||||
{currentScenario && (
|
||||
<div className="mt-6">
|
||||
<Label className="text-base mb-3 block">
|
||||
{scenarios.find((s) => s.id === formData.scenario)?.name}标签(可多选)
|
||||
{currentScenario.name}标签(可多选)
|
||||
</Label>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{(scenarios.find((s) => s.id === formData.scenario)?.scenarioTags || []).map((tag: string) => {
|
||||
{(currentScenario.scenarioTags || []).map((tag: string) => {
|
||||
const idx = getTagColorIdx(tag);
|
||||
const selected = selectedScenarioTags.includes(tag);
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
key={tag}
|
||||
className={`px-3 py-2 rounded-full text-sm cursor-pointer transition-all ${
|
||||
className={`px-3 py-2 rounded-full text-sm cursor-pointer transition-all ${
|
||||
selected
|
||||
? tagColorPoolDark[idx] + " ring-2 ring-blue-400"
|
||||
: tagColorPoolLight[idx] + " hover:ring-1 hover:ring-gray-300"
|
||||
@@ -830,7 +841,7 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
onClick={() => handleScenarioTagToggle(tag)}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -897,10 +908,10 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.scenario && (
|
||||
{currentScenario && (
|
||||
<>
|
||||
{scenarios.find((s) => s.id === formData.scenario)?.type === "social" &&
|
||||
formData.scenario !== "phone" && (
|
||||
{currentScenario.type === "social" &&
|
||||
currentScenario.id !== "phone" && (
|
||||
<div>
|
||||
<Label>绑定账号</Label>
|
||||
<div className="flex gap-2 mt-2">
|
||||
@@ -940,7 +951,7 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.scenario === "phone" && (
|
||||
{String(formData.scenario) === "phone" && (
|
||||
<Card className="p-4 border-blue-100 bg-blue-50/50 mt-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Label className="text-base font-medium text-blue-700">电话获客设置</Label>
|
||||
@@ -1001,7 +1012,7 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{formData.scenario === "phone" && (
|
||||
{String(formData.scenario) === "phone" && (
|
||||
<>
|
||||
<div className="mt-6">
|
||||
<Label className="text-base mb-2 block">通话类型</Label>
|
||||
@@ -1034,13 +1045,13 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
<div className="mt-6">
|
||||
<Label className="text-base mb-2 block">通话标签(可多选)</Label>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{(scenarios.find((s: any) => s.id === formData.scenario)?.scenarioTags || []).map((tag: string) => {
|
||||
{(currentScenario.scenarioTags || []).map((tag: string) => {
|
||||
const idx = getTagColorIdx(tag);
|
||||
const selected = selectedPhoneTags.includes(tag);
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
key={tag}
|
||||
className={`px-3 py-1.5 rounded-full text-sm cursor-pointer ${
|
||||
className={`px-3 py-1.5 rounded-full text-sm cursor-pointer ${
|
||||
selected
|
||||
? tagColorPoolDark[idx] + " ring-2 ring-blue-400"
|
||||
: tagColorPoolLight[idx] + " hover:ring-1 hover:ring-gray-300"
|
||||
@@ -1048,7 +1059,7 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
onClick={() => handleTagToggle(tag)}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -1056,13 +1067,13 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
</>
|
||||
)}
|
||||
|
||||
{((currentScenario?.type === "material" || currentScenario?.name === "海报获客" || currentScenario?.id === 1) && (
|
||||
{((currentScenario?.type === "material" || currentScenario?.name === "海报获客" || String(currentScenario?.id) === "1") && (
|
||||
<div>
|
||||
{renderSceneExtra()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{scenarios.find((s: any) => s.id === formData.scenario)?.id === "order" && (
|
||||
{String(currentScenario?.id) === "order" && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Label>订单导入</Label>
|
||||
@@ -1114,7 +1125,7 @@ export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSe
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{formData.scenario === "weixinqun" && (
|
||||
{String(formData.scenario) === "weixinqun" && (
|
||||
<>
|
||||
<div>
|
||||
<Label>群管理设置</Label>
|
||||
|
||||
@@ -102,8 +102,8 @@ export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: Fr
|
||||
// 设备选择回填
|
||||
const handleDeviceSelect = (deviceIds: string[]) => {
|
||||
setSelectedDeviceIds(deviceIds)
|
||||
// 只存id,或如需完整对象可自行扩展
|
||||
onChange({ ...formData, selectedDevices: deviceIds })
|
||||
// 同步 device 和 selectedDevices 字段
|
||||
onChange({ ...formData, selectedDevices: deviceIds, device: deviceIds })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -583,7 +583,7 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS
|
||||
<Button variant="outline" onClick={onPrev}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button onClick={onNext}>下一步</Button>
|
||||
<Button onClick={onNext}>提交</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function EditTrafficDistributionPage({ params }: { params: Promis
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
basicInfo: {
|
||||
name: "",
|
||||
name: "",
|
||||
distributeType: 1,
|
||||
maxPerDay: 100,
|
||||
timeType: 2,
|
||||
@@ -57,7 +57,7 @@ export default function EditTrafficDistributionPage({ params }: { params: Promis
|
||||
endTime: "22:00",
|
||||
source: "",
|
||||
sourceIcon: "",
|
||||
description: "",
|
||||
description: "",
|
||||
},
|
||||
targetSettings: {
|
||||
targetGroups: [],
|
||||
@@ -217,4 +217,4 @@ export default function EditTrafficDistributionPage({ params }: { params: Promis
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ Route::group('v1/', function () {
|
||||
Route::get('copy', 'app\cunkebao\controller\plan\PlanSceneV1Controller@copy');
|
||||
Route::delete('delete', 'app\cunkebao\controller\plan\PlanSceneV1Controller@delete');
|
||||
Route::post('updateStatus', 'app\cunkebao\controller\plan\PlanSceneV1Controller@updateStatus');
|
||||
Route::get('detail', 'app\cunkebao\controller\plan\GetAddFriendPlanDetailV1Controller@index');
|
||||
Route::PUT('update', 'app\cunkebao\controller\plan\PostUpdateAddFriendPlanV1Controller@index');
|
||||
});
|
||||
|
||||
// 流量池相关
|
||||
|
||||
@@ -79,7 +79,6 @@ class GetDeviceListV1Controller extends BaseController
|
||||
])
|
||||
->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId')
|
||||
->leftJoin('wechat_account a', 'l.wechatId = a.wechatId')
|
||||
->group('l.wechatId')
|
||||
->order('d.id desc');
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 获取获客计划详情控制器
|
||||
*/
|
||||
class GetAddFriendPlanDetailV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取计划详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$planId = $this->request->param('planId');
|
||||
|
||||
if (empty($planId)) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 查询计划详情
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $planId)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
// 解析JSON字段
|
||||
$sceneConf = json_decode($plan['sceneConf'], true) ?: [];
|
||||
$reqConf = json_decode($plan['reqConf'], true) ?: [];
|
||||
$msgConf= json_decode($plan['msgConf'], true) ?: [];
|
||||
$tagConf = json_decode($plan['tagConf'], true) ?: [];
|
||||
|
||||
$newData['messagePlans'] = $msgConf;
|
||||
$newData = array_merge($newData,$sceneConf,$reqConf,$tagConf,$plan);
|
||||
unset(
|
||||
$newData['sceneConf'],
|
||||
$newData['reqConf'],
|
||||
$newData['msgConf'],
|
||||
$newData['tagConf'],
|
||||
$newData['userInfo'],
|
||||
$newData['createTime'],
|
||||
$newData['updateTime'],
|
||||
$newData['deleteTime'],
|
||||
$newData['apiKey']
|
||||
);
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success($newData, '获取计划详情成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['planName'])) {
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['planName'],
|
||||
$sceneConf['scenario'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
@@ -106,8 +106,8 @@ class PostCreateAddFriendPlanV1Controller extends Controller
|
||||
|
||||
// 构建数据
|
||||
$data = [
|
||||
'name' => $params['planName'],
|
||||
'sceneId' => $params['scenario'],
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 更新获客计划控制器
|
||||
*/
|
||||
class PostUpdateAddFriendPlanV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* 更新计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['planId'])) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['device'])) {
|
||||
return ResponseHelper::error('请选择设备', 400);
|
||||
}
|
||||
|
||||
// 检查计划是否存在
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
// 归类参数
|
||||
$msgConf = isset($params['messagePlans']) ? $params['messagePlans'] : [];
|
||||
$tagConf = [
|
||||
'scenarioTags' => $params['scenarioTags'] ?? [],
|
||||
'customTags' => $params['customTags'] ?? [],
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $params['device'] ?? [],
|
||||
'remarkType' => $params['remarkType'] ?? '',
|
||||
'greeting' => $params['greeting'] ?? '',
|
||||
'addFriendInterval' => $params['addFriendInterval'] ?? '',
|
||||
'startTime' => $params['startTime'] ?? '',
|
||||
'endTime' => $params['endTime'] ?? '',
|
||||
];
|
||||
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['device'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['endTime']
|
||||
);
|
||||
|
||||
// 构建更新数据
|
||||
$data = [
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
|
||||
'updateTime'=> time(),
|
||||
];
|
||||
|
||||
// 开启事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 更新数据
|
||||
$result = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->update($data);
|
||||
|
||||
if ($result === false) {
|
||||
throw new \Exception('更新计划失败');
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success(['planId' => $params['planId']], '更新计划任务成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user