Files
users/app/ai-insight/prediction/page.tsx
v0 b17b488f8e refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2026-01-31 04:32:36 +00:00

332 lines
14 KiB
TypeScript

"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import {
TrendingUp,
TrendingDown,
Users,
AlertTriangle,
Target,
ArrowLeft,
Play,
Calendar,
BarChart3,
PieChart,
} from "lucide-react"
import Link from "next/link"
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts"
// 模拟预测数据
const churnPrediction = [
{ date: "12-06", actual: 120, predicted: 125 },
{ date: "12-07", actual: 135, predicted: 130 },
{ date: "12-08", actual: 128, predicted: 132 },
{ date: "12-09", actual: 142, predicted: 138 },
{ date: "12-10", actual: 138, predicted: 145 },
{ date: "12-11", actual: 155, predicted: 150 },
{ date: "12-12", actual: null, predicted: 162 },
{ date: "12-13", actual: null, predicted: 158 },
{ date: "12-14", actual: null, predicted: 165 },
]
const conversionPrediction = [
{ date: "12-06", rate: 3.2 },
{ date: "12-07", rate: 3.5 },
{ date: "12-08", rate: 3.3 },
{ date: "12-09", rate: 3.8 },
{ date: "12-10", rate: 4.1 },
{ date: "12-11", rate: 4.0 },
{ date: "12-12", rate: 4.3 },
{ date: "12-13", rate: 4.5 },
{ date: "12-14", rate: 4.8 },
]
const riskUsers = [
{ id: 1, name: "用户A****8", risk: 92, lastActive: "7天前", value: "高", reason: "活跃度骤降" },
{ id: 2, name: "用户B****2", risk: 88, lastActive: "5天前", value: "高", reason: "消费频次下降" },
{ id: 3, name: "用户C****6", risk: 85, lastActive: "6天前", value: "中", reason: "互动减少" },
{ id: 4, name: "用户D****1", risk: 82, lastActive: "4天前", value: "高", reason: "投诉增加" },
{ id: 5, name: "用户E****9", risk: 78, lastActive: "8天前", value: "中", reason: "登录减少" },
]
export default function PredictionPage() {
const [timeRange, setTimeRange] = useState("7d")
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50/30 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/ai-insight">
<Button variant="ghost" size="icon" className="rounded-full">
<ArrowLeft className="w-5 h-5" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-slate-800"></h1>
<p className="text-slate-500 mt-1"></p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("7d")}
className={timeRange === "7d" ? "bg-slate-100" : ""}
>
<Calendar className="w-4 h-4 mr-1" />
7
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("30d")}
className={timeRange === "30d" ? "bg-slate-100" : ""}
>
30
</Button>
<Button className="bg-gradient-to-r from-blue-500 to-cyan-600 hover:from-blue-600 hover:to-cyan-700">
<Play className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* 预测概览 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-red-600">2,340</p>
</div>
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<TrendingDown className="w-5 h-5 text-red-600" />
</div>
</div>
<p className="text-xs text-red-600 mt-2">7</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-emerald-600">890</p>
</div>
<div className="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
<TrendingUp className="w-5 h-5 text-emerald-600" />
</div>
</div>
<p className="text-xs text-emerald-600 mt-2"> {"> "}75%</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-slate-800">91.8%</p>
</div>
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<Target className="w-5 h-5 text-blue-600" />
</div>
</div>
<p className="text-xs text-emerald-600 mt-2"> +1.2%</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-500"></p>
<p className="text-2xl font-bold text-amber-600">156</p>
</div>
<div className="w-10 h-10 rounded-lg bg-amber-100 flex items-center justify-center">
<AlertTriangle className="w-5 h-5 text-amber-600" />
</div>
</div>
<p className="text-xs text-amber-600 mt-2"> {"> "}80</p>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 流失预测趋势 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center gap-2">
<BarChart3 className="w-4 h-4 text-slate-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={churnPrediction}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="date" tick={{ fontSize: 12 }} stroke="#94a3b8" />
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" />
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e2e8f0",
borderRadius: "8px",
boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
}}
/>
<Line
type="monotone"
dataKey="actual"
stroke="#3b82f6"
strokeWidth={2}
dot={{ r: 4 }}
name="实际值"
/>
<Line
type="monotone"
dataKey="predicted"
stroke="#f97316"
strokeWidth={2}
strokeDasharray="5 5"
dot={{ r: 4 }}
name="预测值"
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="flex items-center justify-center gap-6 mt-4">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-blue-500" />
<span className="text-xs text-slate-600"></span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-0.5 bg-orange-500" style={{ borderStyle: "dashed" }} />
<span className="text-xs text-slate-600"></span>
</div>
</div>
</CardContent>
</Card>
{/* 转化率预测 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center gap-2">
<PieChart className="w-4 h-4 text-slate-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={conversionPrediction}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="date" tick={{ fontSize: 12 }} stroke="#94a3b8" />
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" tickFormatter={(v) => `${v}%`} />
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e2e8f0",
borderRadius: "8px",
}}
formatter={(value: number) => [`${value}%`, "转化率"]}
/>
<defs>
<linearGradient id="colorRate" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
</linearGradient>
</defs>
<Area type="monotone" dataKey="rate" stroke="#10b981" strokeWidth={2} fill="url(#colorRate)" />
</AreaChart>
</ResponsiveContainer>
</div>
<div className="text-center mt-4">
<p className="text-sm text-slate-600">
7 <span className="font-bold text-emerald-600">4.8%</span>
</p>
</div>
</CardContent>
</Card>
</div>
{/* 高风险用户列表 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<AlertTriangle className="w-4 h-4 text-amber-500" />
</CardTitle>
<Button variant="outline" size="sm">
</Button>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500"></th>
</tr>
</thead>
<tbody>
{riskUsers.map((user) => (
<tr key={user.id} className="border-b border-slate-100 hover:bg-slate-50/50">
<td className="py-3 px-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-slate-200 to-slate-300 flex items-center justify-center">
<Users className="w-4 h-4 text-slate-600" />
</div>
<span className="text-sm font-medium text-slate-800">{user.name}</span>
</div>
</td>
<td className="py-3 px-4">
<div className="flex items-center gap-2">
<Progress value={user.risk} className="w-16 h-2" />
<span className="text-sm font-medium text-red-600">{user.risk}</span>
</div>
</td>
<td className="py-3 px-4">
<Badge
className={
user.value === "高" ? "bg-amber-100 text-amber-700" : "bg-slate-100 text-slate-700"
}
>
{user.value}
</Badge>
</td>
<td className="py-3 px-4 text-sm text-slate-600">{user.lastActive}</td>
<td className="py-3 px-4 text-sm text-slate-600">{user.reason}</td>
<td className="py-3 px-4">
<Button variant="outline" size="sm">
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
</div>
)
}