Refactor homepage for focused search and data display; streamline data platform; enhance user and tag management; focus AI assistant on data analysis and report generation. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
568 lines
24 KiB
TypeScript
568 lines
24 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from "react"
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||
import { Label } from "@/components/ui/label"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { Switch } from "@/components/ui/switch"
|
||
import { Users, UserPlus, Tags, Target, Activity, Plus, Edit, Search, Filter } from "lucide-react"
|
||
import { TooltipHelp } from "@/components/ui/tooltip-help"
|
||
import { TooltipProvider } from "@/components/ui/tooltip"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogFooter,
|
||
} from "@/components/ui/dialog"
|
||
|
||
export default function UserPortraitPage() {
|
||
const [selectedSegment, setSelectedSegment] = useState("all")
|
||
const [isAddingUser, setIsAddingUser] = useState(false)
|
||
const [isAddingTag, setIsAddingTag] = useState(false)
|
||
const [searchQuery, setSearchQuery] = useState("")
|
||
|
||
// 用户数据
|
||
const [users, setUsers] = useState([
|
||
{
|
||
id: "U001",
|
||
name: "张三",
|
||
phone: "13800138001",
|
||
email: "zhangsan@example.com",
|
||
tags: ["高价值", "活跃用户", "科技爱好者"],
|
||
value: 12580,
|
||
lastActive: "2小时前",
|
||
source: "微信",
|
||
registerDate: "2023-01-15",
|
||
},
|
||
{
|
||
id: "U002",
|
||
name: "李四",
|
||
phone: "13800138002",
|
||
email: "lisi@example.com",
|
||
tags: ["潜在客户", "新用户", "价格敏感"],
|
||
value: 3240,
|
||
lastActive: "1天前",
|
||
source: "抖音",
|
||
registerDate: "2024-01-10",
|
||
},
|
||
{
|
||
id: "U003",
|
||
name: "王五",
|
||
phone: "13800138003",
|
||
email: "wangwu@example.com",
|
||
tags: ["高价值", "忠实用户", "奢侈品偏好"],
|
||
value: 25600,
|
||
lastActive: "30分钟前",
|
||
source: "小红书",
|
||
registerDate: "2022-08-20",
|
||
},
|
||
])
|
||
|
||
// 标签分类数据
|
||
const [tagCategories, setTagCategories] = useState([
|
||
{
|
||
id: "1",
|
||
name: "用户价值",
|
||
description: "基于用户消费能力和贡献度的分类",
|
||
tags: [
|
||
{ name: "高价值用户", count: 2847, color: "red" },
|
||
{ name: "中价值用户", count: 12456, color: "blue" },
|
||
{ name: "低价值用户", count: 8234, color: "gray" },
|
||
{ name: "潜在价值用户", count: 5287, color: "yellow" },
|
||
],
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "活跃度",
|
||
description: "基于用户活跃程度和互动频率的分类",
|
||
tags: [
|
||
{ name: "超级活跃", count: 1234, color: "green" },
|
||
{ name: "活跃用户", count: 8429, color: "blue" },
|
||
{ name: "一般活跃", count: 15678, color: "yellow" },
|
||
{ name: "不活跃", count: 3456, color: "gray" },
|
||
],
|
||
},
|
||
{
|
||
id: "3",
|
||
name: "消费行为",
|
||
description: "基于用户购买习惯和消费偏好的分类",
|
||
tags: [
|
||
{ name: "冲动消费", count: 4567, color: "red" },
|
||
{ name: "理性消费", count: 12345, color: "blue" },
|
||
{ name: "价格敏感", count: 8901, color: "yellow" },
|
||
{ name: "品质优先", count: 6789, color: "purple" },
|
||
{ name: "奢侈品偏好", count: 2345, color: "pink" },
|
||
],
|
||
},
|
||
{
|
||
id: "4",
|
||
name: "兴趣偏好",
|
||
description: "基于用户兴趣爱好和内容偏好的分类",
|
||
tags: [
|
||
{ name: "科技爱好者", count: 5678, color: "blue" },
|
||
{ name: "时尚达人", count: 7890, color: "pink" },
|
||
{ name: "旅游爱好者", count: 4321, color: "green" },
|
||
{ name: "美食家", count: 6543, color: "orange" },
|
||
{ name: "运动健身", count: 3210, color: "red" },
|
||
{ name: "文艺青年", count: 2109, color: "purple" },
|
||
],
|
||
},
|
||
{
|
||
id: "5",
|
||
name: "生命周期",
|
||
description: "基于用户在平台的生命周期阶段分类",
|
||
tags: [
|
||
{ name: "新用户", count: 8765, color: "green" },
|
||
{ name: "成长期用户", count: 12345, color: "blue" },
|
||
{ name: "成熟期用户", count: 15678, color: "purple" },
|
||
{ name: "衰退期用户", count: 4321, color: "yellow" },
|
||
{ name: "流失用户", count: 2109, color: "gray" },
|
||
],
|
||
},
|
||
{
|
||
id: "6",
|
||
name: "地域分布",
|
||
description: "基于用户地理位置的分类",
|
||
tags: [
|
||
{ name: "一线城市", count: 18765, color: "red" },
|
||
{ name: "二线城市", count: 15432, color: "blue" },
|
||
{ name: "三线城市", count: 9876, color: "green" },
|
||
{ name: "四线及以下", count: 5432, color: "gray" },
|
||
{ name: "海外用户", count: 1234, color: "purple" },
|
||
],
|
||
},
|
||
{
|
||
id: "7",
|
||
name: "设备偏好",
|
||
description: "基于用户使用设备类型的分类",
|
||
tags: [
|
||
{ name: "iOS用户", count: 16789, color: "blue" },
|
||
{ name: "Android用户", count: 23456, color: "green" },
|
||
{ name: "PC用户", count: 8765, color: "gray" },
|
||
{ name: "平板用户", count: 4321, color: "purple" },
|
||
],
|
||
},
|
||
{
|
||
id: "8",
|
||
name: "社交属性",
|
||
description: "基于用户社交行为和影响力的分类",
|
||
tags: [
|
||
{ name: "意见领袖", count: 1234, color: "red" },
|
||
{ name: "活跃分享者", count: 5678, color: "blue" },
|
||
{ name: "默默关注者", count: 18765, color: "gray" },
|
||
{ name: "社交新手", count: 8901, color: "green" },
|
||
],
|
||
},
|
||
])
|
||
|
||
const getTagColor = (color: string) => {
|
||
const colors = {
|
||
red: "bg-red-100 text-red-800",
|
||
blue: "bg-blue-100 text-blue-800",
|
||
green: "bg-green-100 text-green-800",
|
||
yellow: "bg-yellow-100 text-yellow-800",
|
||
purple: "bg-purple-100 text-purple-800",
|
||
pink: "bg-pink-100 text-pink-800",
|
||
orange: "bg-orange-100 text-orange-800",
|
||
gray: "bg-gray-100 text-gray-800",
|
||
}
|
||
return colors[color as keyof typeof colors] || colors.gray
|
||
}
|
||
|
||
return (
|
||
<TooltipProvider>
|
||
<div className="container mx-auto p-6 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold">用户画像</h1>
|
||
<p className="text-muted-foreground mt-2">深度分析用户特征,精准定位目标群体</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 核心指标概览 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||
<Card>
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||
总用户数
|
||
<TooltipHelp content="系统中所有已识别和分析的用户总数,包括活跃和非活跃用户" />
|
||
</CardTitle>
|
||
<Users className="h-4 w-4 text-muted-foreground" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">24,563</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
<span className="text-green-600">+12.5%</span> 较上月
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||
活跃用户
|
||
<TooltipHelp content="近30天内有过互动行为的用户数量,包括点击、浏览、购买等行为" />
|
||
</CardTitle>
|
||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">18,429</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
<span className="text-green-600">+8.2%</span> 较上月
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||
用户分群
|
||
<TooltipHelp content="基于用户行为和特征自动创建的用户群组数量,用于精准营销" />
|
||
</CardTitle>
|
||
<Target className="h-4 w-4 text-muted-foreground" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">156</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
<span className="text-blue-600">+23</span> 新增分群
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||
标签覆盖率
|
||
<TooltipHelp content="已打标签用户占总用户的比例,反映用户画像的完整程度" />
|
||
</CardTitle>
|
||
<Tags className="h-4 w-4 text-muted-foreground" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold">87.3%</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
<span className="text-green-600">+2.1%</span> 较上月
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<Tabs defaultValue="users" className="space-y-6">
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="users">用户管理</TabsTrigger>
|
||
<TabsTrigger value="tags">标签管理</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="users" className="space-y-6">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
用户管理
|
||
<TooltipHelp content="管理真实用户信息,支持添加、编辑和查看用户详细资料" />
|
||
</CardTitle>
|
||
<CardDescription>管理和查看所有真实用户的详细信息</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="flex items-center gap-4 mb-6">
|
||
<div className="relative">
|
||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||
<Input
|
||
placeholder="搜索用户..."
|
||
className="pl-8 max-w-sm"
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
/>
|
||
</div>
|
||
<Select value={selectedSegment} onValueChange={setSelectedSegment}>
|
||
<SelectTrigger className="w-48">
|
||
<SelectValue placeholder="选择用户分群" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">全部用户</SelectItem>
|
||
<SelectItem value="high-value">高价值用户</SelectItem>
|
||
<SelectItem value="active">活跃用户</SelectItem>
|
||
<SelectItem value="potential">潜在用户</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Button variant="outline" size="sm">
|
||
<Filter className="h-4 w-4 mr-2" />
|
||
筛选
|
||
</Button>
|
||
<Button onClick={() => setIsAddingUser(true)}>
|
||
<UserPlus className="h-4 w-4 mr-2" />
|
||
添加用户
|
||
</Button>
|
||
</div>
|
||
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>用户ID</TableHead>
|
||
<TableHead>姓名</TableHead>
|
||
<TableHead>联系方式</TableHead>
|
||
<TableHead>标签</TableHead>
|
||
<TableHead>用户价值</TableHead>
|
||
<TableHead>最后活跃</TableHead>
|
||
<TableHead>来源</TableHead>
|
||
<TableHead>操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{users.map((user) => (
|
||
<TableRow key={user.id}>
|
||
<TableCell className="font-medium">{user.id}</TableCell>
|
||
<TableCell>{user.name}</TableCell>
|
||
<TableCell>
|
||
<div className="text-sm">
|
||
<div>{user.phone}</div>
|
||
<div className="text-muted-foreground">{user.email}</div>
|
||
</div>
|
||
</TableCell>
|
||
<TableCell>
|
||
<div className="flex flex-wrap gap-1">
|
||
{user.tags.slice(0, 2).map((tag, index) => (
|
||
<Badge key={index} variant="secondary" className="text-xs">
|
||
{tag}
|
||
</Badge>
|
||
))}
|
||
{user.tags.length > 2 && (
|
||
<Badge variant="outline" className="text-xs">
|
||
+{user.tags.length - 2}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</TableCell>
|
||
<TableCell>¥{user.value.toLocaleString()}</TableCell>
|
||
<TableCell>{user.lastActive}</TableCell>
|
||
<TableCell>
|
||
<Badge variant="outline">{user.source}</Badge>
|
||
</TableCell>
|
||
<TableCell>
|
||
<div className="flex space-x-2">
|
||
<Button variant="ghost" size="sm">
|
||
<Edit className="h-4 w-4" />
|
||
</Button>
|
||
<Button variant="ghost" size="sm">
|
||
查看详情
|
||
</Button>
|
||
</div>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="tags" className="space-y-6">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex justify-between items-center">
|
||
<div>
|
||
<CardTitle className="flex items-center gap-2">
|
||
标签管理
|
||
<TooltipHelp content="管理用户标签体系,包括标签创建、分类、规则设置和自动化打标" />
|
||
</CardTitle>
|
||
<CardDescription>创建和管理用户标签,实现精准用户画像</CardDescription>
|
||
</div>
|
||
<Button onClick={() => setIsAddingTag(true)}>
|
||
<Plus className="h-4 w-4 mr-2" />
|
||
新建标签
|
||
</Button>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-6">
|
||
{tagCategories.map((category) => (
|
||
<Card key={category.id}>
|
||
<CardHeader className="pb-3">
|
||
<div className="flex justify-between items-center">
|
||
<div>
|
||
<CardTitle className="text-base">{category.name}</CardTitle>
|
||
<CardDescription>{category.description}</CardDescription>
|
||
</div>
|
||
<Badge variant="outline">{category.tags.length} 个标签</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||
{category.tags.map((tag, index) => (
|
||
<div
|
||
key={index}
|
||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<Badge className={getTagColor(tag.color)}>{tag.name}</Badge>
|
||
</div>
|
||
<div className="text-sm text-muted-foreground">{tag.count.toLocaleString()}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
</Tabs>
|
||
|
||
{/* 添加用户对话框 */}
|
||
<Dialog open={isAddingUser} onOpenChange={setIsAddingUser}>
|
||
<DialogContent className="sm:max-w-[500px]">
|
||
<DialogHeader>
|
||
<DialogTitle>添加真实用户</DialogTitle>
|
||
<DialogDescription>录入真实用户信息,完善用户画像数据</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="grid gap-4 py-4">
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="user-name" className="text-right">
|
||
姓名
|
||
</Label>
|
||
<Input id="user-name" className="col-span-3" placeholder="请输入真实姓名" />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="user-phone" className="text-right">
|
||
手机号
|
||
</Label>
|
||
<Input id="user-phone" className="col-span-3" placeholder="请输入手机号码" />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="user-email" className="text-right">
|
||
邮箱
|
||
</Label>
|
||
<Input id="user-email" className="col-span-3" placeholder="请输入邮箱地址" />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="user-source" className="text-right">
|
||
来源渠道
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="user-source" className="col-span-3">
|
||
<SelectValue placeholder="选择用户来源" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="wechat">微信</SelectItem>
|
||
<SelectItem value="douyin">抖音</SelectItem>
|
||
<SelectItem value="xiaohongshu">小红书</SelectItem>
|
||
<SelectItem value="website">官网</SelectItem>
|
||
<SelectItem value="offline">线下</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-start gap-4">
|
||
<Label htmlFor="user-tags" className="text-right pt-2">
|
||
用户标签
|
||
</Label>
|
||
<Textarea id="user-tags" className="col-span-3" placeholder="请输入用户标签,用逗号分隔" rows={3} />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-start gap-4">
|
||
<Label htmlFor="user-notes" className="text-right pt-2">
|
||
备注信息
|
||
</Label>
|
||
<Textarea id="user-notes" className="col-span-3" placeholder="用户备注信息" rows={3} />
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsAddingUser(false)}>
|
||
取消
|
||
</Button>
|
||
<Button onClick={() => setIsAddingUser(false)}>
|
||
<UserPlus className="mr-2 h-4 w-4" />
|
||
添加用户
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* 添加标签对话框 */}
|
||
<Dialog open={isAddingTag} onOpenChange={setIsAddingTag}>
|
||
<DialogContent className="sm:max-w-[500px]">
|
||
<DialogHeader>
|
||
<DialogTitle>新建标签</DialogTitle>
|
||
<DialogDescription>创建新的用户标签,完善标签分类体系</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="grid gap-4 py-4">
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="tag-category" className="text-right">
|
||
标签分类
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="tag-category" className="col-span-3">
|
||
<SelectValue placeholder="选择标签分类" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{tagCategories.map((category) => (
|
||
<SelectItem key={category.id} value={category.id}>
|
||
{category.name}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="tag-name" className="text-right">
|
||
标签名称
|
||
</Label>
|
||
<Input id="tag-name" className="col-span-3" placeholder="请输入标签名称" />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="tag-color" className="text-right">
|
||
标签颜色
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="tag-color" className="col-span-3">
|
||
<SelectValue placeholder="选择标签颜色" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="red">红色</SelectItem>
|
||
<SelectItem value="blue">蓝色</SelectItem>
|
||
<SelectItem value="green">绿色</SelectItem>
|
||
<SelectItem value="yellow">黄色</SelectItem>
|
||
<SelectItem value="purple">紫色</SelectItem>
|
||
<SelectItem value="pink">粉色</SelectItem>
|
||
<SelectItem value="orange">橙色</SelectItem>
|
||
<SelectItem value="gray">灰色</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-start gap-4">
|
||
<Label htmlFor="tag-description" className="text-right pt-2">
|
||
标签描述
|
||
</Label>
|
||
<Textarea id="tag-description" className="col-span-3" placeholder="标签描述信息" rows={3} />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<div className="text-right">
|
||
<Label htmlFor="auto-tag">自动打标</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2 col-span-3">
|
||
<Switch id="auto-tag" />
|
||
<Label htmlFor="auto-tag">启用自动打标规则</Label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsAddingTag(false)}>
|
||
取消
|
||
</Button>
|
||
<Button onClick={() => setIsAddingTag(false)}>
|
||
<Tags className="mr-2 h-4 w-4" />
|
||
创建标签
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
</TooltipProvider>
|
||
)
|
||
}
|