diff --git a/frontend/app/agency/reports/page.tsx b/frontend/app/agency/reports/page.tsx index d21367c..83b985a 100644 --- a/frontend/app/agency/reports/page.tsx +++ b/frontend/app/agency/reports/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' +import { Modal } from '@/components/ui/Modal' import { BarChart3, TrendingUp, @@ -14,20 +15,100 @@ import { Users, CheckCircle, XCircle, - Clock + Clock, + FileSpreadsheet, + File, + Check } from 'lucide-react' -// 模拟数据 -const mockStats = { - totalScripts: 156, - totalVideos: 128, - passRate: 85, - avgReviewTime: 4.2, - trend: { - scripts: '+12%', - videos: '+8%', - passRate: '+3%', - reviewTime: '-15%', +// 时间范围类型 +type DateRange = 'week' | 'month' | 'quarter' | 'year' + +// 按时间范围的模拟数据 +const mockDataByRange: Record = { + week: { + stats: { + totalScripts: 156, + totalVideos: 128, + passRate: 85, + avgReviewTime: 4.2, + trend: { scripts: '+12%', videos: '+8%', passRate: '+3%', reviewTime: '-15%' }, + }, + trendData: [ + { label: '周一', submitted: 25, passed: 22, rejected: 3 }, + { label: '周二', submitted: 32, passed: 28, rejected: 4 }, + { label: '周三', submitted: 18, passed: 16, rejected: 2 }, + { label: '周四', submitted: 41, passed: 35, rejected: 6 }, + { label: '周五', submitted: 35, passed: 30, rejected: 5 }, + { label: '周六', submitted: 12, passed: 11, rejected: 1 }, + { label: '周日', submitted: 8, passed: 7, rejected: 1 }, + ], + compareText: '上周', + }, + month: { + stats: { + totalScripts: 623, + totalVideos: 512, + passRate: 83, + avgReviewTime: 4.5, + trend: { scripts: '+18%', videos: '+15%', passRate: '+2%', reviewTime: '-10%' }, + }, + trendData: [ + { label: '第1周', submitted: 145, passed: 122, rejected: 23 }, + { label: '第2周', submitted: 168, passed: 140, rejected: 28 }, + { label: '第3周', submitted: 155, passed: 128, rejected: 27 }, + { label: '第4周', submitted: 171, passed: 145, rejected: 26 }, + ], + compareText: '上月', + }, + quarter: { + stats: { + totalScripts: 1856, + totalVideos: 1520, + passRate: 82, + avgReviewTime: 4.8, + trend: { scripts: '+25%', videos: '+22%', passRate: '+5%', reviewTime: '-8%' }, + }, + trendData: [ + { label: '1月', submitted: 580, passed: 478, rejected: 102 }, + { label: '2月', submitted: 615, passed: 503, rejected: 112 }, + { label: '3月', submitted: 661, passed: 548, rejected: 113 }, + ], + compareText: '上季度', + }, + year: { + stats: { + totalScripts: 7424, + totalVideos: 6080, + passRate: 81, + avgReviewTime: 5.0, + trend: { scripts: '+45%', videos: '+40%', passRate: '+8%', reviewTime: '-20%' }, + }, + trendData: [ + { label: '1月', submitted: 520, passed: 420, rejected: 100 }, + { label: '2月', submitted: 485, passed: 392, rejected: 93 }, + { label: '3月', submitted: 610, passed: 498, rejected: 112 }, + { label: '4月', submitted: 580, passed: 470, rejected: 110 }, + { label: '5月', submitted: 625, passed: 513, rejected: 112 }, + { label: '6月', submitted: 690, passed: 565, rejected: 125 }, + { label: '7月', submitted: 715, passed: 582, rejected: 133 }, + { label: '8月', submitted: 680, passed: 550, rejected: 130 }, + { label: '9月', submitted: 725, passed: 592, rejected: 133 }, + { label: '10月', submitted: 760, passed: 620, rejected: 140 }, + { label: '11月', submitted: 780, passed: 640, rejected: 140 }, + { label: '12月', submitted: 834, passed: 690, rejected: 144 }, + ], + compareText: '去年', }, } @@ -38,16 +119,6 @@ const mockProjectStats = [ { name: 'XX运动品牌', scripts: 51, videos: 37, passRate: 88 }, ] -const mockWeeklyData = [ - { day: '周一', submitted: 25, passed: 22, rejected: 3 }, - { day: '周二', submitted: 32, passed: 28, rejected: 4 }, - { day: '周三', submitted: 18, passed: 16, rejected: 2 }, - { day: '周四', submitted: 41, passed: 35, rejected: 6 }, - { day: '周五', submitted: 35, passed: 30, rejected: 5 }, - { day: '周六', submitted: 12, passed: 11, rejected: 1 }, - { day: '周日', submitted: 8, passed: 7, rejected: 1 }, -] - const mockCreatorRanking = [ { name: '小美护肤', passRate: 95, total: 28 }, { name: '健身教练王', passRate: 92, total: 15 }, @@ -56,15 +127,24 @@ const mockCreatorRanking = [ { name: '美食探店', passRate: 78, total: 25 }, ] -function StatCard({ title, value, unit, trend, icon: Icon, color }: { +// 时间范围标签 +const dateRangeLabels: Record = { + week: '本周', + month: '本月', + quarter: '本季度', + year: '本年', +} + +function StatCard({ title, value, unit, trend, compareText, icon: Icon, color }: { title: string value: number | string unit?: string trend?: string + compareText: string icon: React.ElementType color: string }) { - const isPositive = trend?.startsWith('+') || trend?.startsWith('-') && title.includes('时间') + const isPositive = trend?.startsWith('+') || (trend?.startsWith('-') && title.includes('时长')) return ( @@ -79,7 +159,7 @@ function StatCard({ title, value, unit, trend, icon: Icon, color }: { {trend && (
{isPositive ? : } - {trend} vs 上周 + {trend} vs {compareText}
)} @@ -93,7 +173,75 @@ function StatCard({ title, value, unit, trend, icon: Icon, color }: { } export default function AgencyReportsPage() { - const [dateRange, setDateRange] = useState('week') + const [dateRange, setDateRange] = useState('week') + const [showExportModal, setShowExportModal] = useState(false) + const [exportFormat, setExportFormat] = useState<'csv' | 'excel' | 'pdf'>('excel') + const [isExporting, setIsExporting] = useState(false) + const [exportSuccess, setExportSuccess] = useState(false) + + const currentData = mockDataByRange[dateRange] + + // 导出报表 + const handleExport = async () => { + setIsExporting(true) + // 模拟导出过程 + await new Promise(resolve => setTimeout(resolve, 1500)) + + // 生成文件名 + const dateStr = new Date().toISOString().split('T')[0] + const fileName = `审核数据报表_${dateRangeLabels[dateRange]}_${dateStr}` + + // 模拟下载 + if (exportFormat === 'csv') { + // 生成 CSV 内容 + const csvContent = generateCSV() + downloadFile(csvContent, `${fileName}.csv`, 'text/csv') + } else if (exportFormat === 'excel') { + // 实际项目中会使用 xlsx 库 + alert(`Excel 文件「${fileName}.xlsx」已开始下载`) + } else { + alert(`PDF 文件「${fileName}.pdf」已开始下载`) + } + + setIsExporting(false) + setExportSuccess(true) + setTimeout(() => { + setShowExportModal(false) + setExportSuccess(false) + }, 1500) + } + + // 生成 CSV 内容 + const generateCSV = () => { + const headers = ['指标', '数值', '趋势'] + const rows = [ + ['脚本审核量', currentData.stats.totalScripts, currentData.stats.trend.scripts], + ['视频审核量', currentData.stats.totalVideos, currentData.stats.trend.videos], + ['通过率', `${currentData.stats.passRate}%`, currentData.stats.trend.passRate], + ['平均审核时长', `${currentData.stats.avgReviewTime}小时`, currentData.stats.trend.reviewTime], + [], + ['时间段', '提交数', '通过数', '驳回数'], + ...currentData.trendData.map(d => [d.label, d.submitted, d.passed, d.rejected]), + [], + ['项目名称', '脚本数', '视频数', '通过率'], + ...mockProjectStats.map(p => [p.name, p.scripts, p.videos, `${p.passRate}%`]), + ] + + return [headers.join(','), ...rows.map(r => r.join(','))].join('\n') + } + + // 下载文件 + const downloadFile = (content: string, fileName: string, mimeType: string) => { + const blob = new Blob(['\ufeff' + content], { type: mimeType + ';charset=utf-8' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = fileName + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + } return (
@@ -105,35 +253,20 @@ export default function AgencyReportsPage() {
- - - + {(['week', 'month', 'quarter', 'year'] as DateRange[]).map((range) => ( + + ))}
- @@ -144,66 +277,70 @@ export default function AgencyReportsPage() {
- {/* 本周趋势 */} + {/* 趋势图 */} - 审核趋势 + 审核趋势 - {dateRangeLabels[dateRange]}
- {mockWeeklyData.map((day) => ( -
-
{day.day}
+ {currentData.trendData.map((item) => ( +
+
{item.label}
-
- {day.passed} +
+ {item.passed} / - {day.submitted} + {item.submitted}
))} @@ -293,6 +430,80 @@ export default function AgencyReportsPage() { + + {/* 导出弹窗 */} + setShowExportModal(false)} title="导出报表"> +
+ {exportSuccess ? ( +
+
+ +
+

导出成功!

+

文件已开始下载

+
+ ) : ( + <> +

+ 导出{dateRangeLabels[dateRange]}的审核数据报表,包含核心指标、趋势数据和项目统计。 +

+ +
+ +
+ {[ + { value: 'excel', label: 'Excel (.xlsx)', desc: '适合数据分析和图表制作', icon: FileSpreadsheet }, + { value: 'csv', label: 'CSV (.csv)', desc: '通用格式,兼容性好', icon: File }, + { value: 'pdf', label: 'PDF (.pdf)', desc: '适合打印和分享', icon: FileText }, + ].map((format) => ( + + ))} +
+
+ +
+ + +
+ + )} +
+
) }