存客宝 React
This commit is contained in:
4
Cunkebao/app/scenarios/phone/acquired/loading.tsx
Normal file
4
Cunkebao/app/scenarios/phone/acquired/loading.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
|
||||
179
Cunkebao/app/scenarios/phone/acquired/page.tsx
Normal file
179
Cunkebao/app/scenarios/phone/acquired/page.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { ChevronLeft, Search, Phone, MoreVertical, UserPlus, Plus } from "lucide-react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||
|
||||
interface Customer {
|
||||
id: string
|
||||
phoneNumber: string
|
||||
time: string
|
||||
status: "pending" | "added"
|
||||
question: string
|
||||
}
|
||||
|
||||
export default function PhoneAcquiredPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const [customers, setCustomers] = useState<Customer[]>([
|
||||
{
|
||||
id: "1",
|
||||
phoneNumber: "138****1234",
|
||||
time: "2024-03-18 15:30",
|
||||
status: "pending",
|
||||
question: "请问贵公司的产品有什么特点?",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
phoneNumber: "139****5678",
|
||||
time: "2024-03-18 14:15",
|
||||
status: "added",
|
||||
question: "你们的合作方式是怎样的?",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
phoneNumber: "137****9012",
|
||||
time: "2024-03-18 11:22",
|
||||
status: "added",
|
||||
question: "能详细介绍一下你们的服务吗?",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
phoneNumber: "135****3456",
|
||||
time: "2024-03-17 16:45",
|
||||
status: "added",
|
||||
question: "你们有什么优惠政策?",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
phoneNumber: "136****7890",
|
||||
time: "2024-03-17 10:20",
|
||||
status: "pending",
|
||||
question: "未识别到有效问题",
|
||||
},
|
||||
])
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
const filteredCustomers = customers.filter(
|
||||
(customer) => customer.phoneNumber.includes(searchQuery) || customer.question.includes(searchQuery),
|
||||
)
|
||||
|
||||
const handleAddFriend = (customerId: string) => {
|
||||
setCustomers(
|
||||
customers.map((customer) => (customer.id === customerId ? { ...customer, status: "added" } : customer)),
|
||||
)
|
||||
|
||||
toast({
|
||||
title: "添加成功",
|
||||
description: "已成功添加为微信好友",
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold text-blue-600">电话已获客</h1>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => router.push(`/scenarios/phone/new`)}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建计划
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="p-4 max-w-7xl mx-auto">
|
||||
<Card className="p-6 bg-white/80 backdrop-blur-sm">
|
||||
<div className="mb-4 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索电话号码或问题"
|
||||
className="pl-10"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>电话号码</TableHead>
|
||||
<TableHead>获客时间</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>首句问题</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredCustomers.map((customer) => (
|
||||
<TableRow key={customer.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar className="h-8 w-8 bg-blue-100">
|
||||
<AvatarFallback className="bg-blue-100 text-blue-600">
|
||||
<Phone className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="font-medium">{customer.phoneNumber}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{customer.time}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={customer.status === "added" ? "success" : "secondary"}>
|
||||
{customer.status === "added" ? "已添加" : "未添加"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[200px] truncate">{customer.question || "未识别到问题"}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{customer.status === "pending" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-blue-600"
|
||||
onClick={() => handleAddFriend(customer.id)}
|
||||
>
|
||||
<UserPlus className="h-4 w-4 mr-1" />
|
||||
添加好友
|
||||
</Button>
|
||||
) : (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem className="cursor-pointer">查看详情</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
4
Cunkebao/app/scenarios/phone/added/loading.tsx
Normal file
4
Cunkebao/app/scenarios/phone/added/loading.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
|
||||
133
Cunkebao/app/scenarios/phone/added/page.tsx
Normal file
133
Cunkebao/app/scenarios/phone/added/page.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { ChevronLeft, Search, User, MessageSquare, Plus } from "lucide-react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||
|
||||
interface Friend {
|
||||
id: string
|
||||
phoneNumber: string
|
||||
nickname: string
|
||||
addedTime: string
|
||||
question: string
|
||||
}
|
||||
|
||||
export default function PhoneAddedPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const [friends, setFriends] = useState<Friend[]>([
|
||||
{
|
||||
id: "1",
|
||||
phoneNumber: "139****5678",
|
||||
nickname: "张先生",
|
||||
addedTime: "2024-03-18 14:20",
|
||||
question: "你们的合作方式是怎样的?",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
phoneNumber: "137****9012",
|
||||
nickname: "李女士",
|
||||
addedTime: "2024-03-18 11:30",
|
||||
question: "能详细介绍一下你们的服务吗?",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
phoneNumber: "135****3456",
|
||||
nickname: "王经理",
|
||||
addedTime: "2024-03-17 16:50",
|
||||
question: "你们有什么优惠政策?",
|
||||
},
|
||||
])
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
const filteredFriends = friends.filter(
|
||||
(friend) =>
|
||||
friend.phoneNumber.includes(searchQuery) ||
|
||||
friend.nickname.includes(searchQuery) ||
|
||||
friend.question.includes(searchQuery),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold text-blue-600">电话已添加</h1>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => router.push(`/scenarios/phone/new`)}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建计划
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="p-4 max-w-7xl mx-auto">
|
||||
<Card className="p-6 bg-white/80 backdrop-blur-sm">
|
||||
<div className="mb-4 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索电话号码或昵称"
|
||||
className="pl-10"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>好友信息</TableHead>
|
||||
<TableHead>添加时间</TableHead>
|
||||
<TableHead>首句问题</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredFriends.map((friend) => (
|
||||
<TableRow key={friend.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar className="h-8 w-8 bg-green-100">
|
||||
<AvatarFallback className="bg-green-100 text-green-600">
|
||||
<User className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{friend.nickname}</div>
|
||||
<div className="text-sm text-gray-500">{friend.phoneNumber}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{friend.addedTime}</TableCell>
|
||||
<TableCell className="max-w-[300px] truncate">{friend.question || "未识别到问题"}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button size="sm" variant="outline">
|
||||
<MessageSquare className="h-4 w-4 mr-1" />
|
||||
发送消息
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
187
Cunkebao/app/scenarios/phone/devices/page.tsx
Normal file
187
Cunkebao/app/scenarios/phone/devices/page.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { ChevronLeft, Plus, MoreVertical, Trash2 } from "lucide-react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
|
||||
|
||||
interface Device {
|
||||
id: string
|
||||
name: string
|
||||
status: "online" | "offline"
|
||||
lastActive: string
|
||||
acquired: number
|
||||
}
|
||||
|
||||
export default function PhoneDevicesPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const [devices, setDevices] = useState<Device[]>([
|
||||
{
|
||||
id: "1",
|
||||
name: "电话设备 1",
|
||||
status: "online",
|
||||
lastActive: "2024-03-18 15:30",
|
||||
acquired: 12,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "电话设备 2",
|
||||
status: "online",
|
||||
lastActive: "2024-03-18 14:45",
|
||||
acquired: 8,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "电话设备 3",
|
||||
status: "offline",
|
||||
lastActive: "2024-03-17 10:20",
|
||||
acquired: 18,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "电话设备 4",
|
||||
status: "online",
|
||||
lastActive: "2024-03-18 16:10",
|
||||
acquired: 0,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "电话设备 5",
|
||||
status: "online",
|
||||
lastActive: "2024-03-18 13:55",
|
||||
acquired: 0,
|
||||
},
|
||||
])
|
||||
|
||||
const [isAddDeviceOpen, setIsAddDeviceOpen] = useState(false)
|
||||
|
||||
const handleRemoveDevice = (deviceId: string) => {
|
||||
const deviceToRemove = devices.find((d) => d.id === deviceId)
|
||||
if (deviceToRemove) {
|
||||
setDevices(devices.filter((d) => d.id !== deviceId))
|
||||
toast({
|
||||
title: "设备已移除",
|
||||
description: `已成功移除"${deviceToRemove.name}"`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold text-blue-600">电话获客设备</h1>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => router.push(`/scenarios/phone/new`)}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建计划
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="p-4 max-w-7xl mx-auto">
|
||||
<Card className="p-6 bg-white/80 backdrop-blur-sm">
|
||||
<div className="rounded-lg border overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>设备名称</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>最后活跃</TableHead>
|
||||
<TableHead>已获客</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{devices.map((device) => (
|
||||
<TableRow key={device.id}>
|
||||
<TableCell className="font-medium">{device.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={device.status === "online" ? "success" : "secondary"}>
|
||||
{device.status === "online" ? "在线" : "离线"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{device.lastActive}</TableCell>
|
||||
<TableCell>{device.acquired}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleRemoveDevice(device.id)}
|
||||
className="text-red-600 cursor-pointer hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
移除设备
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 添加设备对话框 */}
|
||||
<Dialog open={isAddDeviceOpen} onOpenChange={setIsAddDeviceOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加电话获客设备</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-center text-gray-500 mb-4">请选择要添加的电话设备</p>
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((num) => (
|
||||
<Card key={num} className="p-3 cursor-pointer hover:bg-gray-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">电话设备 {num + 5}</p>
|
||||
<p className="text-sm text-gray-500">最后活跃: 2024-03-18</p>
|
||||
</div>
|
||||
<Badge variant="outline">可用</Badge>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsAddDeviceOpen(false)
|
||||
toast({
|
||||
title: "设备已添加",
|
||||
description: "已成功添加电话获客设备",
|
||||
})
|
||||
}}
|
||||
>
|
||||
确认添加
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
402
Cunkebao/app/scenarios/phone/edit/[id]/page.tsx
Normal file
402
Cunkebao/app/scenarios/phone/edit/[id]/page.tsx
Normal file
@@ -0,0 +1,402 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { ChevronLeft, Phone, MessageSquare, Settings, Users } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
|
||||
export default function EditPhoneAcquisitionPlan({ params }: { params: { id: string } }) {
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [formData, setFormData] = useState({
|
||||
planName: "",
|
||||
phoneNumbers: ["400-123-4567"],
|
||||
autoAddEnabled: true,
|
||||
speechToTextEnabled: true,
|
||||
questionExtractionEnabled: true,
|
||||
greetingMessage: "",
|
||||
followUpMessages: [],
|
||||
addToGroups: [],
|
||||
tags: [],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// 模拟从API获取计划数据
|
||||
const fetchPlanData = async () => {
|
||||
try {
|
||||
// 这里应该是实际的API调用
|
||||
const mockData = {
|
||||
planName: "招商电话获客",
|
||||
phoneNumbers: ["400-123-4567"],
|
||||
autoAddEnabled: true,
|
||||
speechToTextEnabled: true,
|
||||
questionExtractionEnabled: true,
|
||||
greetingMessage: "您好,感谢您的来电咨询,我稍后会添加您为好友,为您提供更详细的资料。",
|
||||
followUpMessages: [
|
||||
"您好,我是XX公司的客服,刚才接到您的电话,现在方便聊一下吗?",
|
||||
"关于您刚才咨询的问题,我这边整理了一些资料,希望对您有所帮助。",
|
||||
],
|
||||
addToGroups: ["产品咨询群", "招商合作群"],
|
||||
tags: ["电话咨询", "招商意向"],
|
||||
}
|
||||
setFormData(mockData)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "加载失败",
|
||||
description: "获取计划数据失败,请重试",
|
||||
variant: "destructive",
|
||||
})
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchPlanData()
|
||||
}, [])
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 这里应该是实际的API调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "电话获客计划已更新",
|
||||
})
|
||||
router.push(`/scenarios/phone`)
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "更新计划失败,请重试",
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddPhoneNumber = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
phoneNumbers: [...formData.phoneNumbers, ""],
|
||||
})
|
||||
}
|
||||
|
||||
const handlePhoneNumberChange = (index: number, value: string) => {
|
||||
const updatedPhoneNumbers = [...formData.phoneNumbers]
|
||||
updatedPhoneNumbers[index] = value
|
||||
setFormData({
|
||||
...formData,
|
||||
phoneNumbers: updatedPhoneNumbers,
|
||||
})
|
||||
}
|
||||
|
||||
const handleRemovePhoneNumber = (index: number) => {
|
||||
const updatedPhoneNumbers = [...formData.phoneNumbers]
|
||||
updatedPhoneNumbers.splice(index, 1)
|
||||
setFormData({
|
||||
...formData,
|
||||
phoneNumbers: updatedPhoneNumbers,
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddFollowUpMessage = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
followUpMessages: [...formData.followUpMessages, ""],
|
||||
})
|
||||
}
|
||||
|
||||
const handleFollowUpMessageChange = (index: number, value: string) => {
|
||||
const updatedMessages = [...formData.followUpMessages]
|
||||
updatedMessages[index] = value
|
||||
setFormData({
|
||||
...formData,
|
||||
followUpMessages: updatedMessages,
|
||||
})
|
||||
}
|
||||
|
||||
const handleRemoveFollowUpMessage = (index: number) => {
|
||||
const updatedMessages = [...formData.followUpMessages]
|
||||
updatedMessages.splice(index, 1)
|
||||
setFormData({
|
||||
...formData,
|
||||
followUpMessages: updatedMessages,
|
||||
})
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
|
||||
<p className="mt-2 text-gray-600">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<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.back()}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="ml-2 text-lg font-medium">编辑电话获客计划</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 mb-4">
|
||||
<TabsTrigger value="basic">
|
||||
<Phone className="h-4 w-4 mr-2" />
|
||||
基础设置
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="message">
|
||||
<MessageSquare className="h-4 w-4 mr-2" />
|
||||
消息设置
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="advanced">
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
高级设置
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="basic" className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="planName">计划名称</Label>
|
||||
<Input
|
||||
id="planName"
|
||||
value={formData.planName}
|
||||
onChange={(e) => setFormData({ ...formData, planName: e.target.value })}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>接听电话</Label>
|
||||
<div className="space-y-2 mt-1">
|
||||
{formData.phoneNumbers.map((phone, index) => (
|
||||
<div key={index} className="flex items-center space-x-2">
|
||||
<Input
|
||||
value={phone}
|
||||
onChange={(e) => handlePhoneNumberChange(index, e.target.value)}
|
||||
placeholder="输入电话号码"
|
||||
/>
|
||||
{formData.phoneNumbers.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemovePhoneNumber(index)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" size="sm" onClick={handleAddPhoneNumber}>
|
||||
添加电话号码
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="auto-add">自动添加客户</Label>
|
||||
<p className="text-xs text-gray-500">来电后自动将客户添加为微信好友</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="auto-add"
|
||||
checked={formData.autoAddEnabled}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, autoAddEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="speech-to-text">语音转文字</Label>
|
||||
<p className="text-xs text-gray-500">自动将通话内容转换为文字记录</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="speech-to-text"
|
||||
checked={formData.speechToTextEnabled}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, speechToTextEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="question-extraction">问题提取</Label>
|
||||
<p className="text-xs text-gray-500">自动从通话中提取客户的首句问题</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="question-extraction"
|
||||
checked={formData.questionExtractionEnabled}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, questionExtractionEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="message" className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="greeting">添加好友后的问候语</Label>
|
||||
<Textarea
|
||||
id="greeting"
|
||||
value={formData.greetingMessage}
|
||||
onChange={(e) => setFormData({ ...formData, greetingMessage: e.target.value })}
|
||||
placeholder="输入添加好友后的问候语"
|
||||
className="mt-1 min-h-[100px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>后续跟进消息</Label>
|
||||
<div className="space-y-2 mt-1">
|
||||
{formData.followUpMessages.map((message, index) => (
|
||||
<div key={index} className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm">消息 {index + 1}</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveFollowUpMessage(index)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Textarea
|
||||
value={message}
|
||||
onChange={(e) => handleFollowUpMessageChange(index, e.target.value)}
|
||||
placeholder="输入跟进消息内容"
|
||||
className="min-h-[80px]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" size="sm" onClick={handleAddFollowUpMessage}>
|
||||
添加跟进消息
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="advanced" className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>添加到群组</Label>
|
||||
<Select
|
||||
value={formData.addToGroups.length > 0 ? "selected" : "none"}
|
||||
onValueChange={(value) => {
|
||||
if (value === "none") {
|
||||
setFormData({ ...formData, addToGroups: [] })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="选择群组" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">不添加到群组</SelectItem>
|
||||
<SelectItem value="selected">已选择 {formData.addToGroups.length} 个群组</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{formData.addToGroups.length > 0 && (
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{formData.addToGroups.map((group, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center bg-blue-50 text-blue-700 rounded-full px-3 py-1"
|
||||
>
|
||||
<Users className="h-3 w-3 mr-1" />
|
||||
<span className="text-sm">{group}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="ml-1 p-0 h-4 w-4 rounded-full"
|
||||
onClick={() => {
|
||||
const updatedGroups = [...formData.addToGroups]
|
||||
updatedGroups.splice(index, 1)
|
||||
setFormData({ ...formData, addToGroups: updatedGroups })
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>标签设置</Label>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{formData.tags.map((tag, index) => (
|
||||
<div key={index} className="flex items-center bg-gray-100 rounded-full px-3 py-1">
|
||||
<span className="text-sm">{tag}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="ml-1 p-0 h-4 w-4 rounded-full"
|
||||
onClick={() => {
|
||||
const updatedTags = [...formData.tags]
|
||||
updatedTags.splice(index, 1)
|
||||
setFormData({ ...formData, tags: updatedTags })
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Input
|
||||
placeholder="添加标签"
|
||||
className="w-32"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value) {
|
||||
e.preventDefault()
|
||||
setFormData({
|
||||
...formData,
|
||||
tags: [...formData.tags, e.currentTarget.value],
|
||||
})
|
||||
e.currentTarget.value = ""
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<div className="sticky bottom-0 left-0 right-0 bg-white border-t p-4">
|
||||
<Button className="w-full" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
123
Cunkebao/app/scenarios/phone/page.tsx
Normal file
123
Cunkebao/app/scenarios/phone/page.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { ChevronLeft, Plus } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import { ExpandableAcquisitionCard } from "@/components/acquisition/ExpandableAcquisitionCard"
|
||||
import Link from "next/link"
|
||||
import { DeviceTreeChart } from "@/app/components/acquisition/DeviceTreeChart"
|
||||
|
||||
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 }[]
|
||||
dailyData: { date: string; acquired: number; added: number }[]
|
||||
}
|
||||
|
||||
export default function PhoneAcquisitionPage() {
|
||||
const router = useRouter()
|
||||
const channel = "phone"
|
||||
|
||||
const [tasks, setTasks] = useState<Task[]>([
|
||||
{
|
||||
id: "1",
|
||||
name: "电话招商获客计划",
|
||||
status: "running",
|
||||
stats: {
|
||||
devices: 5,
|
||||
acquired: 38,
|
||||
added: 31,
|
||||
},
|
||||
lastUpdated: "2024-03-18 15:30",
|
||||
executionTime: "2024-03-18 15:30",
|
||||
nextExecutionTime: "预计30分钟后",
|
||||
trend: Array.from({ length: 7 }, (_, i) => ({
|
||||
date: `3月${String(i + 12)}日`,
|
||||
customers: Math.floor(Math.random() * 10) + 5,
|
||||
})),
|
||||
dailyData: [
|
||||
{ date: "3/12", acquired: 12, added: 8 },
|
||||
{ date: "3/13", acquired: 15, added: 10 },
|
||||
{ date: "3/14", acquired: 8, added: 6 },
|
||||
{ date: "3/15", acquired: 10, added: 7 },
|
||||
{ date: "3/16", acquired: 14, added: 11 },
|
||||
{ date: "3/17", acquired: 9, added: 7 },
|
||||
{ date: "3/18", acquired: 11, added: 9 },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const handleCopyPlan = (taskId: string) => {
|
||||
const taskToCopy = tasks.find((task) => task.id === taskId)
|
||||
if (taskToCopy) {
|
||||
const newTask = {
|
||||
...taskToCopy,
|
||||
id: `${Date.now()}`,
|
||||
name: `${taskToCopy.name} (副本)`,
|
||||
status: "paused" as const,
|
||||
}
|
||||
setTasks([...tasks, newTask])
|
||||
toast({
|
||||
title: "计划已复制",
|
||||
description: `已成功复制"${taskToCopy.name}"`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeletePlan = (taskId: string) => {
|
||||
const taskToDelete = tasks.find((t) => t.id === taskId)
|
||||
if (taskToDelete) {
|
||||
setTasks(tasks.filter((t) => t.id !== taskId))
|
||||
toast({
|
||||
title: "计划已删除",
|
||||
description: `已成功删除"${taskToDelete.name}"`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
<div className="flex items-center p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold text-blue-600">电话获客</h1>
|
||||
</div>
|
||||
<Link href="/scenarios/phone/new" className="ml-auto">
|
||||
<Button className="flex items-center gap-1">
|
||||
<Plus className="h-4 w-4" />
|
||||
新建计划
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="p-4 max-w-7xl mx-auto">
|
||||
{tasks.map((task) => (
|
||||
<ExpandableAcquisitionCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
channel={channel}
|
||||
onCopy={handleCopyPlan}
|
||||
onDelete={handleDeletePlan}
|
||||
/>
|
||||
))}
|
||||
<DeviceTreeChart />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user