Your Name 576c89c8c4 feat(agency): 工作台统计数据分类显示脚本和视频
- 待审核数量分脚本/视频显示
- 今日通过数量分脚本/视频显示
- 进行中数量分脚本/视频显示
- 项目概览审核中状态分脚本审核/视频审核显示

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:47:43 +08:00

374 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
import {
AlertTriangle,
Clock,
CheckCircle,
XCircle,
ChevronRight,
FileVideo,
MessageSquare,
TrendingUp
} from 'lucide-react'
// 模拟统计数据
const stats = {
pendingReview: {
script: 8,
video: 4,
},
pendingAppeal: 3,
todayPassed: {
script: 18,
video: 10,
},
inProgress: {
script: 25,
video: 20,
},
}
// 模拟紧急待办
const urgentTodos = [
{
id: 'urgent-001',
type: 'violation',
title: '达人A视频 - 竞品露出',
description: 'XX品牌618推广',
time: '2小时前',
level: 'high',
},
{
id: 'urgent-002',
type: 'appeal',
title: '达人B申诉 - 待仲裁',
description: '对违禁词检测结果有异议',
time: '30分钟前',
level: 'medium',
},
{
id: 'urgent-003',
type: 'ai_done',
title: '达人C视频 - AI审核完成',
description: '新品口红试色',
time: '10分钟前',
level: 'low',
},
]
// 模拟项目概览
const projectOverview = [
{
id: 'proj-001',
name: 'XX品牌618推广',
total: 20,
submitted: 15,
passed: 10,
reviewingScript: 2,
reviewingVideo: 1,
needRevision: 2,
},
{
id: 'proj-002',
name: '新品口红系列',
total: 12,
submitted: 8,
passed: 6,
reviewingScript: 1,
reviewingVideo: 0,
needRevision: 1,
},
{
id: 'proj-003',
name: '护肤品秋季活动',
total: 15,
submitted: 12,
passed: 9,
reviewingScript: 1,
reviewingVideo: 1,
needRevision: 1,
},
]
// 模拟待审核任务列表
const pendingTasks = [
{
id: 'task-001',
videoTitle: '夏日护肤推广',
creatorName: '小美护肤',
brandName: 'XX品牌',
aiScore: 85,
submittedAt: '2026-02-04 14:30',
hasHighRisk: false,
},
{
id: 'task-002',
videoTitle: '新品口红试色',
creatorName: '美妆达人Lisa',
brandName: 'XX品牌',
aiScore: 72,
submittedAt: '2026-02-04 13:45',
hasHighRisk: true,
},
{
id: 'task-003',
videoTitle: '健身器材开箱',
creatorName: '健身教练王',
brandName: 'XX运动',
aiScore: 68,
submittedAt: '2026-02-04 14:50',
hasHighRisk: true,
},
]
function UrgentLevelIcon({ level }: { level: string }) {
if (level === 'high') return <AlertTriangle size={16} className="text-red-500" />
if (level === 'medium') return <MessageSquare size={16} className="text-orange-500" />
return <CheckCircle size={16} className="text-yellow-500" />
}
export default function AgencyDashboard() {
return (
<div className="space-y-6">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-text-primary"></h1>
<div className="text-sm text-text-secondary">{new Date().toLocaleString('zh-CN')}</div>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-gradient-to-br from-accent-coral/20 to-bg-card border-accent-coral/30">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<div className="text-sm text-text-secondary"></div>
<div className="text-3xl font-bold text-accent-coral">{stats.pendingReview.script + stats.pendingReview.video}</div>
<div className="flex gap-3 mt-1 text-xs text-text-tertiary">
<span> {stats.pendingReview.script}</span>
<span> {stats.pendingReview.video}</span>
</div>
</div>
<div className="w-12 h-12 rounded-full bg-accent-coral/20 flex items-center justify-center">
<Clock size={24} className="text-accent-coral" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-orange-500/20 to-bg-card border-orange-500/30">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<div className="text-sm text-text-secondary"></div>
<div className="text-3xl font-bold text-orange-400">{stats.pendingAppeal}</div>
</div>
<div className="w-12 h-12 rounded-full bg-orange-500/20 flex items-center justify-center">
<MessageSquare size={24} className="text-orange-400" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-accent-green/20 to-bg-card border-accent-green/30">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<div className="text-sm text-text-secondary"></div>
<div className="text-3xl font-bold text-accent-green">{stats.todayPassed.script + stats.todayPassed.video}</div>
<div className="flex gap-3 mt-1 text-xs text-text-tertiary">
<span> {stats.todayPassed.script}</span>
<span> {stats.todayPassed.video}</span>
</div>
</div>
<div className="w-12 h-12 rounded-full bg-accent-green/20 flex items-center justify-center">
<CheckCircle size={24} className="text-accent-green" />
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-accent-indigo/20 to-bg-card border-accent-indigo/30">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<div className="text-sm text-text-secondary"></div>
<div className="text-3xl font-bold text-accent-indigo">{stats.inProgress.script + stats.inProgress.video}</div>
<div className="flex gap-3 mt-1 text-xs text-text-tertiary">
<span> {stats.inProgress.script}</span>
<span> {stats.inProgress.video}</span>
</div>
</div>
<div className="w-12 h-12 rounded-full bg-accent-indigo/20 flex items-center justify-center">
<FileVideo size={24} className="text-accent-indigo" />
</div>
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 紧急待办 */}
<Card className="lg:col-span-1">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle size={18} className="text-red-500" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{urgentTodos.map((todo) => (
<Link
key={todo.id}
href={todo.type === 'violation' || todo.type === 'ai_done' ? `/agency/review/${todo.id}` : `/agency/appeals/${todo.id}`}
className="block p-3 rounded-lg border border-border-subtle hover:border-accent-indigo hover:bg-accent-indigo/5 transition-colors"
>
<div className="flex items-start gap-3">
<UrgentLevelIcon level={todo.level} />
<div className="flex-1 min-w-0">
<div className="font-medium text-text-primary truncate">{todo.title}</div>
<div className="text-sm text-text-secondary">{todo.description}</div>
<div className="text-xs text-text-tertiary mt-1">{todo.time}</div>
</div>
<ChevronRight size={16} className="text-text-tertiary flex-shrink-0" />
</div>
</Link>
))}
</CardContent>
</Card>
{/* 项目概览 */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp size={18} className="text-blue-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{projectOverview.map((project) => {
const totalReviewing = project.reviewingScript + project.reviewingVideo
return (
<div key={project.id} className="p-4 rounded-lg bg-bg-elevated">
<div className="flex items-center justify-between mb-3">
<span className="font-medium text-text-primary">{project.name}</span>
<span className="text-sm text-text-secondary">
{project.submitted}/{project.total}
</span>
</div>
<div className="flex h-3 rounded-full overflow-hidden bg-bg-page">
<div
className="bg-accent-green transition-all"
style={{ width: `${(project.passed / project.total) * 100}%` }}
title={`已通过: ${project.passed}`}
/>
<div
className="bg-accent-indigo transition-all"
style={{ width: `${(project.reviewingScript / project.total) * 100}%` }}
title={`脚本审核中: ${project.reviewingScript}`}
/>
<div
className="bg-purple-500 transition-all"
style={{ width: `${(project.reviewingVideo / project.total) * 100}%` }}
title={`视频审核中: ${project.reviewingVideo}`}
/>
<div
className="bg-orange-500 transition-all"
style={{ width: `${(project.needRevision / project.total) * 100}%` }}
title={`需修改: ${project.needRevision}`}
/>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-text-secondary">
<span className="flex items-center gap-1">
<span className="w-2 h-2 bg-accent-green rounded-full" />
{project.passed}
</span>
<span className="flex items-center gap-1">
<span className="w-2 h-2 bg-accent-indigo rounded-full" />
{project.reviewingScript}
</span>
<span className="flex items-center gap-1">
<span className="w-2 h-2 bg-purple-500 rounded-full" />
{project.reviewingVideo}
</span>
<span className="flex items-center gap-1">
<span className="w-2 h-2 bg-orange-500 rounded-full" />
{project.needRevision}
</span>
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
</div>
{/* 待审核列表 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span></span>
<Link href="/agency/tasks">
<Button variant="ghost" size="sm">
<ChevronRight size={16} />
</Button>
</Link>
</CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-border-subtle text-left text-sm text-text-secondary">
<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">AI评分</th>
<th className="pb-3 font-medium"></th>
<th className="pb-3 font-medium"></th>
</tr>
</thead>
<tbody>
{pendingTasks.map((task) => (
<tr key={task.id} className="border-b border-border-subtle last:border-0 hover:bg-bg-elevated">
<td className="py-4">
<div className="flex items-center gap-2">
<div className="font-medium text-text-primary">{task.videoTitle}</div>
{task.hasHighRisk && (
<span className="px-1.5 py-0.5 text-xs bg-accent-coral/20 text-accent-coral rounded">
</span>
)}
</div>
</td>
<td className="py-4 text-text-secondary">{task.creatorName}</td>
<td className="py-4 text-text-secondary">{task.brandName}</td>
<td className="py-4">
<span className={`font-medium ${
task.aiScore >= 80 ? 'text-accent-green' : task.aiScore >= 60 ? 'text-yellow-400' : 'text-accent-coral'
}`}>
{task.aiScore}
</span>
</td>
<td className="py-4 text-sm text-text-tertiary">{task.submittedAt}</td>
<td className="py-4">
<Link href={`/agency/review/${task.id}`}>
<Button size="sm"></Button>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
)
}