Files
users/app/user-portrait/page.tsx
v0 4eed69520c feat: refactor data asset center for enhanced search and analytics
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>
2025-07-25 06:42:34 +00:00

568 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
)
}