refactor: overhaul UI for streamlined user experience

Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

786
app/ai-assistant/page.tsx Normal file
View File

@@ -0,0 +1,786 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Textarea } from "@/components/ui/textarea"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Badge } from "@/components/ui/badge"
import { Separator } from "@/components/ui/separator"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Sparkles, BarChart, TrendingUp, Users, Mail, Clock, Download, ChevronRight } from "lucide-react"
export default function AIAssistantPage() {
const [activeTab, setActiveTab] = useState("data-analysis")
const [selectedUserGroup, setSelectedUserGroup] = useState("")
const [isUserGroupDialogOpen, setIsUserGroupDialogOpen] = useState(false)
const [emailAddress, setEmailAddress] = useState("")
const [scheduleReport, setScheduleReport] = useState(false)
// 分析流程步骤
const [analysisStep, setAnalysisStep] = useState(1)
const [analysisType, setAnalysisType] = useState("")
// 营销策略步骤
const [strategyStep, setStrategyStep] = useState(1)
const [strategyType, setStrategyType] = useState("")
return (
<div className="container mx-auto py-6 space-y-8">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight">AI </h1>
<p className="text-muted-foreground">AI技术分析数据并提供营销策略</p>
</div>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full max-w-md grid-cols-2">
<TabsTrigger value="data-analysis"></TabsTrigger>
<TabsTrigger value="marketing-strategy"></TabsTrigger>
</TabsList>
<TabsContent value="data-analysis" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>AI </CardTitle>
<CardDescription>使AI分析用户数据并生成洞察报告</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* 第1步选择用户分群 */}
<div className={`space-y-4 ${analysisStep !== 1 ? "opacity-60" : ""}`}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
1
</div>
</h3>
{analysisStep > 1 ? (
<Button variant="outline" size="sm" onClick={() => setAnalysisStep(1)}>
</Button>
) : (
<Button variant="outline" onClick={() => setIsUserGroupDialogOpen(true)}>
<Users className="mr-2 h-4 w-4" />
</Button>
)}
</div>
{selectedUserGroup && (
<div className="p-4 border rounded-lg bg-muted/50">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-muted-foreground" />
<span className="font-medium"></span>
<Badge className="bg-blue-100 text-blue-800">{selectedUserGroup}</Badge>
</div>
{analysisStep === 1 && (
<Button
onClick={() => {
setAnalysisStep(2)
}}
disabled={!selectedUserGroup}
>
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
)}
</div>
</div>
)}
</div>
<Separator />
{/* 第2步选择分析类型 */}
<div
className={`space-y-4 ${analysisStep !== 2 ? (analysisStep > 2 ? "opacity-60" : "opacity-40") : ""}`}
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
2
</div>
</h3>
{analysisStep > 2 && (
<Button variant="outline" size="sm" onClick={() => setAnalysisStep(2)}>
</Button>
)}
</div>
{analysisStep >= 2 && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${analysisType === "behavior" ? "border-primary" : "border-transparent"}`}
onClick={() => setAnalysisType("behavior")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
</CardContent>
<CardFooter>
{analysisType === "behavior" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${analysisType === "value" ? "border-primary" : "border-transparent"}`}
onClick={() => setAnalysisType("value")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground"></CardContent>
<CardFooter>
{analysisType === "value" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${analysisType === "churn" ? "border-primary" : "border-transparent"}`}
onClick={() => setAnalysisType("churn")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
</CardContent>
<CardFooter>
{analysisType === "churn" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
</div>
)}
{analysisStep === 2 && analysisType && (
<div className="flex justify-end">
<Button onClick={() => setAnalysisStep(3)}>
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
</div>
)}
</div>
<Separator />
{/* 第3步分析参数设置 */}
<div
className={`space-y-4 ${analysisStep !== 3 ? (analysisStep > 3 ? "opacity-60" : "opacity-40") : ""}`}
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
3
</div>
</h3>
{analysisStep > 3 && (
<Button variant="outline" size="sm" onClick={() => setAnalysisStep(3)}>
</Button>
)}
</div>
{analysisStep >= 3 && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="time-range"></Label>
<Select defaultValue="30days">
<SelectTrigger id="time-range">
<SelectValue placeholder="选择时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">7</SelectItem>
<SelectItem value="30days">30</SelectItem>
<SelectItem value="90days">90</SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="analysis-depth"></Label>
<Select defaultValue="medium">
<SelectTrigger id="analysis-depth">
<SelectValue placeholder="选择分析深度" />
</SelectTrigger>
<SelectContent>
<SelectItem value="basic"></SelectItem>
<SelectItem value="medium"></SelectItem>
<SelectItem value="deep"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
{analysisStep === 3 && (
<div className="flex justify-end">
<Button onClick={() => setAnalysisStep(4)}>
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
</div>
)}
</div>
<Separator />
{/* 第4步报告设置 */}
<div className={`space-y-4 ${analysisStep !== 4 ? "opacity-40" : ""}`}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
4
</div>
</h3>
</div>
{analysisStep >= 4 && (
<div className="space-y-4">
<div className="flex flex-col space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
type="email"
placeholder="输入邮箱地址"
value={emailAddress}
onChange={(e) => setEmailAddress(e.target.value)}
/>
</div>
<div className="flex items-center space-x-2">
<Switch id="schedule-report" checked={scheduleReport} onCheckedChange={setScheduleReport} />
<Label htmlFor="schedule-report"></Label>
</div>
{scheduleReport && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6">
<div className="space-y-2">
<Label htmlFor="schedule-frequency"></Label>
<Select defaultValue="weekly">
<SelectTrigger id="schedule-frequency">
<SelectValue placeholder="选择发送频率" />
</SelectTrigger>
<SelectContent>
<SelectItem value="daily"></SelectItem>
<SelectItem value="weekly"></SelectItem>
<SelectItem value="monthly"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="schedule-time"></Label>
<Select defaultValue="morning">
<SelectTrigger id="schedule-time">
<SelectValue placeholder="选择发送时间" />
</SelectTrigger>
<SelectContent>
<SelectItem value="morning"> 9:00</SelectItem>
<SelectItem value="noon"> 12:00</SelectItem>
<SelectItem value="evening"> 6:00</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</div>
)}
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button
variant="outline"
onClick={() => {
setAnalysisStep(1)
setAnalysisType("")
}}
>
</Button>
<Button disabled={analysisStep < 4 || !emailAddress}>
<Sparkles className="mr-2 h-4 w-4" />
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>AI生成的数据分析结果</CardDescription>
</CardHeader>
<CardContent className="h-96 flex items-center justify-center">
{selectedUserGroup && analysisStep === 4 ? (
<div className="text-center space-y-4">
<BarChart className="h-16 w-16 mx-auto text-primary" />
<p className="text-lg font-medium"></p>
<p className="text-muted-foreground">"开始分析"</p>
<div className="flex justify-center gap-2 pt-4">
<Button variant="outline">
<Download className="mr-2 h-4 w-4" />
</Button>
<Button>
<Mail className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
) : (
<div className="text-center text-muted-foreground">
<Users className="h-16 w-16 mx-auto mb-4" />
<p className="text-lg"></p>
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
<Users className="mr-2 h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="marketing-strategy" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>AI </CardTitle>
<CardDescription>使AI生成针对性的营销策略和建议</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* 第1步选择目标用户分群 */}
<div className={`space-y-4 ${strategyStep !== 1 ? "opacity-60" : ""}`}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
1
</div>
</h3>
{strategyStep > 1 ? (
<Button variant="outline" size="sm" onClick={() => setStrategyStep(1)}>
</Button>
) : (
<Button variant="outline" onClick={() => setIsUserGroupDialogOpen(true)}>
<Users className="mr-2 h-4 w-4" />
</Button>
)}
</div>
{selectedUserGroup && (
<div className="p-4 border rounded-lg bg-muted/50">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-muted-foreground" />
<span className="font-medium"></span>
<Badge className="bg-blue-100 text-blue-800">{selectedUserGroup}</Badge>
</div>
{strategyStep === 1 && (
<Button
onClick={() => {
setStrategyStep(2)
}}
disabled={!selectedUserGroup}
>
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
)}
</div>
</div>
)}
</div>
<Separator />
{/* 第2步选择策略类型 */}
<div
className={`space-y-4 ${strategyStep !== 2 ? (strategyStep > 2 ? "opacity-60" : "opacity-40") : ""}`}
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
2
</div>
</h3>
{strategyStep > 2 && (
<Button variant="outline" size="sm" onClick={() => setStrategyStep(2)}>
</Button>
)}
</div>
{strategyStep >= 2 && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${strategyType === "sales" ? "border-primary" : "border-transparent"}`}
onClick={() => setStrategyType("sales")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
</CardContent>
<CardFooter>
{strategyType === "sales" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${strategyType === "reach" ? "border-primary" : "border-transparent"}`}
onClick={() => setStrategyType("reach")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
</CardContent>
<CardFooter>
{strategyType === "reach" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
<Card
className={`border-2 cursor-pointer hover:shadow-md transition-all ${strategyType === "content" ? "border-primary" : "border-transparent"}`}
onClick={() => setStrategyType("content")}
>
<CardHeader className="pb-2">
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
</CardContent>
<CardFooter>
{strategyType === "content" && <Badge className="bg-primary/10 text-primary"></Badge>}
</CardFooter>
</Card>
</div>
)}
{strategyStep === 2 && strategyType && (
<div className="flex justify-end">
<Button onClick={() => setStrategyStep(3)}>
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
</div>
)}
</div>
<Separator />
{/* 第3步策略参数设置 */}
<div className={`space-y-4 ${strategyStep !== 3 ? "opacity-40" : ""}`}>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium flex items-center">
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-white text-sm mr-2">
3
</div>
</h3>
</div>
{strategyStep >= 3 && (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="strategy-goal"></Label>
<Select defaultValue="conversion">
<SelectTrigger id="strategy-goal">
<SelectValue placeholder="选择营销目标" />
</SelectTrigger>
<SelectContent>
<SelectItem value="awareness"></SelectItem>
<SelectItem value="engagement"></SelectItem>
<SelectItem value="conversion"></SelectItem>
<SelectItem value="retention"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="strategy-budget"></Label>
<Select defaultValue="medium">
<SelectTrigger id="strategy-budget">
<SelectValue placeholder="选择预算范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="low"></SelectItem>
<SelectItem value="medium"></SelectItem>
<SelectItem value="high"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="strategy-notes"></Label>
<Textarea
id="strategy-notes"
placeholder="请输入任何补充说明或特殊要求..."
className="min-h-[100px]"
/>
</div>
<div className="space-y-4">
<div className="flex flex-col space-y-2">
<Label htmlFor="email-strategy"></Label>
<Input
id="email-strategy"
type="email"
placeholder="输入邮箱地址"
value={emailAddress}
onChange={(e) => setEmailAddress(e.target.value)}
/>
</div>
<div className="flex items-center space-x-2">
<Switch id="schedule-strategy" checked={scheduleReport} onCheckedChange={setScheduleReport} />
<Label htmlFor="schedule-strategy"></Label>
</div>
{scheduleReport && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6">
<div className="space-y-2">
<Label htmlFor="update-frequency"></Label>
<Select defaultValue="monthly">
<SelectTrigger id="update-frequency">
<SelectValue placeholder="选择更新频率" />
</SelectTrigger>
<SelectContent>
<SelectItem value="weekly"></SelectItem>
<SelectItem value="biweekly"></SelectItem>
<SelectItem value="monthly"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</div>
</>
)}
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button
variant="outline"
onClick={() => {
setStrategyStep(1)
setStrategyType("")
}}
>
</Button>
<Button disabled={strategyStep < 3 || !emailAddress}>
<Sparkles className="mr-2 h-4 w-4" />
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>AI生成的营销策略建议</CardDescription>
</CardHeader>
<CardContent className="h-96 flex items-center justify-center">
{selectedUserGroup && strategyStep === 3 ? (
<div className="text-center space-y-4">
<TrendingUp className="h-16 w-16 mx-auto text-primary" />
<p className="text-lg font-medium"></p>
<p className="text-muted-foreground">"生成策略"</p>
<div className="flex justify-center gap-2 pt-4">
<Button variant="outline">
<Download className="mr-2 h-4 w-4" />
</Button>
<Button>
<Mail className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
) : (
<div className="text-center text-muted-foreground">
<Users className="h-16 w-16 mx-auto mb-4" />
<p className="text-lg"></p>
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
<Users className="mr-2 h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
<UserGroupSelectionDialog
isOpen={isUserGroupDialogOpen}
setIsOpen={setIsUserGroupDialogOpen}
onSelect={setSelectedUserGroup}
/>
</div>
)
}
// 用户分群选择对话框
function UserGroupSelectionDialog({
isOpen,
setIsOpen,
onSelect,
}: {
isOpen: boolean
setIsOpen: (open: boolean) => void
onSelect: (group: string) => void
}) {
// 模拟用户分群数据
const userGroups = [
{
id: "1",
name: "高价值用户",
description: "消费能力强,购买频率高的用户",
count: 12456,
category: "用户资产",
lastUpdated: "2023-07-20",
},
{
id: "2",
name: "活跃用户",
description: "近30天内有活动的用户",
count: 45678,
category: "用户资产",
lastUpdated: "2023-07-20",
},
{
id: "3",
name: "地摊商户",
description: "从事地摊相关业务的用户",
count: 8765,
category: "地摊相关",
lastUpdated: "2023-07-19",
},
{
id: "4",
name: "樊登读书会员",
description: "樊登读书平台的会员用户",
count: 15678,
category: "樊登读书",
lastUpdated: "2023-07-18",
},
{
id: "5",
name: "魔兽玩家",
description: "魔兽世界游戏的活跃玩家",
count: 23456,
category: "魔兽世界",
lastUpdated: "2023-07-17",
},
{
id: "6",
name: "流失风险用户",
description: "有流失风险的高价值用户",
count: 5678,
category: "用户资产",
lastUpdated: "2023-07-16",
},
]
const [searchQuery, setSearchQuery] = useState("")
const [selectedCategory, setSelectedCategory] = useState("all")
// 过滤用户分群
const filteredGroups = userGroups.filter((group) => {
const matchesSearch =
group.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
group.description.toLowerCase().includes(searchQuery.toLowerCase())
const matchesCategory = selectedCategory === "all" || group.category === selectedCategory
return matchesSearch && matchesCategory
})
// 获取所有分类
const categories = ["all", ...new Set(userGroups.map((group) => group.category))]
const handleSelect = (group: string) => {
onSelect(group)
setIsOpen(false)
}
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="py-4 space-y-4">
<div className="flex flex-col md:flex-row gap-4">
<div className="relative flex-1">
<Input
placeholder="搜索用户分群..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<SelectTrigger className="w-full md:w-[180px]">
<SelectValue placeholder="选择场景分类" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category} value={category}>
{category === "all" ? "所有场景" : category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="max-h-[300px] overflow-y-auto space-y-2">
{filteredGroups.map((group) => (
<div
key={group.id}
className="p-3 border rounded-lg hover:bg-muted/50 cursor-pointer"
onClick={() => handleSelect(group.name)}
>
<div className="flex justify-between items-start">
<div>
<div className="font-medium">{group.name}</div>
<div className="text-sm text-muted-foreground">{group.description}</div>
</div>
<Badge className="bg-blue-100 text-blue-800">{group.category}</Badge>
</div>
<div className="flex justify-between items-center mt-2 text-sm text-muted-foreground">
<div className="flex items-center">
<Users className="h-3 w-3 mr-1" />
{group.count.toLocaleString()}
</div>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
{group.lastUpdated}
</div>
</div>
</div>
))}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsOpen(false)}>
</Button>
<Button onClick={() => setIsOpen(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}