feat: sync Sidebar and BottomNav, standardize user profile API

Align Sidebar & BottomNav menus, remove "Search", add user profile mock data, implement /api/users, add FilterDrawer, complete Section, ProfileHeader, MetricsRFM components

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-08-08 07:00:12 +00:00
parent 4eed69520c
commit f0a6a364f2
85 changed files with 3318 additions and 6786 deletions

View File

@@ -1,516 +1,189 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { useCallback, useMemo, useState } from "react"
import { BarChart3, Database, FileText } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import {
BarChart3,
Send,
FileText,
Mail,
MessageSquare,
Download,
Database,
Target,
Zap,
Eye,
Share,
} from "lucide-react"
import { TooltipHelp, TooltipProvider } from "@/components/ui/tooltip-help"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"
import TaskList from "@/components/ai-assistant/task-list"
import CreateTaskDialog from "@/components/ai-assistant/create-task-dialog"
import ReportCards from "@/components/ai-assistant/report-cards"
import { sanitizeText } from "@/lib/text-sanitize"
import type { AnalysisTask, DatabaseInfo, ReportItem, ReportTemplate } from "@/types/ai-assistant"
const initialTasks: AnalysisTask[] = [
{
id: "task_001",
name: "用户行为分析报告",
database: "微信用户数据库",
status: "completed",
progress: 100,
createdAt: "2025-01-15T09:00:00Z",
completedAt: "2025-01-15T09:30:00Z",
reportUrl: "/reports/user-behavior-analysis.pdf",
description: "基于近30天行为的聚合与序列分析",
},
{
id: "task_002",
name: "流量关键词趋势分析",
database: "流量关键词库",
status: "running",
progress: 65,
createdAt: "2025-01-15T10:00:00Z",
description: "关注核心流量词与曝光、点击、转化等指标",
},
{
id: "task_003",
name: "用户价值分层报告",
database: "微信用户数据库",
status: "pending",
progress: 0,
createdAt: "2025-01-15T10:30:00Z",
description: "RFM 分层、价值区间分布与运营建议",
},
]
const databases: DatabaseInfo[] = [
{ id: "db_001", name: "微信用户数据库", type: "MySQL", tables: 25, records: 4000000000, lastUpdated: "2025-01-15T11:00:00Z" },
{ id: "db_002", name: "流量关键词库", type: "PostgreSQL", tables: 8, records: 150000, lastUpdated: "2025-01-15T10:45:00Z" },
{ id: "db_003", name: "用户行为日志", type: "MongoDB", tables: 12, records: 1500000000, lastUpdated: "2025-01-15T11:15:00Z" },
]
const templates: ReportTemplate[] = [
{ id: "template_001", name: "用户画像分析报告", description: "深度分析用户特征、行为模式和价值分层", category: "用户分析", fields: ["用户基本信息", "RFM分析", "行为轨迹", "价值评估", "推荐策略"] },
{ id: "template_002", name: "流量趋势分析报告", description: "分析关键词搜索趋势和流量变化", category: "流量分析", fields: ["关键词热度", "搜索趋势", "竞争分析", "机会识别", "优化建议"] },
{ id: "template_003", name: "业务运营报告", description: "综合业务数据分析和运营建议", category: "运营分析", fields: ["核心指标", "增长分析", "用户留存", "转化漏斗", "运营建议"] },
{ id: "template_004", name: "数据质量报告", description: "评估数据完整性、准确性和一致性", category: "数据质量", fields: ["数据完整性", "准确性检查", "一致性验证", "异常检测", "改进建议"] },
]
const reportItems: ReportItem[] = [
{ id: "rpt-1", title: "用户行为分析报告", source: "数据源:微信用户数据库", description: '本报告基于近30天互动行为输出用户行为模式与高频路径。\\n"]]}', updatedAt: new Date().toISOString() },
{ id: "rpt-2", title: "流量关键词趋势分析", source: "数据源:流量关键词库", description: '追踪 000000 类流量词及其曝光、点击、转化趋势,适配日/周/月视角。\\n" ]]}', updatedAt: new Date().toISOString() },
{ id: "rpt-3", title: "用户价值分层报告", source: "数据源:微信用户数据库", description: '结合 RFM 得分与标签,给出 S/A/B/C/D 分层与经营建议。\\n" ]] }', updatedAt: new Date().toISOString() },
]
export default function AIAssistantPage() {
const [selectedDatabase, setSelectedDatabase] = useState("all")
const [reportTitle, setReportTitle] = useState("")
const [isGeneratingReport, setIsGeneratingReport] = useState(false)
const [isSendingReport, setIsSendingReport] = useState(false)
const [selectedReport, setSelectedReport] = useState<string | null>(null)
const [tasks, setTasks] = useState<AnalysisTask[]>(initialTasks)
// 数据库列表
const databases = [
{ id: "user_behavior", name: "用户行为数据库", records: "1.2M", description: "用户行为轨迹和交互数据" },
{ id: "user_profile", name: "用户画像数据库", records: "856K", description: "用户基本信息和标签数据" },
{ id: "transaction", name: "交易数据库", records: "324K", description: "用户交易和消费记录" },
{ id: "content", name: "内容数据库", records: "567K", description: "内容互动和偏好数据" },
{ id: "traffic", name: "流量数据库", records: "2.3M", description: "流量关键词和来源数据" },
]
// 进度推进(仅演示用)
const tick = useCallback(() => {
setTasks((prev) =>
prev.map((t) => {
if (t.status === "running" && t.progress < 100) {
const p = Math.min(100, t.progress + Math.random() * 12)
if (p >= 100) {
return {
...t,
progress: 100,
status: "completed",
completedAt: new Date().toISOString(),
reportUrl: `/reports/${t.id}.pdf`,
}
}
return { ...t, progress: p }
}
return t
}),
)
}, [])
// 生成的报告列表
const [reports, setReports] = useState([
{
id: "1",
title: "用户行为分析报告",
database: "用户行为数据库",
generatedAt: "2024-01-15 14:30",
status: "completed",
insights: [
"用户在晚间时段活跃度最高占总活跃时间的35%",
"移动端用户占比87%其中iOS用户转化率更高",
"用户平均会话时长为8.5分钟,高于行业平均水平",
],
recommendations: [
"建议在晚间时段增加营销活动投入",
"优化iOS端用户体验提升转化率",
"延长用户会话时长,增加内容推荐精准度",
],
},
{
id: "2",
title: "用户价值分析报告",
database: "用户画像数据库",
generatedAt: "2024-01-14 16:45",
status: "completed",
insights: [
"高价值用户占比7.2%贡献了45%的总收入",
"用户生命周期价值平均为¥2,580",
"新用户转化为高价值用户的概率为12.5%",
],
recommendations: [
"针对高价值用户制定专属服务策略",
"优化新用户引导流程,提升转化率",
"建立用户价值预警机制,及时挽留流失用户",
],
},
{
id: "3",
title: "流量关键词分析报告",
database: "流量数据库",
generatedAt: "2024-01-13 10:20",
status: "completed",
insights: [
"热门关键词TOP10贡献了60%的流量",
"长尾关键词转化率普遍高于热门关键词",
"品牌相关关键词的用户质量最高",
],
recommendations: ["加大长尾关键词的投入和优化", "提升品牌关键词的覆盖范围", "建立关键词效果监控体系"],
},
])
// 生成报告
const handleGenerateReport = () => {
if (!reportTitle.trim()) return
setIsGeneratingReport(true)
// 模拟报告生成过程
const onCreate = (task: AnalysisTask) => {
setTasks((prev) => [task, ...prev])
// 模拟启动
setTimeout(() => {
const newReport = {
id: Date.now().toString(),
title: reportTitle,
database: databases.find((db) => db.id === selectedDatabase)?.name || "全部数据库",
generatedAt: new Date().toLocaleString(),
status: "completed",
insights: ["AI分析发现的关键洞察1", "AI分析发现的关键洞察2", "AI分析发现的关键洞察3"],
recommendations: ["基于数据分析的建议1", "基于数据分析的建议2", "基于数据分析的建议3"],
}
setReports((prev) => [newReport, ...prev])
setReportTitle("")
setIsGeneratingReport(false)
}, 3000)
setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, status: "running" } : t)))
}, 800)
}
// 发送报告
const handleSendReport = (reportId: string, method: string, target: string) => {
setIsSendingReport(true)
// 模拟发送过程
setTimeout(() => {
setIsSendingReport(false)
setSelectedReport(null)
// 这里可以添加成功提示
}, 2000)
}
const sanitizedItems = useMemo(
() => reportItems.map((i) => ({ ...i, description: sanitizeText(i.description) })),
[],
)
return (
<TooltipProvider>
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">AI智能助手</h1>
<p className="text-muted-foreground mt-2"></p>
</div>
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-indigo-50">
<div className="container mx-auto px-4 py-8">
<div className="mb-6">
<h1 className="text-3xl font-bold">AI智能助手</h1>
<p className="text-gray-600"></p>
</div>
{/* 核心指标概览 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<TooltipHelp content="可分析的数据库总数,包含用户、交易、行为等各类数据" />
</CardTitle>
<Database className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{databases.length}</div>
<p className="text-xs text-muted-foreground"></p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<TooltipHelp content="AI自动生成的数据分析报告总数" />
</CardTitle>
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{reports.length}</div>
<p className="text-xs text-muted-foreground">
<span className="text-green-600">+2</span>
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<TooltipHelp content="AI数据分析的准确率基于历史验证结果计算" />
</CardTitle>
<Target className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">94.2%</div>
<p className="text-xs text-muted-foreground">
<span className="text-green-600">+1.8%</span>
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<TooltipHelp content="AI分析处理的平均速度包括数据读取和分析时间" />
</CardTitle>
<Zap className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">2.3s</div>
<p className="text-xs text-muted-foreground"></p>
</CardContent>
</Card>
</div>
<Tabs defaultValue="generate" className="space-y-6">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="generate"></TabsTrigger>
<TabsTrigger value="reports"></TabsTrigger>
<Tabs defaultValue="analysis" className="space-y-6">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="analysis" className="flex items-center gap-2">
<BarChart3 className="w-4 h-4" />
</TabsTrigger>
<TabsTrigger value="databases" className="flex items-center gap-2">
<Database className="w-4 h-4" />
</TabsTrigger>
<TabsTrigger value="templates" className="flex items-center gap-2">
<FileText className="w-4 h-4" />
</TabsTrigger>
</TabsList>
<TabsContent value="generate" className="space-y-6">
<TabsContent value="analysis" className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold"></h2>
<CreateTaskDialog databases={databases} templates={templates} onCreate={onCreate} />
</div>
<TaskList tasks={tasks} onTick={tick} />
<ReportCards items={sanitizedItems} />
</TabsContent>
<TabsContent value="databases">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
<TooltipHelp content="选择数据库并配置分析参数AI将自动生成详细的数据分析报告" />
<Database className="w-4 h-4" />
</CardTitle>
<CardDescription>AI智能分析数据库</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<Label htmlFor="report-title"></Label>
<Input
id="report-title"
placeholder="请输入报告标题..."
value={reportTitle}
onChange={(e) => setReportTitle(e.target.value)}
className="mt-1"
/>
<CardContent className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{databases.map((db) => (
<Card key={db.id} className="border">
<CardContent className="p-4 space-y-1">
<div className="font-medium">{db.name}</div>
<div className="text-sm text-gray-600">
{db.type} · {db.tables}
</div>
<div>
<Label htmlFor="database-select"></Label>
<Select value={selectedDatabase} onValueChange={setSelectedDatabase}>
<SelectTrigger id="database-select" className="mt-1">
<SelectValue placeholder="选择要分析的数据库" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{databases.map((db) => (
<SelectItem key={db.id} value={db.id}>
{db.name} ({db.records})
</SelectItem>
))}
</SelectContent>
</Select>
<div className="text-xs text-gray-500">
{new Date(db.lastUpdated).toLocaleString("zh-CN")}
</div>
<div>
<Label htmlFor="analysis-type"></Label>
<Select defaultValue="comprehensive">
<SelectTrigger id="analysis-type" className="mt-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="comprehensive"></SelectItem>
<SelectItem value="behavior"></SelectItem>
<SelectItem value="value"></SelectItem>
<SelectItem value="trend"></SelectItem>
<SelectItem value="conversion"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="time-range"></Label>
<Select defaultValue="30days">
<SelectTrigger id="time-range" className="mt-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">7</SelectItem>
<SelectItem value="30days">30</SelectItem>
<SelectItem value="90days">90</SelectItem>
<SelectItem value="1year">1</SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="analysis-depth"></Label>
<Select defaultValue="standard">
<SelectTrigger id="analysis-depth" className="mt-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="basic"></SelectItem>
<SelectItem value="standard"></SelectItem>
<SelectItem value="deep"></SelectItem>
<SelectItem value="expert"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="output-format"></Label>
<Select defaultValue="detailed">
<SelectTrigger id="output-format" className="mt-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="summary"></SelectItem>
<SelectItem value="detailed"></SelectItem>
<SelectItem value="executive"></SelectItem>
<SelectItem value="technical"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Switch id="include-insights" defaultChecked />
<Label htmlFor="include-insights"></Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="include-recommendations" defaultChecked />
<Label htmlFor="include-recommendations"></Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="include-charts" defaultChecked />
<Label htmlFor="include-charts"></Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="include-predictions" />
<Label htmlFor="include-predictions"></Label>
</div>
</div>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={handleGenerateReport}
disabled={isGeneratingReport || !reportTitle.trim()}
className="px-8"
>
{isGeneratingReport ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
...
</>
) : (
<>
<BarChart3 className="h-4 w-4 mr-2" />
AI分析报告
</>
)}
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="reports" className="space-y-6">
<TabsContent value="templates">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
<TooltipHelp content="管理所有生成的AI分析报告支持查看、下载和分享" />
<FileText className="w-4 h-4" />
</CardTitle>
<CardDescription>AI生成的数据分析报告</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reports.map((report) => (
<TableRow key={report.id}>
<TableCell className="font-medium">{report.title}</TableCell>
<TableCell>{report.database}</TableCell>
<TableCell>{report.generatedAt}</TableCell>
<TableCell>
<Badge className="bg-green-100 text-green-800"></Badge>
</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button variant="ghost" size="sm">
<Eye className="h-4 w-4 mr-1" />
</Button>
<Button variant="ghost" size="sm">
<Download className="h-4 w-4 mr-1" />
</Button>
<Button variant="ghost" size="sm" onClick={() => setSelectedReport(report.id)}>
<Share className="h-4 w-4 mr-1" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<CardContent className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{templates.map((t) => (
<Card key={t.id} className="border">
<CardContent className="p-4 space-y-2">
<div className="font-medium">{t.name}</div>
<div className="text-sm text-gray-600">{t.category}</div>
<div className="text-xs text-gray-500">: {t.fields.join(" / ")}</div>
</CardContent>
</Card>
))}
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 发送报告对话框 */}
<Dialog open={!!selectedReport} onOpenChange={(open) => !open && setSelectedReport(null)}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="space-y-4">
<div>
<Label></Label>
<div className="grid grid-cols-2 gap-4 mt-2">
<Card className="cursor-pointer hover:bg-gray-50 p-4">
<div className="flex items-center space-x-3">
<Mail className="h-6 w-6 text-blue-500" />
<div>
<div className="font-medium"></div>
<div className="text-sm text-muted-foreground"></div>
</div>
</div>
</Card>
<Card className="cursor-pointer hover:bg-gray-50 p-4">
<div className="flex items-center space-x-3">
<MessageSquare className="h-6 w-6 text-green-500" />
<div>
<div className="font-medium"></div>
<div className="text-sm text-muted-foreground"></div>
</div>
</div>
</Card>
</div>
</div>
<div>
<Label htmlFor="send-target"></Label>
<Input id="send-target" placeholder="请输入邮箱地址或微信号" className="mt-1" />
</div>
<div>
<Label htmlFor="send-message"></Label>
<Textarea id="send-message" placeholder="可选:添加附加消息" className="mt-1" rows={3} />
</div>
<div className="space-y-2">
<Label></Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Switch id="include-pdf" defaultChecked />
<Label htmlFor="include-pdf">PDF版本</Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="include-data" />
<Label htmlFor="include-data"></Label>
</div>
<div className="flex items-center space-x-2">
<Switch id="schedule-send" />
<Label htmlFor="schedule-send"></Label>
</div>
</div>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setSelectedReport(null)}>
</Button>
<Button
onClick={() => handleSendReport(selectedReport!, "email", "target@example.com")}
disabled={isSendingReport}
>
{isSendingReport ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
...
</>
) : (
<>
<Send className="mr-2 h-4 w-4" />
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</TooltipProvider>
</div>
)
}