存客宝 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,344 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowLeft, Download, Mail, Share2, Tag, Users, Database, BarChart2, PieChart } from "lucide-react"
import { useRouter } from "next/navigation"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface StrategyReport {
id: string
name: string
deviceNames: string[]
trafficPoolNames: string[]
createdAt: string
completedAt: string
strategyType: "jd" | "yisi" | "database" | "custom"
strategyName: string
trafficPoolSize: number
optimizedUsers: number
summary: string
userSegments: {
name: string
count: number
percentage: number
}[]
userAttributes: {
[key: string]: {
name: string
count: number
percentage: number
}[]
}
recommendations: string[]
}
export default function StrategyReportPage({ params }: { params: { id: string } }) {
const router = useRouter()
const [report, setReport] = useState<StrategyReport | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// 模拟加载报告数据
const fetchReport = async () => {
await new Promise((resolve) => setTimeout(resolve, 1500))
const mockReport: StrategyReport = {
id: params.id,
name: "京东会员优化",
deviceNames: ["设备1", "设备2"],
trafficPoolNames: ["微信好友池-设备1", "设备流量池-设备1"],
createdAt: "2023-12-15T10:30:00Z",
completedAt: "2023-12-15T11:45:00Z",
strategyType: "jd",
strategyName: "京东会员识别",
trafficPoolSize: 287,
optimizedUsers: 142,
summary:
"通过京东会员识别策略在287位用户中成功识别出142位京东会员用户占比49.5%。其中PLUS会员38位VIP会员56位普通会员48位。这些用户主要集中在电子产品和美妆护肤品类消费能力中上。建议针对PLUS会员和VIP会员进行高端产品推广对普通会员提供会员升级优惠。",
userSegments: [
{ name: "PLUS会员", count: 38, percentage: 27 },
{ name: "VIP会员", count: 56, percentage: 39 },
{ name: "普通会员", count: 48, percentage: 34 },
],
userAttributes: {
: [
{ name: "电子产品", count: 52, percentage: 37 },
{ name: "美妆护肤", count: 43, percentage: 30 },
{ name: "家居生活", count: 28, percentage: 20 },
{ name: "服装鞋包", count: 19, percentage: 13 },
],
: [
{ name: "高", count: 35, percentage: 25 },
{ name: "中高", count: 48, percentage: 34 },
{ name: "中", count: 39, percentage: 27 },
{ name: "中低", count: 20, percentage: 14 },
],
: [
{ name: "高度活跃", count: 42, percentage: 30 },
{ name: "中度活跃", count: 63, percentage: 44 },
{ name: "低度活跃", count: 37, percentage: 26 },
],
},
recommendations: [
"针对PLUS会员推送高端电子产品和限量版美妆产品",
"为VIP会员提供专属优惠券和新品预售资格",
"向普通会员推广PLUS会员权益提供升级优惠",
"根据用户购物偏好进行精准内容推送",
"对高消费能力用户提供专属客服和VIP服务",
],
}
setReport(mockReport)
setIsLoading(false)
}
fetchReport()
}, [params.id])
const getStrategyTypeLabel = (type: string) => {
switch (type) {
case "jd":
return "京东会员"
case "yisi":
return "易思接口"
case "database":
return "数据库匹配"
case "custom":
return "自定义策略"
default:
return "未知类型"
}
}
return (
<div className="flex flex-col h-screen max-h-screen bg-gray-50">
{/* 头部 */}
<div className="bg-white p-4 border-b flex items-center justify-between">
<div className="flex items-center">
<Button variant="ghost" size="icon" onClick={() => router.back()} className="mr-2">
<ArrowLeft className="h-5 w-5" />
</Button>
<h1 className="text-xl font-semibold"></h1>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Tag className="h-4 w-4" />
<span className="hidden sm:inline"></span>
</Button>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Mail className="h-4 w-4" />
<span className="hidden sm:inline"></span>
</Button>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Download className="h-4 w-4" />
<span className="hidden sm:inline"></span>
</Button>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Share2 className="h-4 w-4" />
<span className="hidden sm:inline"></span>
</Button>
</div>
</div>
{/* 主内容区域 */}
<div className="flex-1 p-4 overflow-auto">
{isLoading ? (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : report ? (
<div className="max-w-5xl mx-auto space-y-6">
{/* 报告头部 */}
<Card>
<CardHeader className="pb-2">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2">
<div>
<CardTitle className="text-xl">{report.name}</CardTitle>
<p className="text-sm text-gray-500 mt-1">{new Date(report.completedAt).toLocaleString()} </p>
</div>
<Badge className="self-start sm:self-auto bg-green-100 text-green-800 border-green-200">
{getStrategyTypeLabel(report.strategyType)}
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
<Users className="h-5 w-5 text-blue-600" />
</div>
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-semibold">{report.trafficPoolSize} </p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center">
<Database className="h-5 w-5 text-green-600" />
</div>
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-semibold">{report.optimizedUsers} </p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center">
<BarChart2 className="h-5 w-5 text-purple-600" />
</div>
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-semibold">
{Math.round((report.optimizedUsers / report.trafficPoolSize) * 100)}%
</p>
</div>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-medium mb-2"></h3>
<p className="text-sm text-gray-700">{report.summary}</p>
</div>
</CardContent>
</Card>
{/* 详细分析 */}
<Tabs defaultValue="segments" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-6">
<TabsTrigger value="segments"></TabsTrigger>
<TabsTrigger value="attributes"></TabsTrigger>
</TabsList>
<TabsContent value="segments" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="font-medium mb-3"></h3>
<div className="space-y-3">
{report.userSegments.map((segment, index) => (
<div key={index}>
<div className="flex justify-between text-sm mb-1">
<span>{segment.name}</span>
<span className="font-medium">
{segment.count} ({segment.percentage}%)
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${segment.percentage}%` }}
></div>
</div>
</div>
))}
</div>
</div>
<div>
<h3 className="font-medium mb-3"></h3>
<div className="flex items-center h-40 justify-center">
<PieChart className="h-32 w-32 text-gray-400" />
</div>
<div className="grid grid-cols-3 text-center mt-2">
{report.userSegments.map((segment, index) => (
<div key={index}>
<div className="text-sm text-gray-500">{segment.name}</div>
<div className="font-medium">
{segment.count} ({segment.percentage}%)
</div>
</div>
))}
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{report.recommendations.map((recommendation, index) => (
<div key={index} className="flex items-start gap-2">
<div className="w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-sm font-medium text-blue-600">{index + 1}</span>
</div>
<p className="text-gray-700">{recommendation}</p>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="attributes" className="space-y-4">
{Object.entries(report.userAttributes).map(([category, attributes], categoryIndex) => (
<Card key={categoryIndex}>
<CardHeader>
<CardTitle className="text-lg">{category}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="font-medium mb-3">{category}</h3>
<div className="space-y-3">
{attributes.map((attribute, index) => (
<div key={index}>
<div className="flex justify-between text-sm mb-1">
<span>{attribute.name}</span>
<span className="font-medium">
{attribute.count} ({attribute.percentage}%)
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-green-500 h-2 rounded-full"
style={{ width: `${attribute.percentage}%` }}
></div>
</div>
</div>
))}
</div>
</div>
<div>
<h3 className="font-medium mb-3"></h3>
<div className="flex items-center h-40 justify-center">
<PieChart className="h-32 w-32 text-gray-400" />
</div>
<div className="grid grid-cols-2 text-center mt-2 gap-2">
{attributes.map((attribute, index) => (
<div key={index}>
<div className="text-sm text-gray-500">{attribute.name}</div>
<div className="font-medium">
{attribute.count} ({attribute.percentage}%)
</div>
</div>
))}
</div>
</div>
</div>
</CardContent>
</Card>
))}
</TabsContent>
</Tabs>
</div>
) : (
<div className="text-center py-12">
<p className="text-gray-500"></p>
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,235 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge"
import { Smartphone, Database, Calendar, Settings, Tag, Mail } from "lucide-react"
import { format } from "date-fns"
interface ConfirmStrategyProps {
formData: any
updateFormData: (data: any) => void
onSubmit: () => void
onBack: () => void
}
export function ConfirmStrategy({ formData, updateFormData, onSubmit, onBack }: ConfirmStrategyProps) {
const [planName, setPlanName] = useState(formData.name || "")
const [isSubmitting, setIsSubmitting] = useState(false)
const getStrategyTypeIcon = (type: string) => {
switch (type) {
case "jd":
return (
<div className="w-8 h-8 rounded-full bg-orange-100 flex items-center justify-center flex-shrink-0">
<Database className="h-4 w-4 text-orange-600" />
</div>
)
case "yisi":
return (
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
<Database className="h-4 w-4 text-blue-600" />
</div>
)
case "database":
return (
<div className="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<Database className="h-4 w-4 text-green-600" />
</div>
)
case "custom":
return (
<div className="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center flex-shrink-0">
<Settings className="h-4 w-4 text-purple-600" />
</div>
)
default:
return (
<div className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
<Settings className="h-4 w-4 text-gray-600" />
</div>
)
}
}
const getStrategyTypeLabel = (type: string) => {
switch (type) {
case "jd":
return "京东会员"
case "yisi":
return "易思接口"
case "database":
return "数据库匹配"
case "custom":
return "自定义策略"
default:
return "未知类型"
}
}
const handleSubmit = async () => {
updateFormData({
name: planName,
})
setIsSubmitting(true)
await onSubmit()
setIsSubmitting(false)
}
return (
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold mb-1"></h2>
<p className="text-gray-500 text-sm"></p>
</div>
<div>
<Label htmlFor="planName" className="text-base font-medium">
</Label>
<Input
id="planName"
value={planName}
onChange={(e) => setPlanName(e.target.value)}
placeholder="输入策略名称"
className="mt-1"
/>
</div>
<div className="bg-gray-50 p-4 rounded-lg space-y-4">
<h3 className="font-medium"></h3>
<div className="flex items-start space-x-3">
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
<Smartphone className="h-4 w-4 text-blue-600" />
</div>
<div>
<p className="font-medium"></p>
<p className="text-sm text-gray-600">{formData.deviceNames.join(", ")}</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<Database className="h-4 w-4 text-green-600" />
</div>
<div>
<p className="font-medium"></p>
<div className="space-y-1">
{formData.trafficPoolNames.map((name: string, index: number) => (
<p key={index} className="text-sm text-gray-600">
{name}
</p>
))}
<p className="text-sm font-medium"> {formData.trafficPoolSize} </p>
</div>
</div>
</div>
<div className="flex items-start space-x-3">
{getStrategyTypeIcon(formData.strategyType)}
<div>
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
{getStrategyTypeLabel(formData.strategyType)} - {formData.strategyName}
</p>
{formData.strategyType === "jd" && (
<div className="mt-1">
<Badge variant="outline" className="text-xs">
:{" "}
{formData.strategyConfig.memberLevel === "all"
? "所有会员"
: formData.strategyConfig.memberLevel === "plus"
? "PLUS会员"
: formData.strategyConfig.memberLevel === "vip"
? "VIP会员"
: "普通会员"}
</Badge>
</div>
)}
{formData.strategyType === "yisi" && (
<div className="mt-1">
<Badge variant="outline" className="text-xs">
:{" "}
{formData.strategyConfig.analysisDepth === "basic"
? "基础分析"
: formData.strategyConfig.analysisDepth === "standard"
? "标准分析"
: "深度分析"}
</Badge>
</div>
)}
{formData.strategyType === "database" && (
<div className="mt-1">
<Badge variant="outline" className="text-xs">
: {formData.strategyConfig.tableName}
</Badge>
<Badge variant="outline" className="text-xs ml-1">
: {formData.strategyConfig.matchFields}
</Badge>
</div>
)}
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center flex-shrink-0">
<Calendar className="h-4 w-4 text-purple-600" />
</div>
<div>
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
{formData.executionConfig.scheduleType === "immediate"
? "提交后立即执行"
: `定时执行: ${format(new Date(formData.executionConfig.scheduledTime), "yyyy-MM-dd HH:mm")}`}
</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-8 h-8 rounded-full bg-amber-100 flex items-center justify-center flex-shrink-0">
<Settings className="h-4 w-4 text-amber-600" />
</div>
<div>
<p className="font-medium"></p>
<div className="flex flex-wrap gap-2 mt-1">
{formData.executionConfig.notifyOnComplete && (
<Badge variant="outline" className="text-xs">
</Badge>
)}
{formData.executionConfig.importTags && (
<Badge variant="outline" className="text-xs flex items-center gap-1">
<Tag className="h-3 w-3" />
</Badge>
)}
{formData.executionConfig.sendToWechat && (
<Badge variant="outline" className="text-xs flex items-center gap-1">
<Mail className="h-3 w-3" />
: {formData.executionConfig.wechatId}
</Badge>
)}
</div>
</div>
</div>
</div>
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onBack}>
</Button>
<Button onClick={handleSubmit} disabled={!planName.trim() || isSubmitting}>
{isSubmitting ? "提交中..." : "开始优化"}
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,203 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { format } from "date-fns"
import { CalendarIcon, Clock } from "lucide-react"
interface ExecutionSetupProps {
formData: any
updateFormData: (data: any) => void
onNext: () => void
onBack: () => void
}
export function ExecutionSetup({ formData, updateFormData, onNext, onBack }: ExecutionSetupProps) {
const [scheduleType, setScheduleType] = useState<string>(formData.executionConfig?.scheduleType || "immediate")
const [scheduledDate, setScheduledDate] = useState<Date | undefined>(
formData.executionConfig?.scheduledTime ? new Date(formData.executionConfig.scheduledTime) : undefined,
)
const [scheduledTime, setScheduledTime] = useState<string>(
formData.executionConfig?.scheduledTime
? format(new Date(formData.executionConfig.scheduledTime), "HH:mm")
: "09:00",
)
const [notifyOnComplete, setNotifyOnComplete] = useState<boolean>(
formData.executionConfig?.notifyOnComplete !== undefined ? formData.executionConfig.notifyOnComplete : true,
)
const [importTags, setImportTags] = useState<boolean>(
formData.executionConfig?.importTags !== undefined ? formData.executionConfig.importTags : true,
)
const [sendToWechat, setSendToWechat] = useState<boolean>(
formData.executionConfig?.sendToWechat !== undefined ? formData.executionConfig.sendToWechat : false,
)
const [wechatId, setWechatId] = useState<string>(formData.executionConfig?.wechatId || "")
const handleContinue = () => {
let scheduledTimeString = ""
if (scheduleType === "scheduled" && scheduledDate) {
const [hours, minutes] = scheduledTime.split(":").map(Number)
const dateObj = new Date(scheduledDate)
dateObj.setHours(hours, minutes)
scheduledTimeString = dateObj.toISOString()
}
updateFormData({
executionConfig: {
scheduleType,
scheduledTime: scheduledTimeString,
notifyOnComplete,
importTags,
sendToWechat,
wechatId: sendToWechat ? wechatId : "",
},
})
onNext()
}
return (
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold mb-1"></h2>
<p className="text-gray-500 text-sm"></p>
</div>
<div className="space-y-4">
<Label className="text-base font-medium"></Label>
<RadioGroup value={scheduleType} onValueChange={setScheduleType} className="space-y-3">
<div className="flex items-start space-x-2">
<RadioGroupItem value="immediate" id="immediate" />
<div className="grid gap-1.5">
<Label htmlFor="immediate" className="font-medium">
</Label>
<p className="text-sm text-gray-500"></p>
</div>
</div>
<div className="flex items-start space-x-2">
<RadioGroupItem value="scheduled" id="scheduled" />
<div className="grid gap-1.5">
<Label htmlFor="scheduled" className="font-medium">
</Label>
<p className="text-sm text-gray-500"></p>
{scheduleType === "scheduled" && (
<div className="flex flex-col sm:flex-row gap-3 mt-2">
<div>
<Label htmlFor="date" className="text-sm">
</Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-start text-left font-normal mt-1" id="date">
<CalendarIcon className="mr-2 h-4 w-4" />
{scheduledDate ? format(scheduledDate, "yyyy-MM-dd") : "选择日期"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={scheduledDate} onSelect={setScheduledDate} initialFocus />
</PopoverContent>
</Popover>
</div>
<div>
<Label htmlFor="time" className="text-sm">
</Label>
<div className="flex items-center mt-1">
<Clock className="mr-2 h-4 w-4 text-gray-400" />
<Input
id="time"
type="time"
value={scheduledTime}
onChange={(e) => setScheduledTime(e.target.value)}
className="w-full"
/>
</div>
</div>
</div>
)}
</div>
</div>
</RadioGroup>
</div>
<div className="space-y-4">
<Label className="text-base font-medium"></Label>
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Checkbox
id="notify"
checked={notifyOnComplete}
onCheckedChange={(checked) => setNotifyOnComplete(checked as boolean)}
/>
<Label htmlFor="notify" className="cursor-pointer">
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="import"
checked={importTags}
onCheckedChange={(checked) => setImportTags(checked as boolean)}
/>
<Label htmlFor="import" className="cursor-pointer">
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="wechat"
checked={sendToWechat}
onCheckedChange={(checked) => setSendToWechat(checked as boolean)}
/>
<Label htmlFor="wechat" className="cursor-pointer">
</Label>
</div>
{sendToWechat && (
<div className="pl-6 pt-2">
<Label htmlFor="wechatId" className="text-sm">
</Label>
<Input
id="wechatId"
value={wechatId}
onChange={(e) => setWechatId(e.target.value)}
placeholder="输入接收报告的微信号"
className="mt-1"
/>
</div>
)}
</div>
</div>
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onBack}>
</Button>
<Button
onClick={handleContinue}
disabled={(scheduleType === "scheduled" && !scheduledDate) || (sendToWechat && !wechatId)}
>
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,58 @@
import { CheckIcon } from "lucide-react"
interface Step {
number: number
title: string
}
interface StepIndicatorProps {
steps: Step[]
currentStep: number
}
export function StepIndicator({ steps, currentStep }: StepIndicatorProps) {
return (
<div className="flex items-center justify-between">
{steps.map((step, index) => {
const isCompleted = currentStep > step.number
const isCurrent = currentStep === step.number
return (
<div key={step.number} className="flex items-center">
{/* 步骤连接线 */}
{index > 0 && <div className={`h-1 w-full ${isCompleted ? "bg-blue-500" : "bg-gray-200"}`} />}
{/* 步骤圆点 */}
<div className="relative flex items-center justify-center">
<div
className={`flex h-8 w-8 items-center justify-center rounded-full border-2 ${
isCompleted
? "border-blue-500 bg-blue-500"
: isCurrent
? "border-blue-500 bg-white"
: "border-gray-300 bg-white"
}`}
>
{isCompleted ? (
<CheckIcon className="h-5 w-5 text-white" />
) : (
<span className={`text-sm font-medium ${isCurrent ? "text-blue-500" : "text-gray-500"}`}>
{step.number}
</span>
)}
</div>
{/* 步骤标题 */}
<div className="absolute top-10 whitespace-nowrap">
<p className={`text-sm font-medium ${isCompleted || isCurrent ? "text-blue-500" : "text-gray-500"}`}>
{step.title}
</p>
</div>
</div>
</div>
)
})}
</div>
)
}

View File

@@ -0,0 +1,322 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Card, CardContent } from "@/components/ui/card"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { ShoppingBag, Database, BarChart, Settings } from "lucide-react"
interface StrategySelectionProps {
formData: any
updateFormData: (data: any) => void
onNext: () => void
onBack: () => void
}
export function StrategySelection({ formData, updateFormData, onNext, onBack }: StrategySelectionProps) {
const [strategyType, setStrategyType] = useState<string>(formData.strategyType || "")
const [jdConfig, setJdConfig] = useState({
username: formData.strategyConfig?.username || "",
password: formData.strategyConfig?.password || "",
memberLevel: formData.strategyConfig?.memberLevel || "all",
})
const [yisiConfig, setYisiConfig] = useState({
apiKey: formData.strategyConfig?.apiKey || "",
apiEndpoint: formData.strategyConfig?.apiEndpoint || "https://api.yisi.com/user-profile",
analysisDepth: formData.strategyConfig?.analysisDepth || "basic",
})
const [dbConfig, setDbConfig] = useState({
connectionString: formData.strategyConfig?.connectionString || "",
tableName: formData.strategyConfig?.tableName || "",
matchFields: formData.strategyConfig?.matchFields || "phone,wechatId",
})
const [customConfig, setCustomConfig] = useState({
name: formData.strategyConfig?.name || "",
description: formData.strategyConfig?.description || "",
})
const getStrategyName = () => {
switch (strategyType) {
case "jd":
return "京东会员识别"
case "yisi":
return "易思用户画像"
case "database":
return "数据库客户匹配"
case "custom":
return customConfig.name || "自定义策略"
default:
return ""
}
}
const getStrategyConfig = () => {
switch (strategyType) {
case "jd":
return jdConfig
case "yisi":
return yisiConfig
case "database":
return dbConfig
case "custom":
return customConfig
default:
return {}
}
}
const handleContinue = () => {
updateFormData({
strategyType,
strategyName: getStrategyName(),
strategyConfig: getStrategyConfig(),
})
onNext()
}
return (
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold mb-1"></h2>
<p className="text-gray-500 text-sm"></p>
</div>
<RadioGroup
value={strategyType}
onValueChange={setStrategyType}
className="grid grid-cols-1 md:grid-cols-2 gap-4"
>
<div>
<RadioGroupItem value="jd" id="jd" className="peer sr-only" />
<Label
htmlFor="jd"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-blue-500 [&:has([data-state=checked])]:border-blue-500 cursor-pointer"
>
<ShoppingBag className="mb-3 h-6 w-6 text-orange-500" />
<div className="text-center">
<p className="font-medium"></p>
<p className="text-sm text-gray-500"></p>
</div>
</Label>
</div>
<div>
<RadioGroupItem value="yisi" id="yisi" className="peer sr-only" />
<Label
htmlFor="yisi"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-blue-500 [&:has([data-state=checked])]:border-blue-500 cursor-pointer"
>
<BarChart className="mb-3 h-6 w-6 text-blue-500" />
<div className="text-center">
<p className="font-medium"></p>
<p className="text-sm text-gray-500">API分析用户画像</p>
</div>
</Label>
</div>
<div>
<RadioGroupItem value="database" id="database" className="peer sr-only" />
<Label
htmlFor="database"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-blue-500 [&:has([data-state=checked])]:border-blue-500 cursor-pointer"
>
<Database className="mb-3 h-6 w-6 text-green-500" />
<div className="text-center">
<p className="font-medium"></p>
<p className="text-sm text-gray-500"></p>
</div>
</Label>
</div>
<div>
<RadioGroupItem value="custom" id="custom" className="peer sr-only" />
<Label
htmlFor="custom"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-blue-500 [&:has([data-state=checked])]:border-blue-500 cursor-pointer"
>
<Settings className="mb-3 h-6 w-6 text-purple-500" />
<div className="text-center">
<p className="font-medium"></p>
<p className="text-sm text-gray-500"></p>
</div>
</Label>
</div>
</RadioGroup>
{strategyType === "jd" && (
<Card>
<CardContent className="p-4 space-y-4">
<h3 className="font-medium"></h3>
<div className="space-y-2">
<Label htmlFor="jd-username"></Label>
<Input
id="jd-username"
value={jdConfig.username}
onChange={(e) => setJdConfig({ ...jdConfig, username: e.target.value })}
placeholder="输入京东账号"
/>
</div>
<div className="space-y-2">
<Label htmlFor="jd-password"></Label>
<Input
id="jd-password"
type="password"
value={jdConfig.password}
onChange={(e) => setJdConfig({ ...jdConfig, password: e.target.value })}
placeholder="输入京东密码"
/>
</div>
<div className="space-y-2">
<Label htmlFor="jd-level"></Label>
<select
id="jd-level"
value={jdConfig.memberLevel}
onChange={(e) => setJdConfig({ ...jdConfig, memberLevel: e.target.value })}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
>
<option value="all"></option>
<option value="plus">PLUS会员</option>
<option value="vip">VIP会员</option>
<option value="regular"></option>
</select>
</div>
</CardContent>
</Card>
)}
{strategyType === "yisi" && (
<Card>
<CardContent className="p-4 space-y-4">
<h3 className="font-medium"></h3>
<div className="space-y-2">
<Label htmlFor="yisi-apikey">API密钥</Label>
<Input
id="yisi-apikey"
value={yisiConfig.apiKey}
onChange={(e) => setYisiConfig({ ...yisiConfig, apiKey: e.target.value })}
placeholder="输入易思API密钥"
/>
</div>
<div className="space-y-2">
<Label htmlFor="yisi-endpoint">API端点</Label>
<Input
id="yisi-endpoint"
value={yisiConfig.apiEndpoint}
onChange={(e) => setYisiConfig({ ...yisiConfig, apiEndpoint: e.target.value })}
placeholder="输入API端点URL"
/>
</div>
<div className="space-y-2">
<Label htmlFor="yisi-depth"></Label>
<select
id="yisi-depth"
value={yisiConfig.analysisDepth}
onChange={(e) => setYisiConfig({ ...yisiConfig, analysisDepth: e.target.value })}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
>
<option value="basic"></option>
<option value="standard"></option>
<option value="deep"></option>
</select>
</div>
</CardContent>
</Card>
)}
{strategyType === "database" && (
<Card>
<CardContent className="p-4 space-y-4">
<h3 className="font-medium"></h3>
<div className="space-y-2">
<Label htmlFor="db-connection"></Label>
<Input
id="db-connection"
value={dbConfig.connectionString}
onChange={(e) => setDbConfig({ ...dbConfig, connectionString: e.target.value })}
placeholder="输入数据库连接字符串"
/>
</div>
<div className="space-y-2">
<Label htmlFor="db-table"></Label>
<Input
id="db-table"
value={dbConfig.tableName}
onChange={(e) => setDbConfig({ ...dbConfig, tableName: e.target.value })}
placeholder="输入表名"
/>
</div>
<div className="space-y-2">
<Label htmlFor="db-fields"></Label>
<Input
id="db-fields"
value={dbConfig.matchFields}
onChange={(e) => setDbConfig({ ...dbConfig, matchFields: e.target.value })}
placeholder="输入匹配字段,用逗号分隔"
/>
<p className="text-xs text-gray-500">phone,wechatId</p>
</div>
</CardContent>
</Card>
)}
{strategyType === "custom" && (
<Card>
<CardContent className="p-4 space-y-4">
<h3 className="font-medium"></h3>
<div className="space-y-2">
<Label htmlFor="custom-name"></Label>
<Input
id="custom-name"
value={customConfig.name}
onChange={(e) => setCustomConfig({ ...customConfig, name: e.target.value })}
placeholder="输入策略名称"
/>
</div>
<div className="space-y-2">
<Label htmlFor="custom-desc"></Label>
<textarea
id="custom-desc"
value={customConfig.description}
onChange={(e) => setCustomConfig({ ...customConfig, description: e.target.value })}
placeholder="输入策略描述"
className="w-full min-h-[100px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
/>
</div>
</CardContent>
</Card>
)}
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onBack}>
</Button>
<Button
onClick={handleContinue}
disabled={
!strategyType ||
(strategyType === "jd" && (!jdConfig.username || !jdConfig.password)) ||
(strategyType === "yisi" && !yisiConfig.apiKey) ||
(strategyType === "database" && (!dbConfig.connectionString || !dbConfig.tableName)) ||
(strategyType === "custom" && !customConfig.name)
}
>
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,294 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { Card, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Search, Smartphone, Users } from "lucide-react"
interface Device {
id: string
name: string
status: "online" | "offline"
}
interface TrafficPool {
id: string
name: string
deviceId: string
deviceName: string
type: "wechat" | "device"
userCount: number
lastUpdated: string
}
interface TrafficPoolSelectionProps {
formData: any
updateFormData: (data: any) => void
onNext: () => void
}
export function TrafficPoolSelection({ formData, updateFormData, onNext }: TrafficPoolSelectionProps) {
const [devices, setDevices] = useState<Device[]>([])
const [trafficPools, setTrafficPools] = useState<TrafficPool[]>([])
const [selectedDeviceIds, setSelectedDeviceIds] = useState<string[]>(formData.deviceIds || [])
const [selectedPoolIds, setSelectedPoolIds] = useState<string[]>(formData.trafficPoolIds || [])
const [searchQuery, setSearchQuery] = useState("")
const [isLoading, setIsLoading] = useState(true)
const [activeTab, setActiveTab] = useState("devices")
useEffect(() => {
// 模拟加载设备和流量池数据
const fetchData = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
const mockDevices: Device[] = [
{ id: "device-1", name: "iPhone 13 Pro", status: "online" },
{ id: "device-2", name: "Xiaomi 12", status: "online" },
{ id: "device-3", name: "Huawei P40", status: "offline" },
{ id: "device-4", name: "OPPO Find X3", status: "online" },
{ id: "device-5", name: "Samsung S21", status: "offline" },
]
const mockTrafficPools: TrafficPool[] = [
{
id: "pool-1",
name: "微信好友池-设备1",
deviceId: "device-1",
deviceName: "iPhone 13 Pro",
type: "wechat",
userCount: 156,
lastUpdated: "2023-12-15T10:30:00Z",
},
{
id: "pool-2",
name: "设备流量池-设备1",
deviceId: "device-1",
deviceName: "iPhone 13 Pro",
type: "device",
userCount: 287,
lastUpdated: "2023-12-14T15:45:00Z",
},
{
id: "pool-3",
name: "微信好友池-设备2",
deviceId: "device-2",
deviceName: "Xiaomi 12",
type: "wechat",
userCount: 203,
lastUpdated: "2023-12-16T09:15:00Z",
},
{
id: "pool-4",
name: "设备流量池-设备3",
deviceId: "device-3",
deviceName: "Huawei P40",
type: "device",
userCount: 156,
lastUpdated: "2023-12-13T14:20:00Z",
},
{
id: "pool-5",
name: "微信好友池-设备4",
deviceId: "device-4",
deviceName: "OPPO Find X3",
type: "wechat",
userCount: 178,
lastUpdated: "2023-12-12T11:30:00Z",
},
]
setDevices(mockDevices)
setTrafficPools(mockTrafficPools)
setIsLoading(false)
}
fetchData()
}, [])
const handleDeviceToggle = (deviceId: string) => {
setSelectedDeviceIds((prev) => {
if (prev.includes(deviceId)) {
return prev.filter((id) => id !== deviceId)
} else {
return [...prev, deviceId]
}
})
}
const handlePoolToggle = (poolId: string) => {
setSelectedPoolIds((prev) => {
if (prev.includes(poolId)) {
return prev.filter((id) => id !== poolId)
} else {
return [...prev, poolId]
}
})
}
const filteredDevices = devices.filter((device) => device.name.toLowerCase().includes(searchQuery.toLowerCase()))
const filteredPools = trafficPools.filter(
(pool) =>
(selectedDeviceIds.length === 0 || selectedDeviceIds.includes(pool.deviceId)) &&
pool.name.toLowerCase().includes(searchQuery.toLowerCase()),
)
const handleContinue = () => {
const selectedPools = trafficPools.filter((pool) => selectedPoolIds.includes(pool.id))
const totalUserCount = selectedPools.reduce((sum, pool) => sum + pool.userCount, 0)
updateFormData({
deviceIds: selectedDeviceIds,
deviceNames: devices.filter((device) => selectedDeviceIds.includes(device.id)).map((device) => device.name),
trafficPoolIds: selectedPoolIds,
trafficPoolNames: selectedPools.map((pool) => pool.name),
trafficPoolSize: totalUserCount,
})
onNext()
}
return (
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold mb-1"></h2>
<p className="text-gray-500 text-sm"></p>
</div>
<div className="relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索设备或流量池"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="devices"></TabsTrigger>
<TabsTrigger value="pools"></TabsTrigger>
</TabsList>
<TabsContent value="devices" className="space-y-4 pt-4">
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : filteredDevices.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{filteredDevices.map((device) => (
<Card
key={device.id}
className={`cursor-pointer transition-all ${
selectedDeviceIds.includes(device.id) ? "border-blue-500 shadow-sm" : "hover:border-gray-300"
}`}
onClick={() => handleDeviceToggle(device.id)}
>
<CardContent className="p-4 flex items-center justify-between">
<div className="flex items-center space-x-3">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center ${
device.status === "online" ? "bg-green-100" : "bg-gray-100"
}`}
>
<Smartphone
className={`h-5 w-5 ${device.status === "online" ? "text-green-600" : "text-gray-400"}`}
/>
</div>
<div>
<p className="font-medium">{device.name}</p>
<Badge
variant="outline"
className={
device.status === "online"
? "bg-green-50 text-green-700 border-green-200"
: "bg-gray-50 text-gray-700 border-gray-200"
}
>
{device.status === "online" ? "在线" : "离线"}
</Badge>
</div>
</div>
<Checkbox
checked={selectedDeviceIds.includes(device.id)}
onCheckedChange={() => handleDeviceToggle(device.id)}
className="h-5 w-5"
/>
</CardContent>
</Card>
))}
</div>
) : (
<div className="text-center py-8 text-gray-500"></div>
)}
</TabsContent>
<TabsContent value="pools" className="space-y-4 pt-4">
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : filteredPools.length > 0 ? (
<div className="space-y-4">
{filteredPools.map((pool) => (
<Card
key={pool.id}
className={`cursor-pointer transition-all ${
selectedPoolIds.includes(pool.id) ? "border-blue-500 shadow-sm" : "hover:border-gray-300"
}`}
onClick={() => handlePoolToggle(pool.id)}
>
<CardContent className="p-4 flex items-center justify-between">
<div className="flex items-center space-x-3">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center ${
pool.type === "wechat" ? "bg-green-100" : "bg-blue-100"
}`}
>
<Users className={`h-5 w-5 ${pool.type === "wechat" ? "text-green-600" : "text-blue-600"}`} />
</div>
<div>
<p className="font-medium">{pool.name}</p>
<div className="flex items-center space-x-2 text-sm text-gray-500">
<span>: {pool.deviceName}</span>
<span></span>
<span>: {pool.userCount}</span>
</div>
</div>
</div>
<Checkbox
checked={selectedPoolIds.includes(pool.id)}
onCheckedChange={() => handlePoolToggle(pool.id)}
className="h-5 w-5"
/>
</CardContent>
</Card>
))}
</div>
) : (
<div className="text-center py-8 text-gray-500">
{selectedDeviceIds.length > 0 ? "所选设备没有可用的流量池" : "请先选择设备或搜索流量池"}
</div>
)}
</TabsContent>
</Tabs>
<div className="flex justify-between pt-4">
<div className="text-sm text-gray-500">
{selectedDeviceIds.length} , {selectedPoolIds.length}
</div>
<Button onClick={handleContinue} disabled={selectedPoolIds.length === 0}>
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,129 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
import { StepIndicator } from "../components/step-indicator"
import { TrafficPoolSelection } from "../components/traffic-pool-selection"
import { StrategySelection } from "../components/strategy-selection"
import { ExecutionSetup } from "../components/execution-setup"
import { ConfirmStrategy } from "../components/confirm-strategy"
export default function NewStrategyPage() {
const router = useRouter()
const [currentStep, setCurrentStep] = useState(1)
const [formData, setFormData] = useState({
name: "",
deviceIds: [] as string[],
deviceNames: [] as string[],
trafficPoolIds: [] as string[],
trafficPoolNames: [] as string[],
trafficPoolSize: 0,
strategyType: "",
strategyName: "",
strategyConfig: {} as Record<string, any>,
executionConfig: {
scheduleType: "immediate", // "immediate", "scheduled"
scheduledTime: "",
notifyOnComplete: true,
importTags: true,
sendToWechat: false,
wechatId: "",
},
})
const handleNext = () => {
setCurrentStep((prev) => prev + 1)
}
const handleBack = () => {
if (currentStep === 1) {
router.back()
} else {
setCurrentStep((prev) => prev - 1)
}
}
const handleSubmit = async () => {
// 模拟提交
console.log("提交策略优化:", formData)
// 模拟加载
await new Promise((resolve) => setTimeout(resolve, 1500))
// 跳转到列表页
router.push("/workspace/ai-strategy")
}
const updateFormData = (data: Partial<typeof formData>) => {
setFormData((prev) => ({ ...prev, ...data }))
}
return (
<div className="flex flex-col h-screen max-h-screen bg-gray-50">
{/* 头部 */}
<div className="bg-white p-4 border-b flex items-center">
<Button variant="ghost" size="icon" onClick={handleBack} className="mr-2">
<ArrowLeft className="h-5 w-5" />
</Button>
<h1 className="text-xl font-semibold"></h1>
</div>
{/* 步骤指示器 */}
<div className="bg-white border-b">
<div className="max-w-4xl mx-auto py-4 px-4">
<StepIndicator
currentStep={currentStep}
steps={[
{ number: 1, title: "选择流量池" },
{ number: 2, title: "选择优化策略" },
{ number: 3, title: "执行设置" },
{ number: 4, title: "确认策略" },
]}
/>
</div>
</div>
{/* 主内容区域 */}
<div className="flex-1 p-4 overflow-auto">
<div className="max-w-4xl mx-auto">
<Card className="p-6">
{currentStep === 1 && (
<TrafficPoolSelection formData={formData} updateFormData={updateFormData} onNext={handleNext} />
)}
{currentStep === 2 && (
<StrategySelection
formData={formData}
updateFormData={updateFormData}
onNext={handleNext}
onBack={handleBack}
/>
)}
{currentStep === 3 && (
<ExecutionSetup
formData={formData}
updateFormData={updateFormData}
onNext={handleNext}
onBack={handleBack}
/>
)}
{currentStep === 4 && (
<ConfirmStrategy
formData={formData}
updateFormData={updateFormData}
onSubmit={handleSubmit}
onBack={handleBack}
/>
)}
</Card>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,371 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowLeft, Plus, FileText, BarChart2, Mail, Tag } from "lucide-react"
import { useRouter } from "next/navigation"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import Link from "next/link"
interface StrategyPlan {
id: string
name: string
deviceIds: string[]
deviceNames: string[]
status: "running" | "completed" | "failed"
createdAt: string
completedAt?: string
strategyType: "jd" | "yisi" | "database" | "custom"
strategyName: string
trafficPoolSize: number
optimizedUsers: number
}
export default function AIStrategyPage() {
const router = useRouter()
const [plans, setPlans] = useState<StrategyPlan[]>([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// 模拟加载数据
const fetchPlans = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
const mockPlans: StrategyPlan[] = [
{
id: "plan-1",
name: "京东会员优化",
deviceIds: ["device-1", "device-2"],
deviceNames: ["设备1", "设备2"],
status: "completed",
createdAt: "2023-12-15T10:30:00Z",
completedAt: "2023-12-15T11:45:00Z",
strategyType: "jd",
strategyName: "京东会员识别",
trafficPoolSize: 287,
optimizedUsers: 142,
},
{
id: "plan-2",
name: "易思API用户分析",
deviceIds: ["device-3"],
deviceNames: ["设备3"],
status: "running",
createdAt: "2023-12-16T09:15:00Z",
strategyType: "yisi",
strategyName: "易思用户画像",
trafficPoolSize: 156,
optimizedUsers: 0,
},
{
id: "plan-3",
name: "数据库客户匹配",
deviceIds: ["device-1", "device-4"],
deviceNames: ["设备1", "设备4"],
status: "completed",
createdAt: "2023-12-14T14:20:00Z",
completedAt: "2023-12-14T16:10:00Z",
strategyType: "database",
strategyName: "CRM客户匹配",
trafficPoolSize: 423,
optimizedUsers: 215,
},
]
setPlans(mockPlans)
setIsLoading(false)
}
fetchPlans()
}, [])
const getStatusBadge = (status: StrategyPlan["status"]) => {
switch (status) {
case "running":
return (
<Badge variant="outline" className="bg-blue-50 text-blue-600 border-blue-200">
</Badge>
)
case "completed":
return (
<Badge variant="outline" className="bg-green-50 text-green-600 border-green-200">
</Badge>
)
case "failed":
return (
<Badge variant="outline" className="bg-red-50 text-red-600 border-red-200">
</Badge>
)
}
}
const getStrategyLabel = (type: StrategyPlan["strategyType"]) => {
switch (type) {
case "jd":
return "京东会员"
case "yisi":
return "易思接口"
case "database":
return "数据库匹配"
case "custom":
return "自定义策略"
}
}
return (
<div className="flex flex-col h-screen max-h-screen bg-gray-50">
{/* 头部 */}
<div className="bg-white p-4 border-b flex items-center justify-between">
<div className="flex items-center">
<Button variant="ghost" size="icon" onClick={() => router.back()} className="mr-2">
<ArrowLeft className="h-5 w-5" />
</Button>
<h1 className="text-xl font-semibold">AI策略优化</h1>
</div>
<Button onClick={() => router.push("/workspace/ai-strategy/new")} className="flex items-center gap-2">
<Plus className="h-4 w-4" />
</Button>
</div>
{/* 主内容区域 */}
<div className="flex-1 p-4 overflow-auto">
<div className="max-w-5xl mx-auto">
<Tabs defaultValue="all" className="w-full">
<TabsList className="grid w-full grid-cols-3 mb-6">
<TabsTrigger value="all"></TabsTrigger>
<TabsTrigger value="running"></TabsTrigger>
<TabsTrigger value="completed"></TabsTrigger>
</TabsList>
<TabsContent value="all" className="space-y-4">
{isLoading ? (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : plans.length > 0 ? (
plans.map((plan) => (
<Card key={plan.id} className="hover:shadow-md transition-shadow">
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<div>
<CardTitle>{plan.name}</CardTitle>
<CardDescription className="mt-1">: {plan.deviceNames.join(", ")}</CardDescription>
</div>
{getStatusBadge(plan.status)}
</div>
</CardHeader>
<CardContent>
<div className="flex flex-col space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{getStrategyLabel(plan.strategyType)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.strategyName}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.trafficPoolSize} </span>
</div>
{plan.status === "completed" && (
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.optimizedUsers} </span>
</div>
)}
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{new Date(plan.createdAt).toLocaleString()}</span>
</div>
{plan.completedAt && (
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{new Date(plan.completedAt).toLocaleString()}</span>
</div>
)}
<div className="flex justify-end gap-2 pt-2">
{plan.status === "completed" && (
<>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Tag className="h-4 w-4" />
<span></span>
</Button>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Mail className="h-4 w-4" />
<span></span>
</Button>
<Link href={`/workspace/ai-strategy/${plan.id}`}>
<Button size="sm" className="flex items-center gap-1">
<BarChart2 className="h-4 w-4" />
<span></span>
</Button>
</Link>
</>
)}
{plan.status === "running" && (
<Button variant="outline" size="sm" className="flex items-center gap-1">
<FileText className="h-4 w-4" />
<span></span>
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))
) : (
<div className="text-center py-12 bg-white rounded-lg border">
<div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<BarChart2 className="h-8 w-8 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-1"></h3>
<p className="text-gray-500 mb-4">AI策略优化计划来开始</p>
<Button onClick={() => router.push("/workspace/ai-strategy/new")}></Button>
</div>
)}
</TabsContent>
<TabsContent value="running" className="space-y-4">
{isLoading ? (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : plans.filter((p) => p.status === "running").length > 0 ? (
plans
.filter((p) => p.status === "running")
.map((plan) => (
<Card key={plan.id} className="hover:shadow-md transition-shadow">
{/* 与上面相同的卡片内容 */}
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<div>
<CardTitle>{plan.name}</CardTitle>
<CardDescription className="mt-1">: {plan.deviceNames.join(", ")}</CardDescription>
</div>
{getStatusBadge(plan.status)}
</div>
</CardHeader>
<CardContent>
<div className="flex flex-col space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{getStrategyLabel(plan.strategyType)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.strategyName}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.trafficPoolSize} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{new Date(plan.createdAt).toLocaleString()}</span>
</div>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" size="sm" className="flex items-center gap-1">
<FileText className="h-4 w-4" />
<span></span>
</Button>
</div>
</div>
</CardContent>
</Card>
))
) : (
<div className="text-center py-12 bg-white rounded-lg border">
<p className="text-gray-500"></p>
</div>
)}
</TabsContent>
<TabsContent value="completed" className="space-y-4">
{isLoading ? (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
</div>
) : plans.filter((p) => p.status === "completed").length > 0 ? (
plans
.filter((p) => p.status === "completed")
.map((plan) => (
<Card key={plan.id} className="hover:shadow-md transition-shadow">
{/* 与上面相同的卡片内容 */}
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<div>
<CardTitle>{plan.name}</CardTitle>
<CardDescription className="mt-1">: {plan.deviceNames.join(", ")}</CardDescription>
</div>
{getStatusBadge(plan.status)}
</div>
</CardHeader>
<CardContent>
<div className="flex flex-col space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{getStrategyLabel(plan.strategyType)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.strategyName}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.trafficPoolSize} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">{plan.optimizedUsers} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{new Date(plan.createdAt).toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{new Date(plan.completedAt!).toLocaleString()}</span>
</div>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Tag className="h-4 w-4" />
<span></span>
</Button>
<Button variant="outline" size="sm" className="flex items-center gap-1">
<Mail className="h-4 w-4" />
<span></span>
</Button>
<Link href={`/workspace/ai-strategy/${plan.id}`}>
<Button size="sm" className="flex items-center gap-1">
<BarChart2 className="h-4 w-4" />
<span></span>
</Button>
</Link>
</div>
</div>
</CardContent>
</Card>
))
) : (
<div className="text-center py-12 bg-white rounded-lg border">
<p className="text-gray-500"></p>
</div>
)}
</TabsContent>
</Tabs>
</div>
</div>
</div>
)
}