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

338 lines
12 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: 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>
)
}