Files
users/app/user-portrait/page.tsx
v0 ecd8a48863 feat: optimize interface and database connection
Adjust MongoDB mock connector and update database structure.
Enhance sidebar, data platform, and AI analysis tools.
Clean up unnecessary code and update development docs.

#VERCEL_SKIP

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-07-21 00:11:52 +00:00

809 lines
34 KiB
TypeScript

"use client"
import { Badge } from "@/components/ui/badge"
import { Avatar } from "@/components/ui/avatar"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Search,
UserPlus,
Download,
MessageCircle,
ChevronDown,
ChevronRight,
Tag,
Users,
Eye,
Settings,
Smartphone,
MapPin,
Layers,
UserCheck,
} from "lucide-react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
export default function UserPortraitPage() {
const router = useRouter()
const [searchQuery, setSearchQuery] = useState("")
const [selectedCategory, setSelectedCategory] = useState("all")
const [selectedPool, setSelectedPool] = useState("all")
const [selectedDevice, setSelectedDevice] = useState("all") // 新增设备筛选
const [selectedWechatDialog, setSelectedWechatDialog] = useState<{ isOpen: boolean; accounts: any[] }>({
isOpen: false,
accounts: [],
})
const [expandedSections, setExpandedSections] = useState({
overview: true, // 用户池概览
userManagement: true, // 用户管理 (原用户池管理)
userSegmentation: false, // 用户分群 (原用户池分群)
tagManagement: false, // 标签管理 (原用户画像标签管理)
})
// 用户池概览数据 (从原 user-pool/page.tsx 整合)
const poolData = {
overview: {
totalUsers: 125678,
activeUsers: 89234,
newUsers: 3456,
privatePool: 78234,
publicPool: 47444,
},
deviceStats: {
ios: 45678,
android: 67890,
other: 12110,
},
locationStats: {
tier1: 56789,
tier2: 34567,
tier3: 23456,
overseas: 10866,
},
}
// 用户数据 (整合了更多设备和平台信息)
const mockUsers = [
{
id: "1",
name: "张明华",
company: "科技创新有限公司",
imei: "860123456789012",
phone: "13800138001",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "技术决策者", "活跃用户", "iOS用户", "企业级客户"],
category: "企业服务",
pool: "私域池",
lastActive: "今天 14:30",
value: 28500,
rfmScore: 95,
wechatAccounts: [
{ id: "wx1", name: "张明华", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "活跃"] },
{ id: "wx2", name: "张明华工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
],
deviceBrand: "iPhone", // 新增设备信息
deviceModel: "iPhone 14 Pro",
location: "北京市",
platform: "微信",
status: "在线",
},
{
id: "2",
name: "李雨婷",
company: "数字营销公司",
imei: "860987654321098",
phone: "13900139001",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中高价值", "产品经理", "内容创作者", "Android用户"],
category: "内容营销",
pool: "公域池",
lastActive: "今天 13:45",
value: 15800,
rfmScore: 78,
wechatAccounts: [
{ id: "wx3", name: "李雨婷", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
],
deviceBrand: "华为",
deviceModel: "Mate 50 Pro",
location: "上海市",
platform: "抖音",
status: "活跃",
},
{
id: "3",
name: "王建国",
company: "软件开发工作室",
imei: "860456789123456",
phone: "13700137001",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "技术专家", "开源贡献者", "MacOS用户"],
category: "技术社区",
pool: "私域池",
lastActive: "今天 12:20",
value: 32400,
rfmScore: 89,
wechatAccounts: [
{ id: "wx4", name: "王建国", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术交流"] },
],
deviceBrand: "小米",
deviceModel: "小米13 Ultra",
location: "深圳市",
platform: "微信",
status: "在线",
},
{
id: "4",
name: "刘思琪",
company: "数据分析咨询",
imei: "860789123456789",
phone: "13600136001",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中价值", "运营专家", "数据分析师", "Windows用户"],
category: "数据分析",
pool: "公域池",
lastActive: "今天 11:15",
value: 18900,
rfmScore: 72,
wechatAccounts: [
{ id: "wx5", name: "刘思琪", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
],
deviceBrand: "OPPO",
deviceModel: "Find X6 Pro",
location: "广州市",
platform: "小红书",
status: "活跃",
},
{
id: "5",
name: "陈浩然",
company: "云计算服务商",
imei: "860321654987321",
phone: "13500135001",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "架构师", "技术领导", "Linux用户"],
category: "技术架构",
pool: "私域池",
lastActive: "今天 10:30",
value: 45600,
rfmScore: 93,
wechatAccounts: [
{ id: "wx6", name: "陈浩然", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术分享"] },
{ id: "wx7", name: "陈浩然个人号", avatar: "/placeholder.svg?height=40&width=40", tags: ["个人账号"] },
],
deviceBrand: "vivo",
deviceModel: "X90 Pro+",
location: "杭州市",
platform: "微信",
status: "在线",
},
]
const [users, setUsers] = useState(mockUsers)
// 标签管理统计 (保持不变)
const tagStats = {
totalTags: 342,
activeTags: 287,
categories: 12,
coverage: 95.8,
topTags: [
{ tag: "高价值", count: 156, color: "bg-green-100 text-green-800" },
{ tag: "活跃用户", count: 134, color: "bg-blue-100 text-blue-800" },
{ tag: "技术专家", count: 98, color: "bg-purple-100 text-purple-800" },
{ tag: "决策者", count: 87, color: "bg-orange-100 text-orange-800" },
{ tag: "企业客户", count: 76, color: "bg-pink-100 text-pink-800" },
],
}
// 用户分析统计 (保持不变,但现在是用户画像的一部分)
const analysisStats = {
totalAnalyzed: 125678,
taggedUsers: 119845,
avgTagsPerUser: 4.2,
autoTagging: 89.5,
recentAnalysis: [
{ type: "设备品牌分析", count: 45, trend: "+12%" },
{ type: "地理位置分析", count: 38, trend: "+8%" },
{ type: "行为模式分析", count: 32, trend: "+15%" },
{ type: "价值分层分析", count: 28, trend: "+6%" },
],
}
// 用户分群数据 (从原 user-pool/page.tsx 整合)
const userSegments = [
{
name: "高价值用户组",
count: 12456,
description: "RFM评分 > 80",
color: "bg-blue-50 text-blue-600",
},
{
name: "潜力用户组",
count: 18765,
description: "增长潜力用户",
color: "bg-green-50 text-green-600",
},
{
name: "技术专家组",
count: 8234,
description: "技术决策者",
color: "bg-purple-50 text-purple-600",
},
]
// 获取所有类别、池子和设备品牌
const categories = ["all", ...new Set(users.map((user) => user.category))]
const pools = ["all", ...new Set(users.map((user) => user.pool))]
const devices = ["all", ...new Set(users.map((user) => user.deviceBrand))]
// 过滤用户的函数
const filteredUsers = users.filter((user) => {
const matchesSearch =
user.name.includes(searchQuery) ||
user.company.includes(searchQuery) ||
user.imei.includes(searchQuery) ||
user.phone.includes(searchQuery) ||
user.tags.some((tag) => tag.includes(searchQuery)) ||
user.category.includes(searchQuery) ||
user.deviceBrand.includes(searchQuery) || // 搜索中包含设备品牌
user.location.includes(searchQuery) // 搜索中包含位置
const matchesCategory = selectedCategory === "all" || user.category === selectedCategory
const matchesPool = selectedPool === "all" || user.pool === selectedPool
const matchesDevice = selectedDevice === "all" || user.deviceBrand === selectedDevice // 筛选设备品牌
return matchesSearch && matchesCategory && matchesPool && matchesDevice
})
// 处理用户点击
const handleUserClick = (userId: string) => {
router.push(`/user-portrait/${userId}`)
}
// 处理微信账号点击
const handleWechatClick = (userId: string) => {
const user = users.find((u) => u.id === userId)
if (user) {
setSelectedWechatDialog({
isOpen: true,
accounts: user.wechatAccounts,
})
}
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
const getStatusColor = (status: string) => {
switch (status) {
case "在线":
return "bg-green-100 text-green-800"
case "活跃":
return "bg-blue-100 text-blue-800"
case "离线":
return "bg-gray-100 text-gray-800"
default:
return "bg-gray-100 text-gray-800"
}
}
return (
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<h1 className="text-2xl md:text-3xl font-bold tracking-tight"></h1>
<p className="text-sm md:text-base text-muted-foreground">IMEI</p>
</div>
<div className="flex gap-2">
<Button size="sm" className="flex-1 md:flex-none">
<UserPlus className="mr-2 h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="flex-1 md:flex-none bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
{/* 用户池概览 (从原 user-pool/page.tsx 整合) */}
<Collapsible
open={expandedSections.overview}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
>
<Card className="border-none shadow-md">
<CollapsibleTrigger className="w-full">
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<Users className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
<div className="text-left">
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.overview ? (
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="p-4">
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-4 mb-6">
<div className="bg-blue-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-blue-600">
{poolData.overview.totalUsers.toLocaleString()}
</div>
<div className="text-xs text-blue-700"></div>
</div>
<div className="bg-green-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-green-600">
{poolData.overview.activeUsers.toLocaleString()}
</div>
<div className="text-xs text-green-700"></div>
</div>
<div className="bg-purple-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-purple-600">
{poolData.overview.newUsers.toLocaleString()}
</div>
<div className="text-xs text-purple-700"></div>
</div>
<div className="bg-orange-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-orange-600">
{poolData.overview.privatePool.toLocaleString()}
</div>
<div className="text-xs text-orange-700"></div>
</div>
<div className="bg-pink-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-pink-600">
{poolData.overview.publicPool.toLocaleString()}
</div>
<div className="text-xs text-pink-700"></div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-medium mb-3 flex items-center">
<Smartphone className="h-4 w-4 mr-2 text-gray-600" />
</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">iOS设备</span>
<span className="font-bold">{poolData.deviceStats.ios.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-sm">Android设备</span>
<span className="font-bold">{poolData.deviceStats.android.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-sm"></span>
<span className="font-bold">{poolData.deviceStats.other.toLocaleString()}</span>
</div>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-medium mb-3 flex items-center">
<MapPin className="h-4 w-4 mr-2 text-gray-600" />
</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">线</span>
<span className="font-bold">{poolData.locationStats.tier1.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-sm">线</span>
<span className="font-bold">{poolData.locationStats.tier2.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-sm">线</span>
<span className="font-bold">{poolData.locationStats.tier3.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-sm"></span>
<span className="font-bold">{poolData.locationStats.overseas.toLocaleString()}</span>
</div>
</div>
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 用户管理 (原用户池的用户管理,现在是用户画像的一部分) */}
<Collapsible
open={expandedSections.userManagement}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userManagement: open }))}
>
<Card className="border-none shadow-md">
<CollapsibleTrigger className="w-full">
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<UserCheck className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
<div className="text-left">
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm">
IMEI ({filteredUsers.length})
</CardDescription>
</div>
</div>
{expandedSections.userManagement ? (
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="p-4">
{/* 搜索和筛选 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索用户、IMEI、手机号、设备、位置..."
className="pl-8 text-sm"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Select value={selectedPool} onValueChange={setSelectedPool}>
<SelectTrigger className="text-sm">
<SelectValue placeholder="选择流量池" />
</SelectTrigger>
<SelectContent>
{pools.map((pool) => (
<SelectItem key={pool} value={pool}>
{pool === "all" ? "所有流量池" : pool}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={selectedDevice} onValueChange={setSelectedDevice}>
<SelectTrigger className="text-sm">
<SelectValue placeholder="选择设备品牌" />
</SelectTrigger>
<SelectContent>
{devices.map((device) => (
<SelectItem key={device} value={device}>
{device === "all" ? "所有设备" : device}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 移动端卡片布局 */}
<div className="space-y-3 md:hidden">
{filteredUsers.map((user) => (
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
</Avatar>
<div>
<p className="font-medium text-sm">{user.name}</p>
<p className="text-xs text-gray-500">{user.lastActive}</p>
</div>
</div>
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<p className="text-gray-500">IMEI</p>
<p className="font-mono">{user.imei.slice(-6)}</p>
</div>
<div>
<p className="text-gray-500"></p>
<p className="font-medium">{user.phone}</p>
</div>
<div>
<p className="text-gray-500"></p>
<p className="font-medium">{user.deviceBrand}</p>
</div>
<div>
<p className="text-gray-500"></p>
<p className="font-medium">{user.location}</p>
</div>
</div>
<div className="flex items-center justify-between text-xs">
<div>
<p className="text-gray-500"></p>
<Badge variant="outline" className="text-xs">
{user.pool}
</Badge>
</div>
<div>
<p className="text-gray-500"></p>
<Badge variant="outline" className="text-xs">
{user.platform}
</Badge>
</div>
</div>
<div className="flex flex-wrap gap-1">
{user.tags.map((tag, index) => (
<Badge key={index} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
</div>
<div className="flex items-center justify-between">
<Button variant="ghost" size="sm" className="text-xs">
<MessageCircle className="h-3 w-3 mr-1" />
</Button>
<Button
variant="outline"
size="sm"
className="text-xs bg-transparent"
onClick={() => handleUserClick(user.id)}
>
<Eye className="h-3 w-3 mr-1" />
</Button>
</div>
</div>
))}
</div>
{/* 桌面端表格布局 */}
<div className="hidden md:block overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-gray-50">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">IMEI/</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">/</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500"></th>
</tr>
</thead>
<tbody className="divide-y">
{filteredUsers.map((user) => (
<tr key={user.id} className="hover:bg-gray-50">
<td className="px-4 py-3 whitespace-nowrap">
<div className="flex items-center gap-3">
<Avatar>
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
</Avatar>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-gray-500">{user.lastActive}</p>
</div>
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<p className="font-mono text-sm">{user.imei}</p>
<p className="font-medium">{user.phone}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<div>
<p className="font-medium">{user.deviceBrand}</p>
<p className="text-sm text-gray-500">{user.deviceModel}</p>
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<p className="text-sm">{user.location}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<div className="space-y-1">
<Badge variant="outline" className="text-xs">
{user.pool}
</Badge>
<p className="text-xs text-gray-500">{user.platform}</p>
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<div className="flex flex-wrap gap-1 max-w-xs">
{user.tags.slice(0, 3).map((tag, index) => (
<Badge key={index} variant="outline" className="text-xs">
{tag}
</Badge>
))}
{user.tags.length > 3 && (
<Badge variant="outline" className="text-xs">
+{user.tags.length - 3}
</Badge>
)}
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={() => handleUserClick(user.id)}>
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)}>
<MessageCircle className="h-4 w-4" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 用户分群 (从原 user-pool/page.tsx 整合) */}
<Collapsible
open={expandedSections.userSegmentation}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userSegmentation: open }))}
>
<Card className="border-none shadow-md">
<CollapsibleTrigger className="w-full">
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<Layers className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
<div className="text-left">
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.userSegmentation ? (
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="p-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{userSegments.map((segment, index) => (
<div key={index} className={`${segment.color} p-4 rounded-lg`}>
<h3 className="font-medium mb-2">{segment.name}</h3>
<p className="text-2xl font-bold text-current">{segment.count.toLocaleString()}</p>
<p className="text-sm text-gray-600 mt-1">{segment.description}</p>
<div className="mt-3">
<Button size="sm" variant="outline" className="w-full bg-transparent">
</Button>
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 标签管理 (原用户画像的标签管理,现在是用户画像的一部分) */}
<Collapsible
open={expandedSections.tagManagement}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, tagManagement: open }))}
>
<Card className="border shadow-sm">
<CollapsibleTrigger className="w-full">
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer py-3 md:py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Tag className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
<div className="text-left">
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.tagManagement ? (
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="pt-0 px-4 md:px-6">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4 mb-4">
<div className="bg-purple-50 p-2 md:p-3 rounded-lg">
<p className="text-xs md:text-sm font-medium text-purple-600"></p>
<p className="text-lg md:text-2xl font-bold text-purple-900">{tagStats.totalTags}</p>
</div>
<div className="bg-blue-50 p-2 md:p-3 rounded-lg">
<p className="text-xs md:text-sm font-medium text-blue-600"></p>
<p className="text-lg md:text-2xl font-bold text-blue-900">{tagStats.activeTags}</p>
</div>
<div className="bg-green-50 p-2 md:p-3 rounded-lg">
<p className="text-xs md:text-sm font-medium text-green-600"></p>
<p className="text-lg md:text-2xl font-bold text-green-900">{tagStats.coverage}%</p>
</div>
<div className="bg-orange-50 p-2 md:p-3 rounded-lg">
<Button
variant="outline"
size="sm"
className="w-full h-full bg-transparent text-xs md:text-sm"
onClick={() => router.push("/user-portrait/tags")}
>
<Settings className="mr-1 md:mr-2 h-3 w-3 md:h-4 md:w-4" />
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-5 gap-2">
{tagStats.topTags.map((item, index) => (
<div key={index} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<Badge className={`${item.color} text-xs`}>{item.tag}</Badge>
<span className="text-xs font-bold ml-2">{item.count}</span>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 微信账号对话框 */}
<Dialog
open={selectedWechatDialog.isOpen}
onOpenChange={(open) => setSelectedWechatDialog({ ...selectedWechatDialog, isOpen: open })}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{selectedWechatDialog.accounts.map((account) => (
<div key={account.id} className="flex items-center justify-between p-2 border rounded-md">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
<img src={account.avatar || "/placeholder.svg"} alt={account.name} />
</Avatar>
<div>
<p className="font-medium">{account.name}</p>
<div className="flex gap-1 mt-1">
{account.tags.map((tag, index) => (
<Badge key={index} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
</div>
</div>
</div>
<Button variant="outline" size="sm">
</Button>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
)
}