主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
338 lines
12 KiB
TypeScript
338 lines
12 KiB
TypeScript
'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: 12,
|
||
pendingAppeal: 3,
|
||
todayPassed: 28,
|
||
inProgress: 45,
|
||
}
|
||
|
||
// 模拟紧急待办
|
||
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,
|
||
reviewing: 3,
|
||
needRevision: 2,
|
||
},
|
||
{
|
||
id: 'proj-002',
|
||
name: '新品口红系列',
|
||
total: 12,
|
||
submitted: 8,
|
||
passed: 6,
|
||
reviewing: 1,
|
||
needRevision: 1,
|
||
},
|
||
{
|
||
id: 'proj-003',
|
||
name: '护肤品秋季活动',
|
||
total: 15,
|
||
submitted: 12,
|
||
passed: 9,
|
||
reviewing: 2,
|
||
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}</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}</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}</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) => (
|
||
<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.reviewing / project.total) * 100}%` }}
|
||
title={`审核中: ${project.reviewing}`}
|
||
/>
|
||
<div
|
||
className="bg-orange-500 transition-all"
|
||
style={{ width: `${(project.needRevision / project.total) * 100}%` }}
|
||
title={`需修改: ${project.needRevision}`}
|
||
/>
|
||
</div>
|
||
<div className="flex gap-4 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.reviewing}
|
||
</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>
|
||
)
|
||
}
|