存客宝 React

This commit is contained in:
柳清爽
2025-03-29 16:50:39 +08:00
parent caea0b4b99
commit 7e7c199996
388 changed files with 53282 additions and 2076 deletions

View File

@@ -0,0 +1,4 @@
export default function Loading() {
return null
}

View 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>
)
}

View File

@@ -0,0 +1,4 @@
export default function Loading() {
return null
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}