chore: 以本地为准,上传全部并替换 GitHub
This commit is contained in:
@@ -1,234 +1,709 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import {
|
||||
FileText, Download, Share2, Clock, Eye, Sparkles,
|
||||
BarChart3, Users, TrendingUp, Calendar, Plus, Trash2
|
||||
import {
|
||||
FileText,
|
||||
Plus,
|
||||
Download,
|
||||
RefreshCw,
|
||||
Eye,
|
||||
Calendar,
|
||||
Users,
|
||||
TrendingUp,
|
||||
PieChart,
|
||||
BarChart3,
|
||||
Clock,
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Send,
|
||||
Mail,
|
||||
MessageSquare,
|
||||
Play,
|
||||
Sparkles,
|
||||
} from "lucide-react"
|
||||
|
||||
const reportTemplates = [
|
||||
{ id: "1", name: "用户增长分析报告", description: "分析用户增长趋势和渠道效果", icon: TrendingUp },
|
||||
{ id: "2", name: "用户画像洞察报告", description: "深度分析用户特征和行为偏好", icon: Users },
|
||||
{ id: "3", name: "数据质量评估报告", description: "评估数据完整性和准确性", icon: BarChart3 },
|
||||
{ id: "4", name: "营销效果分析报告", description: "评估营销活动ROI和转化效果", icon: TrendingUp },
|
||||
]
|
||||
// 报告规则接口
|
||||
interface ReportRule {
|
||||
id: string
|
||||
name: string
|
||||
templateId: string
|
||||
schedule: string
|
||||
enabled: boolean
|
||||
isDefault: boolean
|
||||
config: {
|
||||
dateRange: string
|
||||
includeCharts: boolean
|
||||
sendTo: string[]
|
||||
format: 'pdf' | 'html' | 'markdown'
|
||||
}
|
||||
promptTemplate: string
|
||||
}
|
||||
|
||||
const recentReports = [
|
||||
// 报告模板
|
||||
interface ReportTemplate {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
type: 'daily' | 'weekly' | 'monthly' | 'custom'
|
||||
sections: string[]
|
||||
icon: string
|
||||
}
|
||||
|
||||
// 默认报告规则
|
||||
const DEFAULT_RULES: ReportRule[] = [
|
||||
{
|
||||
id: "r1",
|
||||
name: "2024年1月用户增长分析报告",
|
||||
template: "用户增长分析报告",
|
||||
createdAt: "2024-01-15 14:30",
|
||||
status: "completed",
|
||||
pages: 12,
|
||||
id: 'rule_daily',
|
||||
name: '每日运营日报规则',
|
||||
templateId: 'tpl_daily',
|
||||
schedule: '每日 23:59',
|
||||
enabled: true,
|
||||
isDefault: true,
|
||||
config: {
|
||||
dateRange: 'today',
|
||||
includeCharts: true,
|
||||
sendTo: ['admin@company.com'],
|
||||
format: 'pdf',
|
||||
},
|
||||
promptTemplate: `生成每日运营日报,包含以下内容:
|
||||
|
||||
1. 核心指标
|
||||
- 总用户数及日增量
|
||||
- 活跃用户数及活跃率
|
||||
- 新增用户数
|
||||
|
||||
2. 用户活跃
|
||||
- 各等级用户活跃情况
|
||||
- 活跃时段分布
|
||||
|
||||
3. 渠道消息
|
||||
- 各渠道消息量统计
|
||||
- 响应时间分析
|
||||
|
||||
4. AI查询统计
|
||||
- 查询次数
|
||||
- 平均响应时间
|
||||
- 热门查询类型
|
||||
|
||||
输出格式:结构化报告,包含数据表格和趋势描述`,
|
||||
},
|
||||
{
|
||||
id: "r2",
|
||||
name: "高价值用户画像洞察",
|
||||
template: "用户画像洞察报告",
|
||||
createdAt: "2024-01-14 10:20",
|
||||
status: "completed",
|
||||
pages: 8,
|
||||
id: 'rule_weekly',
|
||||
name: '周度数据质量规则',
|
||||
templateId: 'tpl_weekly',
|
||||
schedule: '每周日 00:00',
|
||||
enabled: true,
|
||||
isDefault: true,
|
||||
config: {
|
||||
dateRange: 'week',
|
||||
includeCharts: true,
|
||||
sendTo: ['data@company.com'],
|
||||
format: 'pdf',
|
||||
},
|
||||
promptTemplate: `生成周度数据质量报告:
|
||||
|
||||
1. 质量概览
|
||||
- 数据完整度评分
|
||||
- 数据准确度评分
|
||||
- 本周质量趋势
|
||||
|
||||
2. 清洗统计
|
||||
- 清洗记录数
|
||||
- 格式化字段数
|
||||
- 异常数据处理
|
||||
|
||||
3. 去重效果
|
||||
- 去重前后对比
|
||||
- 重复率统计
|
||||
|
||||
4. 数据完整度
|
||||
- 各字段填充率
|
||||
- 缺失字段分析`,
|
||||
},
|
||||
{
|
||||
id: "r3",
|
||||
name: "Q4数据质量评估",
|
||||
template: "数据质量评估报告",
|
||||
createdAt: "2024-01-10 16:45",
|
||||
status: "generating",
|
||||
pages: 0,
|
||||
id: 'rule_monthly',
|
||||
name: '月度资产报告规则',
|
||||
templateId: 'tpl_monthly',
|
||||
schedule: '每月1日 08:00',
|
||||
enabled: true,
|
||||
isDefault: true,
|
||||
config: {
|
||||
dateRange: 'month',
|
||||
includeCharts: true,
|
||||
sendTo: ['admin@company.com', 'ceo@company.com'],
|
||||
format: 'pdf',
|
||||
},
|
||||
promptTemplate: `生成月度用户资产报告:
|
||||
|
||||
1. 资产总览
|
||||
- 用户总资产价值
|
||||
- 月度变化趋势
|
||||
- 核心指标汇总
|
||||
|
||||
2. 用户增长
|
||||
- 新增用户分析
|
||||
- 流失用户分析
|
||||
- 净增长情况
|
||||
|
||||
3. RFM分析
|
||||
- RFM评分分布
|
||||
- 各等级用户占比
|
||||
- 价值迁移分析
|
||||
|
||||
4. 流量池分布
|
||||
- 各流量池规模
|
||||
- 月度变化
|
||||
- 转化分析
|
||||
|
||||
5. 标签覆盖
|
||||
- 标签覆盖率
|
||||
- 热门标签TOP10`,
|
||||
},
|
||||
]
|
||||
|
||||
export default function AIReportPage() {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [selectedTemplate, setSelectedTemplate] = useState("")
|
||||
const [reportConfig, setReportConfig] = useState({
|
||||
name: "",
|
||||
dateRange: "last_30_days",
|
||||
userSegment: "all",
|
||||
customPrompt: "",
|
||||
})
|
||||
// 预定义报告模板
|
||||
const REPORT_TEMPLATES: ReportTemplate[] = [
|
||||
{
|
||||
id: 'tpl_daily',
|
||||
name: '每日运营日报',
|
||||
description: '包含当日用户活跃、渠道消息、AI查询等实时数据汇总',
|
||||
type: 'daily',
|
||||
sections: ['核心指标', '用户活跃', '渠道消息', 'AI查询统计'],
|
||||
icon: '📊',
|
||||
},
|
||||
{
|
||||
id: 'tpl_weekly',
|
||||
name: '周度数据质量报告',
|
||||
description: '数据清洗、去重、补全等质量指标周度汇总',
|
||||
type: 'weekly',
|
||||
sections: ['质量概览', '清洗统计', '去重效果', '数据完整度'],
|
||||
icon: '📈',
|
||||
},
|
||||
{
|
||||
id: 'tpl_monthly',
|
||||
name: '月度用户资产报告',
|
||||
description: '用户增长、RFM分布、流量池变化等核心资产指标',
|
||||
type: 'monthly',
|
||||
sections: ['资产总览', '用户增长', 'RFM分析', '流量池分布', '标签覆盖'],
|
||||
icon: '📑',
|
||||
},
|
||||
{
|
||||
id: 'tpl_rfm',
|
||||
name: 'RFM分析报告',
|
||||
description: '详细的RFM评分分布和用户价值分层分析',
|
||||
type: 'custom',
|
||||
sections: ['RFM概览', '评分分布', '等级分层', '价值趋势'],
|
||||
icon: '💎',
|
||||
},
|
||||
{
|
||||
id: 'tpl_portrait',
|
||||
name: '用户画像报告',
|
||||
description: '用户画像特征分析,包含地域、年龄、偏好等维度',
|
||||
type: 'custom',
|
||||
sections: ['画像概览', '地域分布', '行为特征', '偏好分析'],
|
||||
icon: '👤',
|
||||
},
|
||||
{
|
||||
id: 'tpl_traffic',
|
||||
name: '流量池分析报告',
|
||||
description: '各流量池用户规模、转化率、价值贡献分析',
|
||||
type: 'custom',
|
||||
sections: ['池规模', '转化漏斗', '价值贡献', '增长趋势'],
|
||||
icon: '🏊',
|
||||
},
|
||||
]
|
||||
|
||||
// 生成的报告
|
||||
interface GeneratedReport {
|
||||
id: string
|
||||
templateId: string
|
||||
title: string
|
||||
generatedAt: string
|
||||
status: 'ready' | 'generating'
|
||||
content?: string
|
||||
metrics: {
|
||||
totalUsers: number
|
||||
activeUsers: number
|
||||
newUsers: number
|
||||
rfmAvg: number
|
||||
}
|
||||
}
|
||||
|
||||
const GENERATED_REPORTS: GeneratedReport[] = [
|
||||
{
|
||||
id: 'rpt_1',
|
||||
templateId: 'tpl_monthly',
|
||||
title: '2024年1月用户资产月报',
|
||||
generatedAt: '2024-02-01 08:00',
|
||||
status: 'ready',
|
||||
metrics: { totalUsers: 2013000000, activeUsers: 450000000, newUsers: 15000000, rfmAvg: 52.3 },
|
||||
},
|
||||
{
|
||||
id: 'rpt_2',
|
||||
templateId: 'tpl_weekly',
|
||||
title: '本周数据质量报告',
|
||||
generatedAt: '2024-01-28 00:00',
|
||||
status: 'ready',
|
||||
metrics: { totalUsers: 2013000000, activeUsers: 320000000, newUsers: 2500000, rfmAvg: 51.8 },
|
||||
},
|
||||
{
|
||||
id: 'rpt_3',
|
||||
templateId: 'tpl_daily',
|
||||
title: '今日运营日报',
|
||||
generatedAt: '2024-01-30 23:59',
|
||||
status: 'ready',
|
||||
metrics: { totalUsers: 2013000000, activeUsers: 45000000, newUsers: 350000, rfmAvg: 52.5 },
|
||||
},
|
||||
]
|
||||
|
||||
export default function ReportPage() {
|
||||
const [reports, setReports] = useState<GeneratedReport[]>(GENERATED_REPORTS)
|
||||
const [rules, setRules] = useState<ReportRule[]>(DEFAULT_RULES)
|
||||
const [activeTab, setActiveTab] = useState("templates")
|
||||
const [generating, setGenerating] = useState<string | null>(null)
|
||||
const [showGenerateDialog, setShowGenerateDialog] = useState(false)
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<ReportTemplate | null>(null)
|
||||
const [showPreviewDialog, setShowPreviewDialog] = useState(false)
|
||||
const [previewReport, setPreviewReport] = useState<GeneratedReport | null>(null)
|
||||
const [showRuleDialog, setShowRuleDialog] = useState(false)
|
||||
const [selectedRule, setSelectedRule] = useState<ReportRule | null>(null)
|
||||
const [editingPrompt, setEditingPrompt] = useState('')
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)}B`
|
||||
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
|
||||
if (num >= 1000) return `${(num / 1000).toFixed(0)}K`
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
// 通过Skill生成报告
|
||||
const generateReport = async (template: ReportTemplate) => {
|
||||
setGenerating(template.id)
|
||||
setShowGenerateDialog(false)
|
||||
|
||||
try {
|
||||
// 调用AI Chat API执行报告生成
|
||||
const response = await fetch('/api/ai-chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
message: `生成${template.name},包含以下章节:${template.sections.join('、')}`
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// 添加新报告到列表
|
||||
const newReport: GeneratedReport = {
|
||||
id: `rpt_${Date.now()}`,
|
||||
templateId: template.id,
|
||||
title: `${template.name} - ${new Date().toLocaleDateString()}`,
|
||||
generatedAt: new Date().toLocaleString(),
|
||||
status: 'ready',
|
||||
content: data.success ? data.response?.content : '报告生成失败',
|
||||
metrics: {
|
||||
totalUsers: 2013000000,
|
||||
activeUsers: 450000000,
|
||||
newUsers: 15000000,
|
||||
rfmAvg: 52.3,
|
||||
},
|
||||
}
|
||||
|
||||
setReports([newReport, ...reports])
|
||||
} catch (error) {
|
||||
console.error('报告生成失败:', error)
|
||||
} finally {
|
||||
setGenerating(null)
|
||||
}
|
||||
}
|
||||
|
||||
const openPreview = (report: GeneratedReport) => {
|
||||
setPreviewReport(report)
|
||||
setShowPreviewDialog(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">智能报告</h1>
|
||||
<p className="text-muted-foreground">AI自动生成数据分析报告,支持自定义模板和提示词</p>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20">
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 顶部标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">智能报告</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">AI自动生成数据分析报告,支持多种报告模板</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowGenerateDialog(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
生成报告
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
生成报告
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 报告模板 */}
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-4">报告模板</h2>
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
{reportTemplates.map((template) => (
|
||||
<Card
|
||||
key={template.id}
|
||||
className="cursor-pointer hover:border-primary transition-colors"
|
||||
onClick={() => {
|
||||
setSelectedTemplate(template.id)
|
||||
setShowCreateDialog(true)
|
||||
}}
|
||||
>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="p-3 rounded-full bg-primary/10 mb-3">
|
||||
<template.icon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<h3 className="font-medium mb-1">{template.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{template.description}</p>
|
||||
{/* 标签页 */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="bg-white/80 backdrop-blur">
|
||||
<TabsTrigger value="templates">报告模板</TabsTrigger>
|
||||
<TabsTrigger value="rules">规则配置</TabsTrigger>
|
||||
<TabsTrigger value="history">历史报告</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 报告模板 */}
|
||||
<TabsContent value="templates" className="mt-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{REPORT_TEMPLATES.map(template => (
|
||||
<Card
|
||||
key={template.id}
|
||||
className="border-0 shadow-sm bg-white/80 hover:shadow-lg transition-all cursor-pointer group"
|
||||
>
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<span className="text-3xl">{template.icon}</span>
|
||||
<Badge variant="outline">{
|
||||
template.type === 'daily' ? '日报' :
|
||||
template.type === 'weekly' ? '周报' :
|
||||
template.type === 'monthly' ? '月报' : '自定义'
|
||||
}</Badge>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900 text-lg mb-1">{template.name}</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">{template.description}</p>
|
||||
<div className="flex flex-wrap gap-1 mb-4">
|
||||
{template.sections.map((section, i) => (
|
||||
<Badge key={i} variant="secondary" className="text-xs">{section}</Badge>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => generateReport(template)}
|
||||
disabled={generating === template.id}
|
||||
>
|
||||
{generating === template.id ? (
|
||||
<><Loader2 className="h-4 w-4 animate-spin mr-2" />生成中...</>
|
||||
) : (
|
||||
<><Play className="h-4 w-4 mr-2" />生成报告</>
|
||||
)}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 规则配置 */}
|
||||
<TabsContent value="rules" className="mt-4 space-y-3">
|
||||
{rules.map(rule => {
|
||||
const template = REPORT_TEMPLATES.find(t => t.id === rule.templateId)
|
||||
return (
|
||||
<Card key={rule.id} className="border-0 shadow-sm bg-white/80 backdrop-blur">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 rounded-xl bg-gradient-to-r from-purple-500 to-blue-500 text-2xl">
|
||||
{template?.icon || '📄'}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold text-gray-900">{rule.name}</h3>
|
||||
{rule.isDefault && <Badge variant="outline" className="text-xs">默认规则</Badge>}
|
||||
{rule.enabled ? (
|
||||
<Badge className="bg-green-100 text-green-700">已启用</Badge>
|
||||
) : (
|
||||
<Badge className="bg-gray-100 text-gray-700">已禁用</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">{template?.name}</p>
|
||||
<div className="flex items-center gap-4 text-xs text-gray-400 mt-1">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{rule.schedule}
|
||||
</span>
|
||||
<span>格式: {rule.config.format.toUpperCase()}</span>
|
||||
<span>发送: {rule.config.sendTo.join(', ')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedRule(rule)
|
||||
setEditingPrompt(rule.promptTemplate)
|
||||
setShowRuleDialog(true)
|
||||
}}
|
||||
>
|
||||
编辑规则
|
||||
</Button>
|
||||
<Button
|
||||
variant={rule.enabled ? "outline" : "default"}
|
||||
size="sm"
|
||||
onClick={() => setRules(rules.map(r => r.id === rule.id ? { ...r, enabled: !r.enabled } : r))}
|
||||
>
|
||||
{rule.enabled ? '禁用' : '启用'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</TabsContent>
|
||||
|
||||
{/* 历史报告 */}
|
||||
<TabsContent value="history" className="mt-4">
|
||||
<Card className="border-0 shadow-sm bg-white/80">
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{reports.map(report => {
|
||||
const template = REPORT_TEMPLATES.find(t => t.id === report.templateId)
|
||||
|
||||
return (
|
||||
<div key={report.id} className="flex items-center justify-between p-4 rounded-xl bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2.5 rounded-xl bg-white text-2xl">
|
||||
{template?.icon || '📄'}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{report.title}</h3>
|
||||
<div className="flex items-center gap-4 mt-1 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{report.generatedAt}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="h-3 w-3" />
|
||||
{formatNumber(report.metrics.totalUsers)} 用户
|
||||
</span>
|
||||
<span>RFM均值: {report.metrics.rfmAvg}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => openPreview(report)}>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Send className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* 最近报告 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>最近报告</CardTitle>
|
||||
<CardDescription>查看和管理已生成的报告</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{recentReports.map((report) => (
|
||||
<div
|
||||
key={report.id}
|
||||
className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-lg bg-primary/10">
|
||||
<FileText className="h-5 w-5 text-primary" />
|
||||
{/* 生成报告弹窗 */}
|
||||
<Dialog open={showGenerateDialog} onOpenChange={setShowGenerateDialog}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>生成智能报告</DialogTitle>
|
||||
<DialogDescription>选择报告模板,AI将自动生成分析报告</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label>报告模板</Label>
|
||||
<Select onValueChange={(v) => setSelectedTemplate(REPORT_TEMPLATES.find(t => t.id === v) || null)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择报告模板" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{REPORT_TEMPLATES.map(template => (
|
||||
<SelectItem key={template.id} value={template.id}>
|
||||
{template.icon} {template.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{selectedTemplate && (
|
||||
<div className="p-3 rounded-lg bg-purple-50">
|
||||
<p className="text-sm text-gray-600 mb-2">{selectedTemplate.description}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedTemplate.sections.map((section, i) => (
|
||||
<Badge key={i} variant="secondary" className="text-xs">{section}</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium">{report.name}</h4>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span>{report.template}</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{report.createdAt}
|
||||
</span>
|
||||
{report.status === "completed" && (
|
||||
<span>{report.pages}页</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label>数据范围</Label>
|
||||
<Select defaultValue="all">
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部数据</SelectItem>
|
||||
<SelectItem value="today">今日</SelectItem>
|
||||
<SelectItem value="week">本周</SelectItem>
|
||||
<SelectItem value="month">本月</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowGenerateDialog(false)}>取消</Button>
|
||||
<Button
|
||||
onClick={() => selectedTemplate && generateReport(selectedTemplate)}
|
||||
disabled={!selectedTemplate}
|
||||
>
|
||||
<Sparkles className="h-4 w-4 mr-2" />
|
||||
开始生成
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 报告预览弹窗 */}
|
||||
<Dialog open={showPreviewDialog} onOpenChange={setShowPreviewDialog}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{previewReport?.title}</DialogTitle>
|
||||
<DialogDescription>生成时间: {previewReport?.generatedAt}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{previewReport && (
|
||||
<div className="space-y-4 py-4">
|
||||
{/* 核心指标 */}
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<Card className="border bg-blue-50">
|
||||
<CardContent className="p-3 text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">{formatNumber(previewReport.metrics.totalUsers)}</div>
|
||||
<div className="text-xs text-gray-500">总用户</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border bg-green-50">
|
||||
<CardContent className="p-3 text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{formatNumber(previewReport.metrics.activeUsers)}</div>
|
||||
<div className="text-xs text-gray-500">活跃用户</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border bg-orange-50">
|
||||
<CardContent className="p-3 text-center">
|
||||
<div className="text-2xl font-bold text-orange-600">{formatNumber(previewReport.metrics.newUsers)}</div>
|
||||
<div className="text-xs text-gray-500">新增用户</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border bg-purple-50">
|
||||
<CardContent className="p-3 text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">{previewReport.metrics.rfmAvg}</div>
|
||||
<div className="text-xs text-gray-500">RFM均值</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 报告内容 */}
|
||||
<Card className="border">
|
||||
<CardContent className="p-4">
|
||||
<h4 className="font-medium text-gray-900 mb-3">报告摘要</h4>
|
||||
<div className="prose prose-sm text-gray-600">
|
||||
{previewReport.content || (
|
||||
<div className="space-y-2">
|
||||
<p>本报告基于神射手数据中台生成,涵盖用户资产数字化核心指标。</p>
|
||||
<p><strong>用户规模:</strong> 总用户{formatNumber(previewReport.metrics.totalUsers)},其中活跃用户占比{((previewReport.metrics.activeUsers / previewReport.metrics.totalUsers) * 100).toFixed(1)}%</p>
|
||||
<p><strong>用户价值:</strong> 平均RFM评分{previewReport.metrics.rfmAvg},较上期提升0.5分</p>
|
||||
<p><strong>增长趋势:</strong> 新增用户{formatNumber(previewReport.metrics.newUsers)},环比增长8.2%</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowPreviewDialog(false)}>关闭</Button>
|
||||
<Button variant="outline">
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
下载PDF
|
||||
</Button>
|
||||
<Button>
|
||||
<Send className="h-4 w-4 mr-2" />
|
||||
发送报告
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 规则编辑弹窗 */}
|
||||
<Dialog open={showRuleDialog} onOpenChange={setShowRuleDialog}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑报告规则</DialogTitle>
|
||||
<DialogDescription>{selectedRule?.name}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedRule && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>执行时间</Label>
|
||||
<Input value={selectedRule.schedule} readOnly className="bg-gray-50" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>输出格式</Label>
|
||||
<Select defaultValue={selectedRule.config.format}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="pdf">PDF</SelectItem>
|
||||
<SelectItem value="html">HTML</SelectItem>
|
||||
<SelectItem value="markdown">Markdown</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{report.status === "generating" ? (
|
||||
<Badge className="bg-blue-100 text-blue-700">
|
||||
<Sparkles className="h-3 w-3 mr-1 animate-pulse" />
|
||||
生成中
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outline" size="sm">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
预览
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
下载
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Share2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label>发送目标</Label>
|
||||
<Input defaultValue={selectedRule.config.sendTo.join(', ')} placeholder="多个邮箱用逗号分隔" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>报告生成提示词 <span className="text-gray-400 text-xs">(AI根据此提示词生成报告内容)</span></Label>
|
||||
<Textarea
|
||||
value={editingPrompt}
|
||||
onChange={(e) => setEditingPrompt(e.target.value)}
|
||||
rows={12}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 创建报告弹窗 */}
|
||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>生成智能报告</DialogTitle>
|
||||
<DialogDescription>选择模板并配置报告参数,AI将自动生成分析报告</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>报告名称</Label>
|
||||
<Input
|
||||
placeholder="输入报告名称"
|
||||
value={reportConfig.name}
|
||||
onChange={(e) => setReportConfig({...reportConfig, name: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>时间范围</Label>
|
||||
<Select
|
||||
value={reportConfig.dateRange}
|
||||
onValueChange={(v) => setReportConfig({...reportConfig, dateRange: v})}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="last_7_days">最近7天</SelectItem>
|
||||
<SelectItem value="last_30_days">最近30天</SelectItem>
|
||||
<SelectItem value="last_90_days">最近90天</SelectItem>
|
||||
<SelectItem value="custom">自定义</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>用户群体</Label>
|
||||
<Select
|
||||
value={reportConfig.userSegment}
|
||||
onValueChange={(v) => setReportConfig({...reportConfig, userSegment: v})}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部用户</SelectItem>
|
||||
<SelectItem value="high_value">高价值用户</SelectItem>
|
||||
<SelectItem value="new_users">新用户</SelectItem>
|
||||
<SelectItem value="active">活跃用户</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>自定义分析要求(可选)</Label>
|
||||
<Textarea
|
||||
placeholder="输入额外的分析要求,例如:重点分析转化漏斗、对比上月数据等"
|
||||
value={reportConfig.customPrompt}
|
||||
onChange={(e) => setReportConfig({...reportConfig, customPrompt: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowCreateDialog(false)}>取消</Button>
|
||||
<Button>
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
开始生成
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowRuleDialog(false)}>取消</Button>
|
||||
<Button onClick={() => {
|
||||
if (selectedRule) {
|
||||
setRules(rules.map(r => r.id === selectedRule.id ? { ...r, promptTemplate: editingPrompt } : r))
|
||||
}
|
||||
setShowRuleDialog(false)
|
||||
}}>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
保存规则
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user