refactor: streamline mobile interface for data analysis focus

Remove settings, optimize mobile layout, highlight data analysis features
Use generic company names, simulate today's timeline, responsive design

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-19 02:17:17 +00:00
parent 2408d50cb0
commit ea95107713
8 changed files with 1072 additions and 578 deletions

View File

@@ -2,14 +2,13 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Home, Users, BarChart3, Database, Settings } from "lucide-react"
import { Home, Users, Target, Database } from "lucide-react"
const navItems = [
{ href: "/", icon: Home, label: "首页" },
{ href: "/", icon: Home, label: "概览" },
{ href: "/data-integration", icon: Database, label: "数据集成" },
{ href: "/user-portrait", icon: Users, label: "用户画像" },
{ href: "/user-value", icon: BarChart3, label: "用户估值" },
{ href: "/data-platform", icon: Database, label: "数据中台" },
{ href: "/settings", icon: Settings, label: "设置" },
{ href: "/user-value", icon: Target, label: "价值评估" },
]
export default function BottomNav() {
@@ -25,11 +24,11 @@ export default function BottomNav() {
<Link
key={item.href}
href={item.href}
className={`flex flex-col items-center justify-center px-3 py-2 rounded-xl transition-all duration-300 min-w-0 flex-1 ${
className={`flex flex-col items-center justify-center px-2 py-2 rounded-xl transition-all duration-300 min-w-0 flex-1 ${
isActive ? "glass-heavy text-blue-600 scale-105" : "text-gray-600 hover:glass-light hover:text-blue-500"
}`}
>
<item.icon className={`h-5 w-5 mb-1 ${isActive ? "text-blue-600" : ""}`} />
<item.icon className={`h-4 w-4 mb-1 ${isActive ? "text-blue-600" : ""}`} />
<span className="text-xs font-medium truncate">{item.label}</span>
</Link>
)

View File

@@ -7,7 +7,6 @@ import { cn } from "@/lib/utils"
import {
Database,
LayoutDashboard,
Settings,
Users,
Target,
X,
@@ -28,7 +27,7 @@ interface MobileSidebarProps {
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
const pathname = usePathname()
const [expandedSections, setExpandedSections] = useState({
"user-portrait": true,
"user-portrait": false,
"user-value": false,
})
@@ -53,55 +52,63 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
const navItems = [
{
title: "概览",
title: "数据概览",
href: "/",
icon: <LayoutDashboard className="h-5 w-5" />,
icon: <LayoutDashboard className="h-4 w-4" />,
primary: true,
description: "平台整体数据分析",
},
{
title: "数据集成",
href: "/data-integration",
icon: <Database className="h-5 w-5" />,
icon: <Database className="h-4 w-4" />,
primary: true,
tag: "核心",
description: "多源数据整合平台",
},
{
title: "用户画像",
href: "/user-portrait",
icon: <Users className="h-5 w-5" />,
icon: <Users className="h-4 w-4" />,
primary: true,
expandable: true,
section: "user-portrait",
description: "用户标签与分群分析",
children: [
{
title: "用户词",
href: "/user-portrait/user-keywords",
icon: <Tag className="h-4 w-4" />,
title: "流量关键词",
href: "/user-portrait/keywords",
icon: <Tag className="h-3 w-3" />,
description: "关键词价值评估",
},
{
title: "标签管理",
href: "/user-portrait/tags",
icon: <Tag className="h-4 w-4" />,
icon: <Tag className="h-3 w-3" />,
description: "用户标签分类",
},
],
},
{
title: "用户估值",
title: "价值评估",
href: "/user-value",
icon: <Target className="h-5 w-5" />,
icon: <Target className="h-4 w-4" />,
primary: true,
expandable: true,
section: "user-value",
description: "用户价值分析模型",
children: [
{
title: "估值模型",
href: "/user-value/model",
icon: <BarChart3 className="h-4 w-4" />,
icon: <BarChart3 className="h-3 w-3" />,
description: "RFM价值模型",
},
{
title: "用户升级路径",
title: "升级路径",
href: "/user-value/upgrade-paths",
icon: <TrendingUp className="h-4 w-4" />,
icon: <TrendingUp className="h-3 w-3" />,
description: "用户价值提升",
},
],
},
@@ -110,9 +117,9 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
const getTagColor = (tag: string) => {
switch (tag) {
case "核心":
return "glass-light text-orange-700 border-orange-200"
return "bg-blue-100 text-blue-700 border-blue-200"
default:
return "glass-light text-gray-700 border-gray-200"
return "bg-gray-100 text-gray-700 border-gray-200"
}
}
@@ -130,56 +137,63 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
{/* 侧边栏 */}
<div
className={cn(
"fixed left-0 top-0 h-full w-80 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
"fixed left-0 top-0 h-full w-72 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
isOpen ? "translate-x-0" : "-translate-x-full",
)}
>
{/* 头部 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-white/20">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg glass-light flex items-center justify-center">
<Database className="h-5 w-5 text-blue-600" />
<div className="flex items-center justify-between px-4 py-3 border-b border-white/20">
<div className="flex items-center space-x-2">
<div className="w-6 h-6 rounded-lg glass-light flex items-center justify-center">
<Database className="h-4 w-4 text-blue-600" />
</div>
<h1 className="text-lg font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
<h1 className="text-sm font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
</h1>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="glass-light rounded-xl">
<X className="h-5 w-5" />
<Button variant="ghost" size="icon" onClick={onClose} className="glass-light rounded-lg h-8 w-8">
<X className="h-4 w-4" />
</Button>
</div>
{/* 导航菜单 */}
<div className="flex-1 overflow-y-auto py-4">
<nav className="space-y-2 px-4">
<div className="flex-1 overflow-y-auto py-2">
<nav className="space-y-1 px-3">
{navItems.map((item) => (
<div key={item.href}>
<div
className={cn(
"flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-300 group",
"flex items-center px-3 py-2 text-xs font-medium rounded-lg transition-all duration-300 group",
pathname === item.href
? "glass-heavy text-blue-700 shadow-glass"
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600",
)}
>
<Link href={item.href} onClick={onClose} className="flex items-center flex-1">
<div className="transition-colors duration-300">{item.icon}</div>
<div className="flex-1 flex items-center justify-between ml-3">
<span className="font-medium">{item.title}</span>
{item.tag && (
<Badge className={cn("text-xs px-2 py-1 rounded-lg", getTagColor(item.tag))}>{item.tag}</Badge>
)}
<Link href={item.href} onClick={onClose} className="flex items-center flex-1 min-w-0">
<div className="transition-colors duration-300 flex-shrink-0">{item.icon}</div>
<div className="flex-1 ml-2 min-w-0">
<div className="flex items-center justify-between">
<span className="font-medium truncate">{item.title}</span>
{item.tag && (
<Badge
className={cn("text-xs px-1.5 py-0.5 rounded ml-1 flex-shrink-0", getTagColor(item.tag))}
>
{item.tag}
</Badge>
)}
</div>
<p className="text-xs text-gray-500 truncate mt-0.5">{item.description}</p>
</div>
</Link>
{item.expandable && (
<button
onClick={() => toggleSection(item.section!)}
className="ml-2 p-1 hover:bg-white/20 rounded-lg transition-colors duration-200"
className="ml-1 p-1 hover:bg-white/20 rounded transition-colors duration-200 flex-shrink-0"
>
{expandedSections[item.section!] ? (
<ChevronDown className="h-4 w-4" />
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-4 w-4" />
<ChevronRight className="h-3 w-3" />
)}
</button>
)}
@@ -187,21 +201,26 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
{/* 子菜单 */}
{item.children && expandedSections[item.section!] && (
<div className="ml-6 mt-2 space-y-1">
<div className="ml-4 mt-1 space-y-1">
{item.children.map((subItem) => (
<Link
key={subItem.href}
href={subItem.href}
onClick={onClose}
className={cn(
"flex items-center px-3 py-2 text-sm rounded-lg transition-all duration-300",
"flex items-center px-2 py-1.5 text-xs rounded transition-all duration-300",
pathname === subItem.href
? "glass-light text-blue-600 shadow-glass-sm"
: "text-gray-600 hover:glass-light hover:text-blue-500",
)}
>
<div className="mr-3 text-gray-400 transition-colors duration-300">{subItem.icon}</div>
<span>{subItem.title}</span>
<div className="mr-2 text-gray-400 transition-colors duration-300 flex-shrink-0">
{subItem.icon}
</div>
<div className="min-w-0 flex-1">
<p className="font-medium truncate">{subItem.title}</p>
<p className="text-xs text-gray-500 truncate">{subItem.description}</p>
</div>
</Link>
))}
</div>
@@ -211,21 +230,21 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
</nav>
</div>
{/* 底部设置 */}
<div className="border-t border-white/20 p-4">
<Link
href="/settings"
onClick={onClose}
className={cn(
"flex items-center px-4 py-2 text-xs font-medium rounded-lg transition-all duration-300 w-full",
pathname === "/settings"
? "glass-heavy text-blue-700 shadow-glass"
: "glass-light text-gray-600 hover:glass-heavy hover:text-blue-500",
)}
>
<Settings className="h-4 w-4 transition-colors duration-300" />
<span className="ml-2"></span>
</Link>
{/* 底部信息 */}
<div className="border-t border-white/20 p-3">
<div className="glass-light rounded-lg p-2">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-600"></span>
<div className="flex items-center">
<div className="w-2 h-2 bg-green-500 rounded-full mr-1"></div>
<span className="text-green-600"></span>
</div>
</div>
<div className="flex items-center justify-between text-xs mt-1">
<span className="text-gray-600"></span>
<span className="text-gray-500">2</span>
</div>
</div>
</div>
</div>
</>

View File

@@ -38,48 +38,48 @@ export default function UserPortrait() {
userList: true,
})
// 使用真实的用户数据
// 使用通用公司名称和当天时间线的用户数据
const mockUsers = [
{
id: "1",
name: "明华",
company: "阿里巴巴集团",
name: "明华",
company: "科技创新有限公司",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "技术决策者", "活跃用户", "iOS用户", "企业级客户"],
category: "企业服务",
pool: "私域池",
lastActive: "2024-01-18",
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: ["工作账号"] },
{ id: "wx1", name: "明华", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "活跃"] },
{ id: "wx2", name: "明华工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
],
},
{
id: "2",
name: "雨婷",
company: "腾讯科技",
name: "雨婷",
company: "数字营销公司",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中高价值", "产品经理", "内容创作者", "Android用户"],
category: "内容营销",
pool: "公域池",
lastActive: "2024-01-17",
lastActive: "今天 13:45",
value: 15800,
rfmScore: 78,
wechatAccounts: [
{ id: "wx3", name: "雨婷", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
{ id: "wx3", name: "雨婷", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
],
},
{
id: "3",
name: "王建国",
company: "字节跳动",
company: "软件开发工作室",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "技术专家", "开源贡献者", "MacOS用户"],
category: "技术社区",
pool: "私域池",
lastActive: "2024-01-16",
lastActive: "今天 12:20",
value: 32400,
rfmScore: 89,
wechatAccounts: [
@@ -89,12 +89,12 @@ export default function UserPortrait() {
{
id: "4",
name: "刘思琪",
company: "美团点评",
company: "数据分析咨询",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中价值", "运营专家", "数据分析师", "Windows用户"],
category: "数据分析",
pool: "私域池",
lastActive: "2024-01-15",
lastActive: "今天 11:15",
value: 18900,
rfmScore: 72,
wechatAccounts: [
@@ -104,12 +104,12 @@ export default function UserPortrait() {
{
id: "5",
name: "陈浩然",
company: "京东集团",
company: "云计算服务商",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "架构师", "技术领导", "Linux用户"],
category: "技术架构",
pool: "私域池",
lastActive: "2024-01-14",
lastActive: "今天 10:30",
value: 45600,
rfmScore: 93,
wechatAccounts: [
@@ -120,12 +120,12 @@ export default function UserPortrait() {
{
id: "6",
name: "周欣妍",
company: "滴滴出行",
company: "用户体验设计",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中价值", "UI设计师", "用户体验", "iOS用户"],
category: "设计创意",
pool: "公域池",
lastActive: "2024-01-13",
lastActive: "今天 09:45",
value: 12300,
rfmScore: 65,
wechatAccounts: [
@@ -135,12 +135,12 @@ export default function UserPortrait() {
{
id: "7",
name: "马志强",
company: "华为技术",
company: "企业解决方案",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["高价值", "解决方案专家", "B2B销售", "Android用户"],
category: "企业解决方案",
pool: "私域池",
lastActive: "2024-01-12",
lastActive: "今天 08:20",
value: 38700,
rfmScore: 87,
wechatAccounts: [
@@ -150,12 +150,12 @@ export default function UserPortrait() {
{
id: "8",
name: "赵丽娟",
company: "小米科技",
company: "品牌营销策划",
avatar: "/placeholder.svg?height=40&width=40",
tags: ["中高价值", "市场营销", "品牌推广", "iOS用户"],
category: "品牌营销",
pool: "公域池",
lastActive: "2024-01-11",
lastActive: "今天 07:30",
value: 21500,
rfmScore: 76,
wechatAccounts: [
@@ -167,17 +167,17 @@ export default function UserPortrait() {
const [users, setUsers] = useState(mockUsers)
// 用户词统计
const userKeywordStats = {
// 流量关键词统计
const keywordStats = {
totalKeywords: 156,
activeKeywords: 124,
coverage: 89.4,
topKeywords: [
{ keyword: "技术", count: 45, trend: "+12%" },
{ keyword: "产品", count: 38, trend: "+8%" },
{ keyword: "运营", count: 32, trend: "+15%" },
{ keyword: "设计", count: 28, trend: "+6%" },
{ keyword: "市场", count: 25, trend: "+10%" },
{ keyword: "技术解决方案", count: 45, trend: "+12%", value: "高" },
{ keyword: "数据分析", count: 38, trend: "+8%", value: "中高" },
{ keyword: "用户体验", count: 32, trend: "+15%", value: "中" },
{ keyword: "品牌营销", count: 28, trend: "+6%", value: "中高" },
{ keyword: "企业服务", count: 25, trend: "+10%", value: "高" },
],
}
@@ -237,81 +237,98 @@ export default function UserPortrait() {
}))
}
const getValueColor = (value: string) => {
switch (value) {
case "高":
return "bg-green-100 text-green-800"
case "中高":
return "bg-blue-100 text-blue-800"
case "中":
return "bg-yellow-100 text-yellow-800"
default:
return "bg-gray-100 text-gray-800"
}
}
return (
<div className="container mx-auto py-6 space-y-6">
<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-3xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground"></p>
<h1 className="text-2xl md:text-3xl font-bold tracking-tight"></h1>
<p className="text-sm md:text-base text-muted-foreground"></p>
</div>
<div className="flex gap-2">
<Button>
<Button size="sm" className="flex-1 md:flex-none">
<UserPlus className="mr-2 h-4 w-4" />
</Button>
<Button variant="outline">
<Button variant="outline" size="sm" className="flex-1 md:flex-none bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
{/* 用户词管理 */}
{/* 流量关键词管理 */}
<Collapsible
open={expandedSections.userKeywords}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userKeywords: open }))}
>
<Card className="border shadow-sm">
<CollapsibleTrigger className="w-full">
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer">
<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-5 w-5 text-blue-600" />
<Tag className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
<div className="text-left">
<CardTitle></CardTitle>
<CardDescription></CardDescription>
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.userKeywords ? (
<ChevronDown className="h-5 w-5" />
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-5 w-5" />
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="pt-0">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div className="bg-blue-50 p-3 rounded-lg">
<p className="text-sm font-medium text-blue-600"></p>
<p className="text-2xl font-bold text-blue-900">{userKeywordStats.totalKeywords}</p>
<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-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">{keywordStats.totalKeywords}</p>
</div>
<div className="bg-green-50 p-3 rounded-lg">
<p className="text-sm font-medium text-green-600"></p>
<p className="text-2xl font-bold text-green-900">{userKeywordStats.activeKeywords}</p>
<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">{keywordStats.activeKeywords}</p>
</div>
<div className="bg-purple-50 p-3 rounded-lg">
<p className="text-sm font-medium text-purple-600"></p>
<p className="text-2xl font-bold text-purple-900">{userKeywordStats.coverage}%</p>
<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">{keywordStats.coverage}%</p>
</div>
<div className="bg-orange-50 p-3 rounded-lg">
<div className="bg-orange-50 p-2 md:p-3 rounded-lg">
<Button
variant="outline"
className="w-full h-full bg-transparent"
onClick={() => router.push("/user-portrait/user-keywords")}
size="sm"
className="w-full h-full bg-transparent text-xs md:text-sm"
onClick={() => router.push("/user-portrait/keywords")}
>
<Settings className="mr-2 h-4 w-4" />
<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">
{userKeywordStats.topKeywords.map((item, index) => (
<div key={index} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<span className="text-sm font-medium">{item.keyword}</span>
<div className="text-right">
<p className="text-sm font-bold">{item.count}</p>
<div className="space-y-2">
{keywordStats.topKeywords.map((item, index) => (
<div key={index} className="flex items-center justify-between p-2 md:p-3 bg-gray-50 rounded">
<div className="flex items-center gap-2 min-w-0 flex-1">
<span className="text-xs md:text-sm font-medium truncate">{item.keyword}</span>
<Badge className={`text-xs ${getValueColor(item.value)}`}>{item.value}</Badge>
</div>
<div className="text-right flex-shrink-0">
<p className="text-xs md:text-sm font-bold">{item.count}</p>
<p className="text-xs text-green-600">{item.trend}</p>
</div>
</div>
@@ -329,54 +346,55 @@ export default function UserPortrait() {
>
<Card className="border shadow-sm">
<CollapsibleTrigger className="w-full">
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer">
<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-5 w-5 text-purple-600" />
<Tag className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
<div className="text-left">
<CardTitle></CardTitle>
<CardDescription></CardDescription>
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.tagManagement ? (
<ChevronDown className="h-5 w-5" />
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-5 w-5" />
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
)}
</div>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="pt-0">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div className="bg-purple-50 p-3 rounded-lg">
<p className="text-sm font-medium text-purple-600"></p>
<p className="text-2xl font-bold text-purple-900">{tagStats.totalTags}</p>
<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-3 rounded-lg">
<p className="text-sm font-medium text-blue-600"></p>
<p className="text-2xl font-bold text-blue-900">{tagStats.activeTags}</p>
<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-3 rounded-lg">
<p className="text-sm font-medium text-green-600"></p>
<p className="text-2xl font-bold text-green-900">{tagStats.coverage}%</p>
<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-3 rounded-lg">
<div className="bg-orange-50 p-2 md:p-3 rounded-lg">
<Button
variant="outline"
className="w-full h-full bg-transparent"
size="sm"
className="w-full h-full bg-transparent text-xs md:text-sm"
onClick={() => router.push("/user-portrait/tags")}
>
<Settings className="mr-2 h-4 w-4" />
<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}>{item.tag}</Badge>
<span className="text-sm font-bold">{item.count}</span>
<Badge className={`${item.color} text-xs`}>{item.tag}</Badge>
<span className="text-xs font-bold ml-2">{item.count}</span>
</div>
))}
</div>
@@ -392,35 +410,41 @@ export default function UserPortrait() {
>
<Card className="border shadow-sm">
<CollapsibleTrigger className="w-full">
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer">
<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">
<Users className="h-5 w-5 text-green-600" />
<Users className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
<div className="text-left">
<CardTitle></CardTitle>
<CardDescription> ({filteredUsers.length})</CardDescription>
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm">
({filteredUsers.length})
</CardDescription>
</div>
</div>
{expandedSections.userList ? <ChevronDown className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
{expandedSections.userList ? (
<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">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<CardContent className="pt-0 px-4 md:px-6">
<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="搜索用户、公司或标签..."
className="pl-8"
className="pl-8 text-sm"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<SelectTrigger>
<SelectTrigger className="text-sm">
<SelectValue placeholder="选择场景分类" />
</SelectTrigger>
<SelectContent>
@@ -433,7 +457,7 @@ export default function UserPortrait() {
</Select>
<Select value={selectedPool} onValueChange={setSelectedPool}>
<SelectTrigger>
<SelectTrigger className="text-sm">
<SelectValue placeholder="选择流量池" />
</SelectTrigger>
<SelectContent>
@@ -446,7 +470,66 @@ export default function UserPortrait() {
</Select>
</div>
<div className="overflow-x-auto">
<div className="space-y-3 md:hidden">
{/* 移动端卡片布局 */}
{filteredUsers.map((user) => (
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-2">
<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.company}</p>
</div>
</div>
<Badge
className={
user.rfmScore >= 90
? "bg-green-100 text-green-800"
: user.rfmScore >= 80
? "bg-blue-100 text-blue-800"
: user.rfmScore >= 70
? "bg-yellow-100 text-yellow-800"
: "bg-gray-100 text-gray-800"
}
>
{user.rfmScore}
</Badge>
</div>
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500">{user.lastActive}</span>
<span className="font-medium">¥{user.value.toLocaleString()}</span>
</div>
<div className="flex flex-wrap gap-1">
{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>
<div className="flex items-center justify-between">
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)} className="text-xs">
<MessageCircle className="h-3 w-3 mr-1" />
{user.wechatAccounts.length}
</Button>
<Button variant="outline" size="sm" onClick={() => handleUserClick(user.id)} className="text-xs">
<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">

View File

@@ -27,12 +27,12 @@ import {
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
// 真实用户数据
// 使用通用公司名称和当天时间线的用户数据
const mockUsers = [
{
id: "1",
name: "明华",
company: "阿里巴巴集团",
name: "明华",
company: "科技创新有限公司",
recency: 1,
frequency: 28,
monetary: 45600,
@@ -42,11 +42,12 @@ const mockUsers = [
upgradeFromDays: 45,
nextUpgrade: "VIP客户",
upgradeProbability: 85,
lastActive: "今天 14:30",
},
{
id: "2",
name: "雨婷",
company: "腾讯科技",
name: "雨婷",
company: "数字营销公司",
recency: 3,
frequency: 22,
monetary: 32800,
@@ -56,11 +57,12 @@ const mockUsers = [
upgradeFromDays: 28,
nextUpgrade: "白金会员",
upgradeProbability: 78,
lastActive: "今天 13:45",
},
{
id: "3",
name: "王建国",
company: "字节跳动",
company: "软件开发工作室",
recency: 2,
frequency: 25,
monetary: 38900,
@@ -70,11 +72,12 @@ const mockUsers = [
upgradeFromDays: 38,
nextUpgrade: "企业VIP",
upgradeProbability: 82,
lastActive: "今天 12:20",
},
{
id: "4",
name: "刘思琪",
company: "美团点评",
company: "数据分析咨询",
recency: 7,
frequency: 18,
monetary: 25400,
@@ -84,11 +87,12 @@ const mockUsers = [
upgradeFromDays: 12,
nextUpgrade: "高价值用户",
upgradeProbability: 68,
lastActive: "今天 11:15",
},
{
id: "5",
name: "陈浩然",
company: "京东集团",
company: "云计算服务商",
recency: 4,
frequency: 20,
monetary: 28700,
@@ -98,11 +102,12 @@ const mockUsers = [
upgradeFromDays: 22,
nextUpgrade: "高价值用户",
upgradeProbability: 72,
lastActive: "今天 10:30",
},
{
id: "6",
name: "周欣妍",
company: "滴滴出行",
company: "用户体验设计",
recency: 12,
frequency: 15,
monetary: 18600,
@@ -112,11 +117,12 @@ const mockUsers = [
upgradeFromDays: 8,
nextUpgrade: "中高价值用户",
upgradeProbability: 55,
lastActive: "今天 09:45",
},
{
id: "7",
name: "马志强",
company: "华为技术",
company: "企业解决方案",
recency: 6,
frequency: 16,
monetary: 22100,
@@ -126,11 +132,12 @@ const mockUsers = [
upgradeFromDays: 15,
nextUpgrade: "中高价值用户",
upgradeProbability: 61,
lastActive: "今天 08:20",
},
{
id: "8",
name: "赵丽娟",
company: "小米科技",
company: "品牌营销策划",
recency: 15,
frequency: 12,
monetary: 15800,
@@ -140,6 +147,7 @@ const mockUsers = [
upgradeFromDays: 5,
nextUpgrade: "中高价值用户",
upgradeProbability: 45,
lastActive: "今天 07:30",
},
]
@@ -186,33 +194,33 @@ export default function UserValuePage() {
}
return (
<div className="container mx-auto p-4 space-y-6">
<div className="container mx-auto p-4 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-3xl font-bold"></h1>
<p className="text-muted-foreground mt-1"></p>
<h1 className="text-2xl md:text-3xl font-bold"></h1>
<p className="text-sm md:text-base text-muted-foreground mt-1"></p>
</div>
<div className="flex flex-wrap gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="选择时间范围" />
<SelectTrigger className="w-[120px] md:w-[140px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">7</SelectItem>
<SelectItem value="30days">30</SelectItem>
<SelectItem value="90days">90</SelectItem>
<SelectItem value="7days">7</SelectItem>
<SelectItem value="30days">30</SelectItem>
<SelectItem value="90days">90</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline">
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
@@ -224,118 +232,122 @@ export default function UserValuePage() {
>
<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">
<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">
<Target className="h-5 w-5 text-green-600" />
<Target className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
<div className="text-left">
<CardTitle></CardTitle>
<CardDescription></CardDescription>
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.overview ? <ChevronDown className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
{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-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
<Card className="bg-gradient-to-br from-green-50 to-emerald-50 border-none shadow-sm">
<CardContent className="p-4">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-sm font-medium text-green-600"></div>
<div className="text-2xl font-bold mt-2">¥3,248</div>
<div className="text-sm text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-4 w-4 mr-1" />
8.2%
<div className="text-xs md:text-sm font-medium text-green-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">¥3,248</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+8.2%
</div>
</div>
<div className="bg-green-100 p-2 rounded-full">
<Target className="h-5 w-5 text-green-600" />
<div className="bg-green-100 p-1.5 md:p-2 rounded-full">
<Target className="h-3 w-3 md:h-5 md:w-5 text-green-600" />
</div>
</div>
<div className="mt-3">
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span></span>
<span>94%</span>
</div>
<Progress value={94} className="h-1.5 bg-green-100" />
<Progress value={94} className="h-1 md:h-1.5 bg-green-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-none shadow-sm">
<CardContent className="p-4">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-sm font-medium text-purple-600"></div>
<div className="text-2xl font-bold mt-2">45,678</div>
<div className="text-sm text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-4 w-4 mr-1" />
12.3%
<div className="text-xs md:text-sm font-medium text-purple-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">45,678</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+12.3%
</div>
</div>
<div className="bg-purple-100 p-2 rounded-full">
<TrendingUp className="h-5 w-5 text-purple-600" />
<div className="bg-purple-100 p-1.5 md:p-2 rounded-full">
<TrendingUp className="h-3 w-3 md:h-5 md:w-5 text-purple-600" />
</div>
</div>
<div className="mt-3">
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span></span>
<span>28.6%</span>
</div>
<Progress value={28.6} className="h-1.5 bg-purple-100" />
<Progress value={28.6} className="h-1 md:h-1.5 bg-purple-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-none shadow-sm">
<CardContent className="p-4">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-sm font-medium text-blue-600"></div>
<div className="text-2xl font-bold mt-2">15.8%</div>
<div className="text-sm text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-4 w-4 mr-1" />
2.1%
<div className="text-xs md:text-sm font-medium text-blue-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">15.8%</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+2.1%
</div>
</div>
<div className="bg-blue-100 p-2 rounded-full">
<BarChart3 className="h-5 w-5 text-blue-600" />
<div className="bg-blue-100 p-1.5 md:p-2 rounded-full">
<BarChart3 className="h-3 w-3 md:h-5 md:w-5 text-blue-600" />
</div>
</div>
<div className="mt-3">
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>11.2%</span>
</div>
<Progress value={141} className="h-1.5 bg-blue-100" />
<Progress value={141} className="h-1 md:h-1.5 bg-blue-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-amber-50 to-yellow-50 border-none shadow-sm">
<CardContent className="p-4">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-sm font-medium text-amber-600"></div>
<div className="text-2xl font-bold mt-2">12.5%</div>
<div className="text-sm text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-4 w-4 mr-1" />
1.8%
<div className="text-xs md:text-sm font-medium text-amber-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">12.5%</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+1.8%
</div>
</div>
<div className="bg-amber-100 p-2 rounded-full">
<Users className="h-5 w-5 text-amber-600" />
<div className="bg-amber-100 p-1.5 md:p-2 rounded-full">
<Users className="h-3 w-3 md:h-5 md:w-5 text-amber-600" />
</div>
</div>
<div className="mt-3">
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>15%</span>
</div>
<Progress value={83} className="h-1.5 bg-amber-100" />
<Progress value={83} className="h-1 md:h-1.5 bg-amber-100" />
</div>
</CardContent>
</Card>
@@ -352,13 +364,13 @@ export default function UserValuePage() {
>
<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">
<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">
<BarChart3 className="h-5 w-5 text-blue-600" />
<BarChart3 className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
<div className="text-left">
<CardTitle className="flex items-center gap-2">
RFM用户价值模型
<CardTitle className="flex items-center gap-2 text-base md:text-lg">
RFM值模型
<Button
variant="ghost"
size="sm"
@@ -366,44 +378,48 @@ export default function UserValuePage() {
e.stopPropagation()
setShowModelDialog(true)
}}
className="h-6 w-6 p-0"
className="h-5 w-5 md:h-6 md:w-6 p-0"
>
<HelpCircle className="h-4 w-4" />
<HelpCircle className="h-3 w-3 md:h-4 md:w-4" />
</Button>
</CardTitle>
<CardDescription></CardDescription>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.model ? <ChevronDown className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
{expandedSections.model ? (
<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-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-6">
<div className="space-y-2">
<h3 className="font-semibold flex items-center">
<span className="bg-red-100 text-red-800 w-6 h-6 rounded-full flex items-center justify-center mr-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-red-100 text-red-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
R
</span>
Recency ()
</h3>
<p className="text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-3 rounded-md">
<div className="text-sm">
<div className="flex justify-between mb-1">
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>1-3</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>4-7</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>8-14</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>15-30</span>
<span className="font-medium">2</span>
</div>
@@ -416,28 +432,28 @@ export default function UserValuePage() {
</div>
<div className="space-y-2">
<h3 className="font-semibold flex items-center">
<span className="bg-green-100 text-green-800 w-6 h-6 rounded-full flex items-center justify-center mr-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-green-100 text-green-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
F
</span>
Frequency ()
</h3>
<p className="text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-3 rounded-md">
<div className="text-sm">
<div className="flex justify-between mb-1">
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>25/</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>20-24/</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>15-19/</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>10-14/</span>
<span className="font-medium">2</span>
</div>
@@ -450,28 +466,28 @@ export default function UserValuePage() {
</div>
<div className="space-y-2">
<h3 className="font-semibold flex items-center">
<span className="bg-blue-100 text-blue-800 w-6 h-6 rounded-full flex items-center justify-center mr-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-blue-100 text-blue-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
M
</span>
Monetary ()
</h3>
<p className="text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-3 rounded-md">
<div className="text-sm">
<div className="flex justify-between mb-1">
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>30000</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>20000-29999</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>10000-19999</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between mb-1">
<div className="flex justify-between">
<span>5000-9999</span>
<span className="font-medium">2</span>
</div>
@@ -484,28 +500,28 @@ export default function UserValuePage() {
</div>
</div>
<div className="mt-6">
<h3 className="font-semibold mb-3">RFM评分等级</h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
<div className="bg-green-100 p-3 rounded-md text-center">
<p className="font-medium text-green-800"></p>
<p className="text-sm text-green-700">85-100</p>
<div className="mt-4 md:mt-6">
<h3 className="font-semibold mb-3 text-sm md:text-base">RFM评分等级</h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 md:gap-3">
<div className="bg-green-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-green-800 text-xs md:text-sm"></p>
<p className="text-xs text-green-700">85-100</p>
</div>
<div className="bg-blue-100 p-3 rounded-md text-center">
<p className="font-medium text-blue-800"></p>
<p className="text-sm text-blue-700">70-84</p>
<div className="bg-blue-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-blue-800 text-xs md:text-sm"></p>
<p className="text-xs text-blue-700">70-84</p>
</div>
<div className="bg-yellow-100 p-3 rounded-md text-center">
<p className="font-medium text-yellow-800"></p>
<p className="text-sm text-yellow-700">55-69</p>
<div className="bg-yellow-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-yellow-800 text-xs md:text-sm"></p>
<p className="text-xs text-yellow-700">55-69</p>
</div>
<div className="bg-gray-100 p-3 rounded-md text-center">
<p className="font-medium text-gray-800"></p>
<p className="text-sm text-gray-700">40-54</p>
<div className="bg-gray-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-gray-800 text-xs md:text-sm"></p>
<p className="text-xs text-gray-700">40-54</p>
</div>
<div className="bg-red-100 p-3 rounded-md text-center">
<p className="font-medium text-red-800"></p>
<p className="text-sm text-red-700">0-39</p>
<div className="bg-red-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-red-800 text-xs md:text-sm"></p>
<p className="text-xs text-red-700">0-39</p>
</div>
</div>
</div>
@@ -521,35 +537,75 @@ export default function UserValuePage() {
>
<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">
<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">
<Users className="h-5 w-5 text-purple-600" />
<Users className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
<div className="text-left">
<CardTitle></CardTitle>
<CardDescription> ({filteredUsers.length})</CardDescription>
<CardTitle className="text-base md:text-lg"></CardTitle>
<CardDescription className="text-xs md:text-sm">
({filteredUsers.length})
</CardDescription>
</div>
</div>
{expandedSections.analysis ? <ChevronDown className="h-5 w-5" /> : <ChevronRight className="h-5 w-5" />}
{expandedSections.analysis ? (
<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="mb-4">
<div className="relative w-64">
<div className="relative w-full md:w-64">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="搜索用户或公司..."
className="pl-8"
className="pl-8 text-sm"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<div className="rounded-md border">
{/* 移动端卡片布局 */}
<div className="space-y-3 md:hidden">
{filteredUsers.map((user) => (
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-2">
<div className="flex items-center justify-between">
<div>
<p className="font-medium text-sm">{user.name}</p>
<p className="text-xs text-gray-500">{user.company}</p>
</div>
<Badge className={`${getRFMLevelColor(user.level)} text-xs`}>{user.rfmScore}</Badge>
</div>
<div className="grid grid-cols-3 gap-2 text-xs">
<div className="text-center">
<p className="text-gray-500">R值</p>
<p className="font-medium">{user.recency}</p>
</div>
<div className="text-center">
<p className="text-gray-500">F值</p>
<p className="font-medium">{user.frequency}</p>
</div>
<div className="text-center">
<p className="text-gray-500">M值</p>
<p className="font-medium">¥{(user.monetary / 1000).toFixed(0)}K</p>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500">{user.lastActive}</span>
<span className="text-xs text-green-600">{user.growth}</span>
</div>
</div>
))}
</div>
{/* 桌面端表格布局 */}
<div className="hidden md:block rounded-md border">
<Table>
<TableHeader>
<TableRow>
@@ -593,13 +649,13 @@ export default function UserValuePage() {
>
<Card className="border-none shadow-md">
<CollapsibleTrigger className="w-full">
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer">
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer py-3 md:py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<TrendingUp className="h-5 w-5 text-orange-600" />
<TrendingUp className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
<div className="text-left">
<CardTitle className="flex items-center gap-2">
<CardTitle className="flex items-center gap-2 text-base md:text-lg">
<Button
variant="ghost"
size="sm"
@@ -607,45 +663,45 @@ export default function UserValuePage() {
e.stopPropagation()
setShowUpgradeDialog(true)
}}
className="h-6 w-6 p-0"
className="h-5 w-5 md:h-6 md:w-6 p-0"
>
<HelpCircle className="h-4 w-4" />
<HelpCircle className="h-3 w-3 md:h-4 md:w-4" />
</Button>
</CardTitle>
<CardDescription></CardDescription>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.upgradePaths ? (
<ChevronDown className="h-5 w-5" />
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
) : (
<ChevronRight className="h-5 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-4 mb-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-lg flex items-center">
<Target className="h-5 w-5 mr-2 text-green-600" />
<CardTitle className="text-sm md:text-lg flex items-center">
<Target className="h-4 w-4 md:h-5 md:w-5 mr-2 text-green-600" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">2,345</span>
<span></span>
<span className="font-medium">2,345</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">5,678</span>
<span></span>
<span className="font-medium">5,678</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">1,234</span>
<span></span>
<span className="font-medium">1,234</span>
</div>
</div>
</CardContent>
@@ -653,24 +709,24 @@ export default function UserValuePage() {
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-lg flex items-center">
<Layers className="h-5 w-5 mr-2 text-blue-600" />
<CardTitle className="text-sm md:text-lg flex items-center">
<Layers className="h-4 w-4 md:h-5 md:w-5 mr-2 text-blue-600" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">12.5%</span>
<span></span>
<span className="font-medium">12.5%</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">28</span>
<span></span>
<span className="font-medium">28</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">1,856</span>
<span></span>
<span className="font-medium">1,856</span>
</div>
</div>
</CardContent>
@@ -678,54 +734,54 @@ export default function UserValuePage() {
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-lg flex items-center">
<Tag className="h-5 w-5 mr-2 text-purple-600" />
<CardTitle className="text-sm md:text-lg flex items-center">
<Tag className="h-4 w-4 md:h-5 md:w-5 mr-2 text-purple-600" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">¥2.3M</span>
<span></span>
<span className="font-medium">¥2.3M</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">¥1,245</span>
<span></span>
<span className="font-medium">¥1,245</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm">ROI提升</span>
<span className="text-sm font-medium">35.8%</span>
<span>ROI提升</span>
<span className="font-medium">35.8%</span>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="space-y-4">
<h3 className="text-lg font-medium"></h3>
<div className="space-y-3">
<div className="space-y-3 md:space-y-4">
<h3 className="text-sm md:text-lg font-medium"></h3>
<div className="space-y-2 md:space-y-3">
{filteredUsers.slice(0, 5).map((user) => (
<div
key={user.id}
className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50"
className="flex flex-col md:flex-row md:items-center justify-between p-3 md:p-4 border rounded-lg hover:bg-gray-50 space-y-2 md:space-y-0"
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-3 md:gap-4">
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-gray-500">{user.company}</p>
<p className="font-medium text-sm md:text-base">{user.name}</p>
<p className="text-xs md:text-sm text-gray-500">{user.company}</p>
</div>
<Badge className={getRFMLevelColor(user.level)}>{user.level}</Badge>
<Badge className={`${getRFMLevelColor(user.level)} text-xs`}>{user.level}</Badge>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="text-sm font-medium">: {user.nextUpgrade}</p>
<p className="text-sm text-gray-500">: {user.upgradeProbability}%</p>
<div className="flex flex-col md:flex-row items-start md:items-center gap-2 md:gap-4">
<div className="text-left md:text-right">
<p className="text-xs md:text-sm font-medium">: {user.nextUpgrade}</p>
<p className="text-xs text-gray-500">: {user.upgradeProbability}%</p>
</div>
<div className="w-24">
<Progress value={user.upgradeProbability} className="h-2" />
<div className="w-full md:w-24">
<Progress value={user.upgradeProbability} className="h-1.5 md:h-2" />
</div>
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" className="text-xs bg-transparent">
</Button>
</div>