Optimize user detail page for asset assessment and tag info. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
312 lines
12 KiB
TypeScript
312 lines
12 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { useParams, useRouter } from "next/navigation"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { ArrowLeft, User, Tag, TrendingUp, DollarSign, Calendar, Phone, Mail, MapPin } from "lucide-react"
|
|
|
|
type UserDetail = {
|
|
id: string
|
|
name: string
|
|
avatar?: string
|
|
email: string
|
|
phone: string
|
|
city: string
|
|
store: string
|
|
project: string
|
|
team: string
|
|
tags: string[]
|
|
persona: string[]
|
|
source: string
|
|
recency: number
|
|
frequency: number
|
|
monetary: number
|
|
rfmScore: number
|
|
assetValue: number
|
|
riskLevel: "低" | "中" | "高"
|
|
lastActivity: string
|
|
status: "活跃" | "沉睡" | "流失风险"
|
|
detailedTags: {
|
|
category: string
|
|
tags: { name: string; confidence: number; source: string }[]
|
|
}[]
|
|
assetBreakdown: {
|
|
category: string
|
|
value: number
|
|
percentage: number
|
|
}[]
|
|
}
|
|
|
|
export default function UserDetailPage() {
|
|
const params = useParams<{ id: string }>()
|
|
const router = useRouter()
|
|
const [data, setData] = useState<UserDetail | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
if (!params?.id) return
|
|
setLoading(true)
|
|
setTimeout(() => {
|
|
setData({
|
|
id: params.id,
|
|
name: "张三",
|
|
email: "zhangsan@example.com",
|
|
phone: "138****8888",
|
|
city: "厦门",
|
|
store: "思明店",
|
|
project: "贷款业务",
|
|
team: "A组",
|
|
tags: ["高价值客户", "活跃用户", "信用良好"],
|
|
persona: ["高净值人群", "投资偏好保守"],
|
|
source: "微信推广",
|
|
recency: 85,
|
|
frequency: 92,
|
|
monetary: 88,
|
|
rfmScore: 88,
|
|
assetValue: 1250000,
|
|
riskLevel: "低",
|
|
lastActivity: "2024-01-15 14:30:00",
|
|
status: "活跃",
|
|
detailedTags: [
|
|
{
|
|
category: "行为标签",
|
|
tags: [
|
|
{ name: "高频访问", confidence: 95, source: "系统分析" },
|
|
{ name: "深度浏览", confidence: 88, source: "行为追踪" },
|
|
{ name: "主动咨询", confidence: 92, source: "客服记录" },
|
|
],
|
|
},
|
|
{
|
|
category: "偏好标签",
|
|
tags: [
|
|
{ name: "理财产品", confidence: 90, source: "购买记录" },
|
|
{ name: "保险产品", confidence: 75, source: "浏览记录" },
|
|
{ name: "投资咨询", confidence: 85, source: "咨询记录" },
|
|
],
|
|
},
|
|
{
|
|
category: "风险标签",
|
|
tags: [
|
|
{ name: "信用优良", confidence: 98, source: "征信报告" },
|
|
{ name: "收入稳定", confidence: 92, source: "银行流水" },
|
|
{ name: "资产充足", confidence: 88, source: "资产证明" },
|
|
],
|
|
},
|
|
],
|
|
assetBreakdown: [
|
|
{ category: "客户生命周期价值", value: 450000, percentage: 36 },
|
|
{ category: "交叉销售潜力", value: 320000, percentage: 26 },
|
|
{ category: "推荐价值", value: 280000, percentage: 22 },
|
|
{ category: "品牌忠诚度价值", value: 200000, percentage: 16 },
|
|
],
|
|
})
|
|
setLoading(false)
|
|
}, 500)
|
|
}, [params?.id])
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 p-4">
|
|
<div className="animate-pulse space-y-4">
|
|
<div className="h-12 bg-slate-200 rounded"></div>
|
|
<div className="h-32 bg-slate-200 rounded"></div>
|
|
<div className="h-48 bg-slate-200 rounded"></div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!data) {
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 p-4 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<p className="text-slate-600 mb-4">未找到该用户</p>
|
|
<Button onClick={() => router.back()}>返回</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50">
|
|
<div className="bg-white border-b px-4 py-3 flex items-center gap-3">
|
|
<Button variant="ghost" size="sm" onClick={() => router.back()}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
<h1 className="font-semibold">用户详细信息</h1>
|
|
</div>
|
|
|
|
<div className="container mx-auto px-4 py-4 space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<User className="h-5 w-5" />
|
|
基本信息
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-bold">{data.name}</h2>
|
|
<div className="flex items-center gap-4 text-sm text-slate-600 mt-1">
|
|
<span className="flex items-center gap-1">
|
|
<Phone className="h-3 w-3" />
|
|
{data.phone}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Mail className="h-3 w-3" />
|
|
{data.email}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-sm text-slate-600 mt-1">
|
|
<span className="flex items-center gap-1">
|
|
<MapPin className="h-3 w-3" />
|
|
{data.city} · {data.store}
|
|
</span>
|
|
<span>
|
|
{data.project} · {data.team}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<Badge variant={data.status === "活跃" ? "default" : "secondary"}>{data.status}</Badge>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
{data.persona.map((p) => (
|
|
<Badge key={p} variant="outline" className="text-xs">
|
|
{p}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<DollarSign className="h-5 w-5" />
|
|
用户资产评估
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-green-600">¥{data.assetValue.toLocaleString()}</div>
|
|
<div className="text-xs text-slate-600">总资产价值</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">{data.rfmScore}</div>
|
|
<div className="text-xs text-slate-600">RFM评分</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div
|
|
className={`text-2xl font-bold ${data.riskLevel === "低" ? "text-green-600" : data.riskLevel === "中" ? "text-yellow-600" : "text-red-600"}`}
|
|
>
|
|
{data.riskLevel}
|
|
</div>
|
|
<div className="text-xs text-slate-600">风险等级</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-blue-600">{data.source}</div>
|
|
<div className="text-xs text-slate-600">获客来源</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<h4 className="font-medium text-sm">资产价值构成</h4>
|
|
{data.assetBreakdown.map((item) => (
|
|
<div key={item.category} className="flex items-center justify-between">
|
|
<span className="text-sm">{item.category}</span>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-20 bg-slate-200 rounded-full h-2">
|
|
<div className="bg-blue-500 h-2 rounded-full" style={{ width: `${item.percentage}%` }}></div>
|
|
</div>
|
|
<span className="text-sm font-medium">¥{item.value.toLocaleString()}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Tag className="h-5 w-5" />
|
|
详细标签信息
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{data.detailedTags.map((category) => (
|
|
<div key={category.category} className="space-y-2">
|
|
<h4 className="font-medium text-sm text-slate-700">{category.category}</h4>
|
|
<div className="grid gap-2">
|
|
{category.tags.map((tag) => (
|
|
<div key={tag.name} className="flex items-center justify-between p-2 bg-slate-50 rounded-md">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="secondary" className="text-xs">
|
|
{tag.name}
|
|
</Badge>
|
|
<span className="text-xs text-slate-600">来源: {tag.source}</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<div className="w-12 bg-slate-200 rounded-full h-1">
|
|
<div className="bg-green-500 h-1 rounded-full" style={{ width: `${tag.confidence}%` }}></div>
|
|
</div>
|
|
<span className="text-xs font-medium">{tag.confidence}%</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<TrendingUp className="h-5 w-5" />
|
|
RFM详细指标
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-xl font-bold">{data.recency}</div>
|
|
<div className="text-xs text-slate-600">最近性 (R)</div>
|
|
<div className="w-full bg-slate-200 rounded-full h-2 mt-1">
|
|
<div className="bg-blue-500 h-2 rounded-full" style={{ width: `${data.recency}%` }}></div>
|
|
</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-xl font-bold">{data.frequency}</div>
|
|
<div className="text-xs text-slate-600">频率 (F)</div>
|
|
<div className="w-full bg-slate-200 rounded-full h-2 mt-1">
|
|
<div className="bg-green-500 h-2 rounded-full" style={{ width: `${data.frequency}%` }}></div>
|
|
</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-xl font-bold">{data.monetary}</div>
|
|
<div className="text-xs text-slate-600">货币价值 (M)</div>
|
|
<div className="w-full bg-slate-200 rounded-full h-2 mt-1">
|
|
<div className="bg-purple-500 h-2 rounded-full" style={{ width: `${data.monetary}%` }}></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 text-center">
|
|
<div className="text-sm text-slate-600">最后活跃时间</div>
|
|
<div className="flex items-center justify-center gap-1 mt-1">
|
|
<Calendar className="h-3 w-3" />
|
|
<span className="text-sm">{data.lastActivity}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|