Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
332 lines
14 KiB
TypeScript
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>
|
|
)
|
|
}
|