主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
199 lines
8.3 KiB
TypeScript
199 lines
8.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Download, Calendar, Filter } from 'lucide-react'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
|
import { Button } from '@/components/ui/Button'
|
|
import { Select } from '@/components/ui/Select'
|
|
import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
|
|
|
// 模拟报表数据
|
|
const mockReportData = [
|
|
{ id: '1', date: '2024-02-04', submitted: 45, passed: 40, failed: 5, avgScore: 82 },
|
|
{ id: '2', date: '2024-02-03', submitted: 52, passed: 48, failed: 4, avgScore: 85 },
|
|
{ id: '3', date: '2024-02-02', submitted: 38, passed: 32, failed: 6, avgScore: 78 },
|
|
{ id: '4', date: '2024-02-01', submitted: 61, passed: 55, failed: 6, avgScore: 84 },
|
|
{ id: '5', date: '2024-01-31', submitted: 55, passed: 50, failed: 5, avgScore: 83 },
|
|
{ id: '6', date: '2024-01-30', submitted: 48, passed: 44, failed: 4, avgScore: 86 },
|
|
{ id: '7', date: '2024-01-29', submitted: 42, passed: 38, failed: 4, avgScore: 81 },
|
|
]
|
|
|
|
// 模拟详细审核记录
|
|
const mockReviewRecords = [
|
|
{ id: '1', videoTitle: '夏日护肤推荐', creator: '小美护肤', platform: '抖音', score: 95, status: 'passed', reviewedAt: '2024-02-04 15:30' },
|
|
{ id: '2', videoTitle: '新品口红试色', creator: '美妆达人Lisa', platform: '小红书', score: 72, status: 'warning', reviewedAt: '2024-02-04 14:20' },
|
|
{ id: '3', videoTitle: '健身器材开箱', creator: '健身教练王', platform: '抖音', score: 45, status: 'failed', reviewedAt: '2024-02-04 13:15' },
|
|
{ id: '4', videoTitle: '美食探店vlog', creator: '吃货小胖', platform: '小红书', score: 88, status: 'passed', reviewedAt: '2024-02-04 12:00' },
|
|
{ id: '5', videoTitle: '数码产品评测', creator: '科技宅', platform: 'B站', score: 91, status: 'passed', reviewedAt: '2024-02-04 11:30' },
|
|
]
|
|
|
|
const periodOptions = [
|
|
{ value: '7d', label: '最近 7 天' },
|
|
{ value: '30d', label: '最近 30 天' },
|
|
{ value: '90d', label: '最近 90 天' },
|
|
]
|
|
|
|
const platformOptions = [
|
|
{ value: 'all', label: '全部平台' },
|
|
{ value: 'douyin', label: '抖音' },
|
|
{ value: 'xiaohongshu', label: '小红书' },
|
|
{ value: 'bilibili', label: 'B站' },
|
|
]
|
|
|
|
export default function ReportsPage() {
|
|
const [period, setPeriod] = useState('7d')
|
|
const [platform, setPlatform] = useState('all')
|
|
|
|
// 计算汇总数据
|
|
const summary = mockReportData.reduce(
|
|
(acc, day) => ({
|
|
totalSubmitted: acc.totalSubmitted + day.submitted,
|
|
totalPassed: acc.totalPassed + day.passed,
|
|
totalFailed: acc.totalFailed + day.failed,
|
|
}),
|
|
{ totalSubmitted: 0, totalPassed: 0, totalFailed: 0 }
|
|
)
|
|
const passRate = Math.round((summary.totalPassed / summary.totalSubmitted) * 100)
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-bold text-gray-900">审核报表</h1>
|
|
<Button icon={Download} variant="secondary">导出报表</Button>
|
|
</div>
|
|
|
|
{/* 筛选器 */}
|
|
<div className="flex gap-4">
|
|
<div className="w-40">
|
|
<Select
|
|
options={periodOptions}
|
|
value={period}
|
|
onChange={(e) => setPeriod(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="w-40">
|
|
<Select
|
|
options={platformOptions}
|
|
value={platform}
|
|
onChange={(e) => setPlatform(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 汇总卡片 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<div className="text-sm text-gray-500">提交总数</div>
|
|
<div className="text-3xl font-bold text-gray-900">{summary.totalSubmitted}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<div className="text-sm text-gray-500">通过数</div>
|
|
<div className="text-3xl font-bold text-green-600">{summary.totalPassed}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<div className="text-sm text-gray-500">驳回数</div>
|
|
<div className="text-3xl font-bold text-red-600">{summary.totalFailed}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<div className="text-sm text-gray-500">通过率</div>
|
|
<div className="text-3xl font-bold text-blue-600">{passRate}%</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* 每日数据表格 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>每日统计</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b text-left text-sm text-gray-500">
|
|
<th className="pb-3 font-medium">日期</th>
|
|
<th className="pb-3 font-medium">提交数</th>
|
|
<th className="pb-3 font-medium">通过数</th>
|
|
<th className="pb-3 font-medium">驳回数</th>
|
|
<th className="pb-3 font-medium">通过率</th>
|
|
<th className="pb-3 font-medium">平均分</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{mockReportData.map((row) => (
|
|
<tr key={row.id} className="border-b last:border-0">
|
|
<td className="py-3 font-medium text-gray-900">{row.date}</td>
|
|
<td className="py-3 text-gray-600">{row.submitted}</td>
|
|
<td className="py-3 text-green-600">{row.passed}</td>
|
|
<td className="py-3 text-red-600">{row.failed}</td>
|
|
<td className="py-3 text-gray-600">
|
|
{Math.round((row.passed / row.submitted) * 100)}%
|
|
</td>
|
|
<td className="py-3">
|
|
<span className={`font-medium ${row.avgScore >= 80 ? 'text-green-600' : 'text-yellow-600'}`}>
|
|
{row.avgScore}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 详细审核记录 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>审核记录</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b text-left text-sm text-gray-500">
|
|
<th className="pb-3 font-medium">视频标题</th>
|
|
<th className="pb-3 font-medium">达人</th>
|
|
<th className="pb-3 font-medium">平台</th>
|
|
<th className="pb-3 font-medium">合规分</th>
|
|
<th className="pb-3 font-medium">状态</th>
|
|
<th className="pb-3 font-medium">审核时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{mockReviewRecords.map((record) => (
|
|
<tr key={record.id} className="border-b last:border-0 hover:bg-gray-50">
|
|
<td className="py-3 font-medium text-gray-900">{record.videoTitle}</td>
|
|
<td className="py-3 text-gray-600">{record.creator}</td>
|
|
<td className="py-3 text-gray-600">{record.platform}</td>
|
|
<td className="py-3">
|
|
<span className={`font-medium ${
|
|
record.score >= 80 ? 'text-green-600' : record.score >= 60 ? 'text-yellow-600' : 'text-red-600'
|
|
}`}>
|
|
{record.score}
|
|
</span>
|
|
</td>
|
|
<td className="py-3">
|
|
{record.status === 'passed' && <SuccessTag>通过</SuccessTag>}
|
|
{record.status === 'warning' && <WarningTag>待改进</WarningTag>}
|
|
{record.status === 'failed' && <ErrorTag>驳回</ErrorTag>}
|
|
</td>
|
|
<td className="py-3 text-sm text-gray-500">{record.reviewedAt}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|