主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
345 lines
12 KiB
TypeScript
345 lines
12 KiB
TypeScript
'use client'
|
||
|
||
import Link from 'next/link'
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||
import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
||
import { ProgressBar } from '@/components/ui/ProgressBar'
|
||
import { Button } from '@/components/ui/Button'
|
||
import {
|
||
TrendingUp,
|
||
TrendingDown,
|
||
BarChart3,
|
||
Target,
|
||
AlertTriangle,
|
||
Clock,
|
||
ChevronRight,
|
||
Shield,
|
||
Users,
|
||
FileVideo
|
||
} from 'lucide-react'
|
||
|
||
// 模拟核心指标
|
||
const metrics = {
|
||
totalReviews: 1234,
|
||
totalTrend: '+12%',
|
||
passRate: 78.5,
|
||
passRateTrend: '+5.2%',
|
||
hardRecall: 96.2,
|
||
hardRecallTarget: 95,
|
||
sentimentBlocks: 23,
|
||
sentimentTrend: '-18%',
|
||
avgCycle: 4.2,
|
||
avgCycleTarget: 5,
|
||
}
|
||
|
||
// 模拟趋势数据
|
||
const weeklyData = [
|
||
{ day: '周一', submitted: 45, passed: 40, failed: 5 },
|
||
{ day: '周二', submitted: 52, passed: 48, failed: 4 },
|
||
{ day: '周三', submitted: 38, passed: 35, failed: 3 },
|
||
{ day: '周四', submitted: 61, passed: 54, failed: 7 },
|
||
{ day: '周五', submitted: 55, passed: 50, failed: 5 },
|
||
{ day: '周六', submitted: 28, passed: 26, failed: 2 },
|
||
{ day: '周日', submitted: 22, passed: 20, failed: 2 },
|
||
]
|
||
|
||
// 模拟违规类型分布
|
||
const violationTypes = [
|
||
{ type: '违禁词', count: 156, percentage: 45, color: 'bg-red-500' },
|
||
{ type: '竞品露出', count: 89, percentage: 26, color: 'bg-orange-500' },
|
||
{ type: '卖点遗漏', count: 67, percentage: 19, color: 'bg-yellow-500' },
|
||
{ type: '舆情风险', count: 34, percentage: 10, color: 'bg-purple-500' },
|
||
]
|
||
|
||
// 模拟代理商排名
|
||
const agencyRanking = [
|
||
{ name: '星耀传媒', passRate: 92, reviews: 156, trend: 'up' },
|
||
{ name: '创意无限', passRate: 88, reviews: 134, trend: 'up' },
|
||
{ name: '美妆达人MCN', passRate: 82, reviews: 98, trend: 'down' },
|
||
{ name: '时尚风向标', passRate: 78, reviews: 87, trend: 'stable' },
|
||
]
|
||
|
||
// 模拟风险预警
|
||
const riskAlerts = [
|
||
{
|
||
id: 'alert-001',
|
||
level: 'high',
|
||
title: '代理商A竞品露出集中',
|
||
description: '过去24小时内5条视频触发"竞品露出"',
|
||
time: '10分钟前',
|
||
},
|
||
{
|
||
id: 'alert-002',
|
||
level: 'medium',
|
||
title: '达人B连续未通过',
|
||
description: '连续3次提交未通过,建议沟通',
|
||
time: '2小时前',
|
||
},
|
||
{
|
||
id: 'alert-003',
|
||
level: 'low',
|
||
title: '舆情风险上升',
|
||
description: '本周舆情风险拦截数异常上升,建议检查阈值',
|
||
time: '5小时前',
|
||
},
|
||
]
|
||
|
||
function MetricCard({
|
||
title,
|
||
value,
|
||
unit = '',
|
||
trend,
|
||
target,
|
||
icon: Icon,
|
||
color,
|
||
}: {
|
||
title: string
|
||
value: number | string
|
||
unit?: string
|
||
trend?: string
|
||
target?: number
|
||
icon: React.ElementType
|
||
color: string
|
||
}) {
|
||
return (
|
||
<Card>
|
||
<CardContent className="py-4">
|
||
<div className="flex items-start justify-between">
|
||
<div>
|
||
<div className="text-sm text-text-secondary mb-1">{title}</div>
|
||
<div className="flex items-baseline gap-1">
|
||
<span className={`text-3xl font-bold ${color}`}>{value}</span>
|
||
{unit && <span className="text-lg text-text-secondary">{unit}</span>}
|
||
</div>
|
||
{trend && (
|
||
<div className={`text-xs mt-1 flex items-center gap-1 ${
|
||
trend.includes('+') || trend.includes('↓') ? 'text-accent-green' : trend.includes('-') ? 'text-accent-coral' : 'text-text-secondary'
|
||
}`}>
|
||
{trend.includes('+') ? <TrendingUp size={12} /> : trend.includes('-') && !trend.includes('↓') ? <TrendingDown size={12} /> : null}
|
||
{trend} vs 上周
|
||
</div>
|
||
)}
|
||
{target && (
|
||
<div className="text-xs text-text-tertiary mt-1">
|
||
目标 ≥{target}{unit} {Number(value) >= target ? '✅' : '⚠️'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className={`w-12 h-12 rounded-lg ${color.replace('text-', 'bg-').replace('600', '').replace('900', '')}/20 flex items-center justify-center`}>
|
||
<Icon size={24} className={color} />
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
function AlertLevelIcon({ level }: { level: string }) {
|
||
if (level === 'high') return <AlertTriangle size={16} className="text-red-500" />
|
||
if (level === 'medium') return <AlertTriangle size={16} className="text-orange-500" />
|
||
return <AlertTriangle size={16} className="text-yellow-500" />
|
||
}
|
||
|
||
export default function BrandDashboard() {
|
||
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-5 gap-4">
|
||
<MetricCard
|
||
title="本月审核总量"
|
||
value={metrics.totalReviews}
|
||
trend={metrics.totalTrend}
|
||
icon={FileVideo}
|
||
color="text-text-primary"
|
||
/>
|
||
<MetricCard
|
||
title="初审通过率"
|
||
value={metrics.passRate}
|
||
unit="%"
|
||
trend={metrics.passRateTrend}
|
||
icon={Target}
|
||
color="text-accent-green"
|
||
/>
|
||
<MetricCard
|
||
title="硬性召回率"
|
||
value={metrics.hardRecall}
|
||
unit="%"
|
||
target={metrics.hardRecallTarget}
|
||
icon={Shield}
|
||
color="text-accent-indigo"
|
||
/>
|
||
<MetricCard
|
||
title="舆情拦截数"
|
||
value={metrics.sentimentBlocks}
|
||
trend={metrics.sentimentTrend + ' ↓'}
|
||
icon={AlertTriangle}
|
||
color="text-purple-400"
|
||
/>
|
||
<MetricCard
|
||
title="平均审核周期"
|
||
value={metrics.avgCycle}
|
||
unit="小时"
|
||
target={metrics.avgCycleTarget}
|
||
icon={Clock}
|
||
color="text-orange-400"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
{/* 本周趋势 */}
|
||
<Card className="lg:col-span-2">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<BarChart3 size={18} className="text-blue-500" />
|
||
本周审核趋势
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
{weeklyData.map((day) => (
|
||
<div key={day.day} className="flex items-center gap-4">
|
||
<div className="w-12 text-sm text-text-secondary font-medium">{day.day}</div>
|
||
<div className="flex-1">
|
||
<div className="flex h-6 rounded-full overflow-hidden bg-bg-elevated">
|
||
<div
|
||
className="bg-accent-green transition-all"
|
||
style={{ width: `${(day.passed / day.submitted) * 100}%` }}
|
||
/>
|
||
<div
|
||
className="bg-accent-coral transition-all"
|
||
style={{ width: `${(day.failed / day.submitted) * 100}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="w-24 text-right text-sm">
|
||
<span className="text-accent-green font-medium">{day.passed}</span>
|
||
<span className="text-text-tertiary"> / </span>
|
||
<span className="text-text-secondary">{day.submitted}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="flex gap-6 mt-4 text-sm">
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-3 h-3 bg-accent-green rounded" />
|
||
<span className="text-text-secondary">通过</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-3 h-3 bg-accent-coral rounded" />
|
||
<span className="text-text-secondary">驳回</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 风险预警 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<AlertTriangle size={18} className="text-red-500" />
|
||
风险预警
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
{riskAlerts.map((alert) => (
|
||
<div
|
||
key={alert.id}
|
||
className={`p-3 rounded-lg border cursor-pointer hover:shadow-sm transition-shadow ${
|
||
alert.level === 'high'
|
||
? 'bg-accent-coral/10 border-accent-coral/30'
|
||
: alert.level === 'medium'
|
||
? 'bg-orange-500/10 border-orange-500/30'
|
||
: 'bg-yellow-500/10 border-yellow-500/30'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-2">
|
||
<AlertLevelIcon level={alert.level} />
|
||
<div className="flex-1 min-w-0">
|
||
<div className="font-medium text-text-primary text-sm">{alert.title}</div>
|
||
<div className="text-xs text-text-secondary mt-0.5">{alert.description}</div>
|
||
<div className="text-xs text-text-tertiary mt-1">{alert.time}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
<Button variant="ghost" fullWidth size="sm">
|
||
查看全部预警
|
||
<ChevronRight size={14} />
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{/* 违规类型分布 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>违规类型分布</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
{violationTypes.map((item) => (
|
||
<div key={item.type}>
|
||
<div className="flex justify-between text-sm mb-2">
|
||
<span className="text-text-primary font-medium">{item.type}</span>
|
||
<span className="text-text-secondary">{item.count} 次 ({item.percentage}%)</span>
|
||
</div>
|
||
<div className="h-2 bg-bg-elevated rounded-full overflow-hidden">
|
||
<div className={`h-full ${item.color} transition-all`} style={{ width: `${item.percentage}%` }} />
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 代理商排名 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Users size={18} className="text-blue-500" />
|
||
代理商通过率排名
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
{agencyRanking.map((agency, index) => (
|
||
<div key={agency.name} className="flex items-center gap-4 p-3 rounded-lg bg-bg-elevated">
|
||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold ${
|
||
index === 0 ? 'bg-yellow-500/20 text-yellow-400' :
|
||
index === 1 ? 'bg-gray-500/20 text-gray-400' :
|
||
index === 2 ? 'bg-orange-500/20 text-orange-400' : 'bg-bg-page text-text-tertiary'
|
||
}`}>
|
||
{index + 1}
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="font-medium text-text-primary">{agency.name}</div>
|
||
<div className="text-xs text-text-secondary">{agency.reviews} 条审核</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className={`font-bold ${agency.passRate >= 90 ? 'text-accent-green' : agency.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}>
|
||
{agency.passRate}%
|
||
</div>
|
||
<div className="flex items-center justify-end gap-1 text-xs">
|
||
{agency.trend === 'up' && <TrendingUp size={12} className="text-accent-green" />}
|
||
{agency.trend === 'down' && <TrendingDown size={12} className="text-accent-coral" />}
|
||
<span className="text-text-tertiary">
|
||
{agency.trend === 'up' ? '上升' : agency.trend === 'down' ? '下降' : '持平'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|