代码提交
This commit is contained in:
@@ -4,10 +4,10 @@ import { useState, useEffect } from "react"
|
|||||||
import { ChevronLeft } from "lucide-react"
|
import { ChevronLeft } from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { BasicSettings } from "../../../new/steps/BasicSettings"
|
import { BasicSettings } from "@/plans/new/steps/BasicSettings"
|
||||||
import { FriendRequestSettings } from "../../../new/steps/FriendRequestSettings"
|
import { FriendRequestSettings } from "@/plans/new/steps/FriendRequestSettings"
|
||||||
import { MessageSettings } from "../../../new/steps/MessageSettings"
|
import { MessageSettings } from "@/plans/new/steps/MessageSettings"
|
||||||
import { TagSettings } from "../../../new/steps/TagSettings"
|
import { TagSettings } from "@/plans/new/steps/TagSettings"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { ChevronLeft, Settings } from "lucide-react"
|
import { ChevronLeft, Settings } from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -9,6 +9,7 @@ import { StepIndicator } from "@/app/components/ui-templates/step-indicator"
|
|||||||
import { BasicSettings } from "./steps/BasicSettings"
|
import { BasicSettings } from "./steps/BasicSettings"
|
||||||
import { FriendRequestSettings } from "./steps/FriendRequestSettings"
|
import { FriendRequestSettings } from "./steps/FriendRequestSettings"
|
||||||
import { MessageSettings } from "./steps/MessageSettings"
|
import { MessageSettings } from "./steps/MessageSettings"
|
||||||
|
import { api, ApiResponse } from "@/lib/api"
|
||||||
|
|
||||||
// 步骤定义 - 只保留三个步骤
|
// 步骤定义 - 只保留三个步骤
|
||||||
const steps = [
|
const steps = [
|
||||||
@@ -22,7 +23,7 @@ export default function NewPlan() {
|
|||||||
const [currentStep, setCurrentStep] = useState(1)
|
const [currentStep, setCurrentStep] = useState(1)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
planName: "",
|
planName: "",
|
||||||
scenario: "haibao",
|
scenario: "",
|
||||||
posters: [],
|
posters: [],
|
||||||
device: "",
|
device: "",
|
||||||
remarkType: "phone",
|
remarkType: "phone",
|
||||||
@@ -31,9 +32,23 @@ export default function NewPlan() {
|
|||||||
startTime: "09:00",
|
startTime: "09:00",
|
||||||
endTime: "18:00",
|
endTime: "18:00",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
// 移除tags字段
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 场景数据
|
||||||
|
const [scenes, setScenes] = useState<any[]>([])
|
||||||
|
const [loadingScenes, setLoadingScenes] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.get<ApiResponse>("/v1/plan/scenes")
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === 200 && Array.isArray(res.data)) {
|
||||||
|
setScenes(res.data)
|
||||||
|
setFormData(prev => ({ ...prev, scenario: prev.scenario || (res.data[0]?.id || "") }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => setLoadingScenes(false))
|
||||||
|
}, [])
|
||||||
|
|
||||||
// 更新表单数据
|
// 更新表单数据
|
||||||
const onChange = (data: any) => {
|
const onChange = (data: any) => {
|
||||||
setFormData((prev) => ({ ...prev, ...data }))
|
setFormData((prev) => ({ ...prev, ...data }))
|
||||||
@@ -78,7 +93,7 @@ export default function NewPlan() {
|
|||||||
const renderStepContent = () => {
|
const renderStepContent = () => {
|
||||||
switch (currentStep) {
|
switch (currentStep) {
|
||||||
case 1:
|
case 1:
|
||||||
return <BasicSettings formData={formData} onChange={onChange} onNext={handleNext} />
|
return <BasicSettings formData={formData} onChange={onChange} onNext={handleNext} scenarios={scenes} />
|
||||||
case 2:
|
case 2:
|
||||||
return <FriendRequestSettings formData={formData} onChange={onChange} onNext={handleNext} onPrev={handlePrev} />
|
return <FriendRequestSettings formData={formData} onChange={onChange} onNext={handleNext} onPrev={handlePrev} />
|
||||||
case 3:
|
case 3:
|
||||||
@@ -88,6 +103,10 @@ export default function NewPlan() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loadingScenes) {
|
||||||
|
return <div className="flex justify-center items-center h-40">加载中...</div>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<div className="max-w-[390px] mx-auto bg-white min-h-screen flex flex-col">
|
<div className="max-w-[390px] mx-auto bg-white min-h-screen flex flex-col">
|
||||||
|
|||||||
@@ -20,97 +20,11 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
|
|
||||||
// 调整场景顺序,确保API获客在最后,并且前三个是最常用的场景
|
|
||||||
const scenarios = [
|
|
||||||
{ id: "haibao", name: "海报获客", type: "material" },
|
|
||||||
{ id: "order", name: "订单获客", type: "api" },
|
|
||||||
{ id: "douyin", name: "抖音获客", type: "social" },
|
|
||||||
{ id: "xiaohongshu", name: "小红书获客", type: "social" },
|
|
||||||
{ id: "phone", name: "电话获客", type: "social" },
|
|
||||||
{ id: "gongzhonghao", name: "公众号获客", type: "social" },
|
|
||||||
{ id: "weixinqun", name: "微信群获客", type: "social" },
|
|
||||||
{ id: "payment", name: "付款码获客", type: "material" },
|
|
||||||
{ id: "api", name: "API获客", type: "api" }, // API获客放在最后
|
|
||||||
]
|
|
||||||
|
|
||||||
const phoneCallTags = [
|
|
||||||
{ id: "tag-1", name: "咨询", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "tag-2", name: "投诉", color: "bg-red-100 text-red-800" },
|
|
||||||
{ id: "tag-3", name: "合作", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "tag-4", name: "价格", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "tag-5", name: "售后", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "tag-6", name: "订单", color: "bg-yellow-100 text-yellow-800" },
|
|
||||||
{ id: "tag-7", name: "物流", color: "bg-teal-100 text-teal-800" },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 不同场景的预设标签
|
|
||||||
const scenarioTags = {
|
|
||||||
haibao: [
|
|
||||||
{ id: "poster-tag-1", name: "活动推广", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "poster-tag-2", name: "产品宣传", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "poster-tag-3", name: "品牌展示", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "poster-tag-4", name: "优惠促销", color: "bg-red-100 text-red-800" },
|
|
||||||
{ id: "poster-tag-5", name: "新品发布", color: "bg-orange-100 text-orange-800" },
|
|
||||||
],
|
|
||||||
order: [
|
|
||||||
{ id: "order-tag-1", name: "新订单", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "order-tag-2", name: "复购客户", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "order-tag-3", name: "高价值订单", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "order-tag-4", name: "待付款", color: "bg-yellow-100 text-yellow-800" },
|
|
||||||
{ id: "order-tag-5", name: "已完成", color: "bg-gray-100 text-gray-800" },
|
|
||||||
],
|
|
||||||
douyin: [
|
|
||||||
{ id: "douyin-tag-1", name: "短视频", color: "bg-pink-100 text-pink-800" },
|
|
||||||
{ id: "douyin-tag-2", name: "直播", color: "bg-red-100 text-red-800" },
|
|
||||||
{ id: "douyin-tag-3", name: "带货", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "douyin-tag-4", name: "粉丝互动", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "douyin-tag-5", name: "热门话题", color: "bg-purple-100 text-purple-800" },
|
|
||||||
],
|
|
||||||
xiaohongshu: [
|
|
||||||
{ id: "xhs-tag-1", name: "种草笔记", color: "bg-red-100 text-red-800" },
|
|
||||||
{ id: "xhs-tag-2", name: "美妆", color: "bg-pink-100 text-pink-800" },
|
|
||||||
{ id: "xhs-tag-3", name: "穿搭", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "xhs-tag-4", name: "生活方式", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "xhs-tag-5", name: "好物推荐", color: "bg-orange-100 text-orange-800" },
|
|
||||||
],
|
|
||||||
phone: phoneCallTags,
|
|
||||||
gongzhonghao: [
|
|
||||||
{ id: "gzh-tag-1", name: "文章推送", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "gzh-tag-2", name: "活动通知", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "gzh-tag-3", name: "产品介绍", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "gzh-tag-4", name: "用户服务", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "gzh-tag-5", name: "品牌故事", color: "bg-gray-100 text-gray-800" },
|
|
||||||
],
|
|
||||||
weixinqun: [
|
|
||||||
{ id: "wxq-tag-1", name: "群活动", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "wxq-tag-2", name: "产品分享", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "wxq-tag-3", name: "用户交流", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "wxq-tag-4", name: "优惠信息", color: "bg-pink-100 text-pink-800" },
|
|
||||||
{ id: "wxq-tag-5", name: "答疑解惑", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "wxq-tag-6", name: "新人欢迎", color: "bg-yellow-100 text-yellow-800" },
|
|
||||||
{ id: "wxq-tag-7", name: "群规通知", color: "bg-gray-100 text-gray-800" },
|
|
||||||
{ id: "wxq-tag-8", name: "活跃互动", color: "bg-indigo-100 text-indigo-800" },
|
|
||||||
],
|
|
||||||
payment: [
|
|
||||||
{ id: "pay-tag-1", name: "扫码支付", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "pay-tag-2", name: "线下门店", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "pay-tag-3", name: "活动收款", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "pay-tag-4", name: "服务费用", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "pay-tag-5", name: "会员充值", color: "bg-yellow-100 text-yellow-800" },
|
|
||||||
],
|
|
||||||
api: [
|
|
||||||
{ id: "api-tag-1", name: "系统对接", color: "bg-blue-100 text-blue-800" },
|
|
||||||
{ id: "api-tag-2", name: "数据同步", color: "bg-green-100 text-green-800" },
|
|
||||||
{ id: "api-tag-3", name: "自动化", color: "bg-purple-100 text-purple-800" },
|
|
||||||
{ id: "api-tag-4", name: "第三方平台", color: "bg-orange-100 text-orange-800" },
|
|
||||||
{ id: "api-tag-5", name: "实时推送", color: "bg-gray-100 text-gray-800" },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BasicSettingsProps {
|
interface BasicSettingsProps {
|
||||||
formData: any
|
formData: any
|
||||||
onChange: (data: any) => void
|
onChange: (data: any) => void
|
||||||
onNext?: () => void
|
onNext?: () => void
|
||||||
|
scenarios: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
@@ -182,7 +96,36 @@ const generatePosterMaterials = (): Material[] => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
|
// 颜色池分为更浅的未选中和深色的选中
|
||||||
|
const tagColorPoolLight = [
|
||||||
|
"bg-blue-50 text-blue-600",
|
||||||
|
"bg-green-50 text-green-600",
|
||||||
|
"bg-purple-50 text-purple-600",
|
||||||
|
"bg-red-50 text-red-600",
|
||||||
|
"bg-orange-50 text-orange-600",
|
||||||
|
"bg-yellow-50 text-yellow-600",
|
||||||
|
"bg-gray-50 text-gray-600",
|
||||||
|
"bg-pink-50 text-pink-600",
|
||||||
|
];
|
||||||
|
const tagColorPoolDark = [
|
||||||
|
"bg-blue-500 text-white",
|
||||||
|
"bg-green-500 text-white",
|
||||||
|
"bg-purple-500 text-white",
|
||||||
|
"bg-red-500 text-white",
|
||||||
|
"bg-orange-500 text-white",
|
||||||
|
"bg-yellow-400 text-white",
|
||||||
|
"bg-gray-700 text-white",
|
||||||
|
"bg-pink-500 text-white",
|
||||||
|
];
|
||||||
|
function getTagColorIdx(tag: string) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < tag.length; i++) {
|
||||||
|
hash = tag.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
return Math.abs(hash) % tagColorPoolLight.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BasicSettings({ formData, onChange, onNext, scenarios }: BasicSettingsProps) {
|
||||||
const [isAccountDialogOpen, setIsAccountDialogOpen] = useState(false)
|
const [isAccountDialogOpen, setIsAccountDialogOpen] = useState(false)
|
||||||
const [isMaterialDialogOpen, setIsMaterialDialogOpen] = useState(false)
|
const [isMaterialDialogOpen, setIsMaterialDialogOpen] = useState(false)
|
||||||
const [isQRCodeOpen, setIsQRCodeOpen] = useState(false)
|
const [isQRCodeOpen, setIsQRCodeOpen] = useState(false)
|
||||||
@@ -225,11 +168,11 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
|
|||||||
const [selectedPhoneTags, setSelectedPhoneTags] = useState<string[]>(formData.phoneTags || [])
|
const [selectedPhoneTags, setSelectedPhoneTags] = useState<string[]>(formData.phoneTags || [])
|
||||||
const [phoneCallType, setPhoneCallType] = useState(formData.phoneCallType || "both")
|
const [phoneCallType, setPhoneCallType] = useState(formData.phoneCallType || "both")
|
||||||
|
|
||||||
// 处理标签选择
|
// 处理标签选择 (现在处理的是字符串标签)
|
||||||
const handleTagToggle = (tagId: string) => {
|
const handleTagToggle = (tag: string) => {
|
||||||
const newTags = selectedPhoneTags.includes(tagId)
|
const newTags = selectedPhoneTags.includes(tag)
|
||||||
? selectedPhoneTags.filter((id) => id !== tagId)
|
? selectedPhoneTags.filter((t) => t !== tag)
|
||||||
: [...selectedPhoneTags, tagId]
|
: [...selectedPhoneTags, tag]
|
||||||
|
|
||||||
setSelectedPhoneTags(newTags)
|
setSelectedPhoneTags(newTags)
|
||||||
onChange({ ...formData, phoneTags: newTags })
|
onChange({ ...formData, phoneTags: newTags })
|
||||||
@@ -267,11 +210,11 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理场景标签选择
|
// 处理场景标签选择 (现在处理的是字符串标签)
|
||||||
const handleScenarioTagToggle = (tagId: string) => {
|
const handleScenarioTagToggle = (tag: string) => {
|
||||||
const newTags = selectedScenarioTags.includes(tagId)
|
const newTags = selectedScenarioTags.includes(tag)
|
||||||
? selectedScenarioTags.filter((id) => id !== tagId)
|
? selectedScenarioTags.filter((t) => t !== tag)
|
||||||
: [...selectedScenarioTags, tagId]
|
: [...selectedScenarioTags, tag]
|
||||||
|
|
||||||
setSelectedScenarioTags(newTags)
|
setSelectedScenarioTags(newTags)
|
||||||
onChange({ ...formData, scenarioTags: newTags })
|
onChange({ ...formData, scenarioTags: newTags })
|
||||||
@@ -448,19 +391,23 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
|
|||||||
|
|
||||||
{/* 预设标签 */}
|
{/* 预设标签 */}
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
{(scenarioTags[formData.scenario as keyof typeof scenarioTags] || []).map((tag) => (
|
{(scenarios.find((s) => s.id === formData.scenario)?.scenarioTags || []).map((tag: string) => {
|
||||||
<div
|
const idx = getTagColorIdx(tag);
|
||||||
key={tag.id}
|
const selected = selectedScenarioTags.includes(tag);
|
||||||
className={`px-3 py-2 rounded-full text-sm cursor-pointer transition-all ${
|
return (
|
||||||
selectedScenarioTags.includes(tag.id)
|
<div
|
||||||
? tag.color + " ring-2 ring-blue-400"
|
key={tag}
|
||||||
: tag.color + " hover:ring-1 hover:ring-gray-300"
|
className={`px-3 py-2 rounded-full text-sm cursor-pointer transition-all ${
|
||||||
}`}
|
selected
|
||||||
onClick={() => handleScenarioTagToggle(tag.id)}
|
? tagColorPoolDark[idx] + " ring-2 ring-blue-400"
|
||||||
>
|
: tagColorPoolLight[idx] + " hover:ring-1 hover:ring-gray-300"
|
||||||
{tag.name}
|
}`}
|
||||||
</div>
|
onClick={() => handleScenarioTagToggle(tag)}
|
||||||
))}
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 自定义标签 */}
|
{/* 自定义标签 */}
|
||||||
@@ -663,29 +610,33 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 添加标签功能 */}
|
{/* 添加标签功能 - 使用从 scenarios 中获取的标签数据 */}
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label className="text-base mb-2 block">通话标签(可多选)</Label>
|
<Label className="text-base mb-2 block">通话标签(可多选)</Label>
|
||||||
<div className="flex flex-wrap gap-2 mt-2">
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
{phoneCallTags.map((tag) => (
|
{(scenarios.find((s: any) => s.id === formData.scenario)?.scenarioTags || []).map((tag: string) => {
|
||||||
<div
|
const idx = getTagColorIdx(tag);
|
||||||
key={tag.id}
|
const selected = selectedPhoneTags.includes(tag);
|
||||||
className={`px-3 py-1.5 rounded-full text-sm cursor-pointer ${
|
return (
|
||||||
selectedPhoneTags.includes(tag.id)
|
<div
|
||||||
? tag.color
|
key={tag}
|
||||||
: "bg-gray-100 text-gray-800 hover:bg-gray-200"
|
className={`px-3 py-1.5 rounded-full text-sm cursor-pointer ${
|
||||||
}`}
|
selected
|
||||||
onClick={() => handleTagToggle(tag.id)}
|
? tagColorPoolDark[idx] + " ring-2 ring-blue-400"
|
||||||
>
|
: tagColorPoolLight[idx] + " hover:ring-1 hover:ring-gray-300"
|
||||||
{tag.name}
|
}`}
|
||||||
</div>
|
onClick={() => handleTagToggle(tag)}
|
||||||
))}
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{scenarios.find((s) => s.id === formData.scenario)?.type === "material" && (
|
{scenarios.find((s: any) => s.id === formData.scenario)?.type === "material" && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<Label>选择海报</Label>
|
<Label>选择海报</Label>
|
||||||
@@ -757,7 +708,7 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{scenarios.find((s) => s.id === formData.scenario)?.id === "order" && (
|
{scenarios.find((s: any) => s.id === formData.scenario)?.id === "order" && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<Label>订单导入</Label>
|
<Label>订单导入</Label>
|
||||||
|
|||||||
@@ -4,87 +4,13 @@ import { Plus, TrendingUp } from "lucide-react"
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { api, ApiResponse } from "@/lib/api"
|
||||||
|
|
||||||
export default function ScenariosPage() {
|
export default function ScenariosPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const [scenarios, setScenarios] = useState<any[]>([])
|
||||||
// 场景数据
|
// AI智能获客用本地 mock 数据
|
||||||
const scenarios = [
|
|
||||||
{
|
|
||||||
id: "poster",
|
|
||||||
name: "海报获客",
|
|
||||||
icon: "🖼️",
|
|
||||||
count: 167,
|
|
||||||
growth: "+10.2%",
|
|
||||||
path: "/scenarios/poster",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "order",
|
|
||||||
name: "订单获客",
|
|
||||||
icon: "📋",
|
|
||||||
count: 112,
|
|
||||||
growth: "+7.8%",
|
|
||||||
path: "/scenarios/order",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "douyin",
|
|
||||||
name: "抖音获客",
|
|
||||||
icon: "📱",
|
|
||||||
count: 156,
|
|
||||||
growth: "+12.5%",
|
|
||||||
path: "/scenarios/douyin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "xiaohongshu",
|
|
||||||
name: "小红书获客",
|
|
||||||
icon: "📕",
|
|
||||||
count: 89,
|
|
||||||
growth: "+8.3%",
|
|
||||||
path: "/scenarios/xiaohongshu",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "phone",
|
|
||||||
name: "电话获客",
|
|
||||||
icon: "📞",
|
|
||||||
count: 42,
|
|
||||||
growth: "+15.8%",
|
|
||||||
path: "/scenarios/phone",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "gongzhonghao",
|
|
||||||
name: "公众号获客",
|
|
||||||
icon: "📢",
|
|
||||||
count: 234,
|
|
||||||
growth: "+15.7%",
|
|
||||||
path: "/scenarios/gongzhonghao",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "weixinqun",
|
|
||||||
name: "微信群获客",
|
|
||||||
icon: "👥",
|
|
||||||
count: 145,
|
|
||||||
growth: "+11.2%",
|
|
||||||
path: "/scenarios/weixinqun",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "payment",
|
|
||||||
name: "付款码获客",
|
|
||||||
icon: "💳",
|
|
||||||
count: 78,
|
|
||||||
growth: "+9.5%",
|
|
||||||
path: "/scenarios/payment",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "api",
|
|
||||||
name: "API获客",
|
|
||||||
icon: "🔌",
|
|
||||||
count: 198,
|
|
||||||
growth: "+14.3%",
|
|
||||||
path: "/scenarios/api",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// AI智能获客
|
|
||||||
const aiScenarios = [
|
const aiScenarios = [
|
||||||
{
|
{
|
||||||
id: "ai-friend",
|
id: "ai-friend",
|
||||||
@@ -114,13 +40,35 @@ export default function ScenariosPage() {
|
|||||||
path: "/scenarios/ai-conversion",
|
path: "/scenarios/ai-conversion",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true)
|
||||||
|
api.get<ApiResponse>("/v1/plan/scenes")
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code === 200 && Array.isArray(res.data)) {
|
||||||
|
setScenarios(res.data)
|
||||||
|
} else {
|
||||||
|
setError(res.msg || "接口返回异常")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => setError(err?.message || "接口请求失败"))
|
||||||
|
.finally(() => setLoading(false))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="flex justify-center items-center h-40">加载中...</div>
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return <div className="text-red-500 text-center py-8">{error}</div>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||||
<header className="sticky top-0 z-10 bg-white border-b">
|
<header className="sticky top-0 z-10 bg-white border-b">
|
||||||
<div className="flex items-center justify-between p-4">
|
<div className="flex items-center justify-between p-4">
|
||||||
<h1 className="text-xl font-semibold">场景获客</h1>
|
<h1 className="text-xl font-semibold">场景获客</h1>
|
||||||
{/* <Button onClick={() => router.push("/plans/new")} size="sm"> */}
|
|
||||||
<Button onClick={() => router.push("/scenarios/new")} size="sm">
|
<Button onClick={() => router.push("/scenarios/new")} size="sm">
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="h-4 w-4 mr-1" />
|
||||||
新建计划
|
新建计划
|
||||||
@@ -134,11 +82,14 @@ export default function ScenariosPage() {
|
|||||||
<Card
|
<Card
|
||||||
key={scenario.id}
|
key={scenario.id}
|
||||||
className="overflow-hidden hover:shadow-md transition-shadow cursor-pointer"
|
className="overflow-hidden hover:shadow-md transition-shadow cursor-pointer"
|
||||||
onClick={() => router.push(scenario.path)}
|
onClick={() => router.push(`/scenarios/${scenario.id}`)}
|
||||||
>
|
>
|
||||||
<CardContent className="p-4 flex flex-col items-center">
|
<CardContent className="p-4 flex flex-col items-center">
|
||||||
<div className="text-3xl mb-2">{scenario.icon}</div>
|
<img src={scenario.image} alt={scenario.name} className="w-12 h-12 mb-2 rounded" />
|
||||||
<h3 className="text-blue-600 font-medium text-center">{scenario.name}</h3>
|
<h3 className="text-blue-600 font-medium text-center">{scenario.name}</h3>
|
||||||
|
{scenario.description && (
|
||||||
|
<p className="text-xs text-gray-500 text-center mt-1 line-clamp-2">{scenario.description}</p>
|
||||||
|
)}
|
||||||
<div className="flex items-center mt-2 text-gray-500 text-sm">
|
<div className="flex items-center mt-2 text-gray-500 text-sm">
|
||||||
<span>今日: </span>
|
<span>今日: </span>
|
||||||
<span className="font-medium ml-1">{scenario.count}</span>
|
<span className="font-medium ml-1">{scenario.count}</span>
|
||||||
@@ -152,6 +103,7 @@ export default function ScenariosPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/*
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<h2 className="text-lg font-medium">AI智能获客</h2>
|
<h2 className="text-lg font-medium">AI智能获客</h2>
|
||||||
@@ -184,6 +136,7 @@ export default function ScenariosPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -158,14 +158,14 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {}, de
|
|||||||
onClick={() => togglePool(pool.label)}
|
onClick={() => togglePool(pool.label)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 p-4 flex-1">
|
<div className="flex items-center space-x-3 p-4 flex-1">
|
||||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
|
||||||
<Database className="h-5 w-5 text-blue-600" />
|
<Database className="h-5 w-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-base">{pool.label}</p>
|
<p className="font-bold text-base">{pool.label}</p>
|
||||||
<p className="text-sm text-gray-500">{poolDescMap[pool.label] || ""}</p>
|
<p className="text-sm text-gray-500">{poolDescMap[pool.label] || ""}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500 mr-4">{pool.count} 人</span>
|
<span className="text-sm text-gray-500 mr-4">{pool.count} 人</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -176,7 +176,7 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {}, de
|
|||||||
togglePool(pool.label);
|
togglePool(pool.label);
|
||||||
}}
|
}}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -199,7 +199,7 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {}, de
|
|||||||
确认
|
确认
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<div className="mt-8 flex justify-between">
|
<div className="mt-8 flex justify-between">
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Route::group('v1/', function () {
|
|||||||
// 获客场景相关
|
// 获客场景相关
|
||||||
Route::group('plan', function () {
|
Route::group('plan', function () {
|
||||||
Route::get('scenes', 'app\cunkebao\controller\plan\GetPlanSceneListV1Controller@index');
|
Route::get('scenes', 'app\cunkebao\controller\plan\GetPlanSceneListV1Controller@index');
|
||||||
|
Route::get('scenes-detail', 'app\cunkebao\controller\plan\GetPlanSceneListV1Controller@detail');
|
||||||
Route::post('create', 'app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller@index');
|
Route::post('create', 'app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller@index');
|
||||||
Route::get('list', 'app\cunkebao\controller\Plan@getList');
|
Route::get('list', 'app\cunkebao\controller\Plan@getList');
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace app\cunkebao\controller\plan;
|
|||||||
use app\common\model\PlanScene as PlansSceneModel;
|
use app\common\model\PlanScene as PlansSceneModel;
|
||||||
use app\cunkebao\controller\BaseController;
|
use app\cunkebao\controller\BaseController;
|
||||||
use library\ResponseHelper;
|
use library\ResponseHelper;
|
||||||
|
use think\Db;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获客场景控制器
|
* 获客场景控制器
|
||||||
@@ -18,13 +19,15 @@ class GetPlanSceneListV1Controller extends BaseController
|
|||||||
*/
|
*/
|
||||||
protected function getSceneList(): array
|
protected function getSceneList(): array
|
||||||
{
|
{
|
||||||
return PlansSceneModel::where(
|
$list = PlansSceneModel::where(['status' => PlansSceneModel::STATUS_ACTIVE])->order('sort desc')->select()->toArray();
|
||||||
[
|
$userInfo = $this->getUserInfo();
|
||||||
'status' => PlansSceneModel::STATUS_ACTIVE
|
foreach($list as &$val){
|
||||||
]
|
$val['scenarioTags'] = json_decode($val['scenarioTags'],true);
|
||||||
)
|
$val['count'] = 0;
|
||||||
->order('sort desc')
|
$val['growth'] = "0%";
|
||||||
->select()->toArray();
|
}
|
||||||
|
unset($val);
|
||||||
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,4 +41,27 @@ class GetPlanSceneListV1Controller extends BaseController
|
|||||||
$this->getSceneList()
|
$this->getSceneList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场景详情
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function detail()
|
||||||
|
{
|
||||||
|
$id = $this->request->param('id','');
|
||||||
|
if(empty($id)){
|
||||||
|
ResponseHelper::error('参数缺失');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = PlansSceneModel::where(['status' => PlansSceneModel::STATUS_ACTIVE,'id' => $id])->find();
|
||||||
|
if(empty($data)){
|
||||||
|
ResponseHelper::error('场景不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['scenarioTags'] = json_decode($data['scenarioTags'],true);
|
||||||
|
return ResponseHelper::success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user