Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

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>
)
}