Your Name 0bfedb95c8 feat: 为所有终端添加平台显示功能
- 新增 frontend/lib/platforms.ts 共享平台配置模块
- 支持6个平台: 抖音、小红书、B站、快手、微博、微信视频号
- 品牌方终端: 项目看板、项目详情、终审台列表添加平台显示
- 代理商终端: 工作台概览、审核台、Brief配置、达人管理、
  数据报表、消息中心、申诉处理添加平台显示
- 达人端: 任务列表添加平台显示
- 统一使用彩色头部条样式展示平台信息

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 18:53:51 +08:00

278 lines
11 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 { useRouter } from 'next/navigation'
import { ArrowLeft, Check, X, CheckSquare, Video, Clock } from 'lucide-react'
import { cn } from '@/lib/utils'
import { getPlatformInfo } from '@/lib/platforms'
// 模拟待审核内容列表
const mockReviewItems = [
{
id: 'review-001',
title: '春季护肤新品体验分享',
creator: '小美',
agency: '代理商A',
platform: 'douyin',
reviewer: '张三',
reviewTime: '2小时前',
agencyOpinion: '内容符合Brief要求卖点覆盖完整建议通过。',
agencyStatus: 'passed',
aiScore: 12,
aiChecks: [
{ label: '合规检测', status: 'passed', description: '未检测到违禁词、竞品Logo等违规内容' },
{ label: '卖点覆盖', status: 'passed', description: '核心卖点覆盖率 95%' },
{ label: '品牌调性', status: 'passed', description: '视觉风格符合品牌调性' },
],
currentStep: 4, // 1-已提交, 2-AI审核, 3-代理商审核, 4-品牌终审
},
{
id: 'review-002',
title: '夏日清爽护肤推荐',
creator: '小红',
agency: '代理商B',
platform: 'xiaohongshu',
reviewer: '李四',
reviewTime: '5小时前',
agencyOpinion: '内容质量良好,但部分镜头略暗,建议后期调整后通过。',
agencyStatus: 'passed',
aiScore: 28,
aiChecks: [
{ label: '合规检测', status: 'passed', description: '未检测到违规内容' },
{ label: '卖点覆盖', status: 'warning', description: '核心卖点覆盖率 78%,建议增加产品特写' },
{ label: '品牌调性', status: 'passed', description: '视觉风格符合品牌调性' },
],
currentStep: 4,
},
]
// 审核流程进度组件
function ReviewProgressBar({ currentStep }: { currentStep: number }) {
const steps = [
{ label: '已提交', step: 1 },
{ label: 'AI审核', step: 2 },
{ label: '代理商审核', step: 3 },
{ label: '品牌终审', step: 4 },
]
return (
<div className="flex items-center w-full">
{steps.map((s, index) => {
const isCompleted = s.step < currentStep
const isCurrent = s.step === currentStep
return (
<div key={s.step} className="flex items-center flex-1">
<div className="flex flex-col items-center gap-1">
<div className={cn(
'flex items-center justify-center rounded-[10px]',
isCurrent ? 'w-6 h-6 bg-accent-indigo' :
isCompleted ? 'w-5 h-5 bg-accent-green' :
'w-5 h-5 bg-bg-elevated border border-border-subtle'
)}>
{isCompleted && <Check className="w-3 h-3 text-white" />}
{isCurrent && <Clock className="w-3 h-3 text-white" />}
</div>
<span className={cn(
'text-[10px]',
isCurrent ? 'text-accent-indigo font-semibold' :
isCompleted ? 'text-text-secondary' :
'text-text-tertiary'
)}>
{s.label}
</span>
</div>
{index < steps.length - 1 && (
<div className={cn(
'h-0.5 flex-1 rounded',
s.step < currentStep ? 'bg-accent-green' :
s.step === currentStep ? 'bg-accent-indigo' :
'bg-border-subtle'
)} />
)}
</div>
)
})}
</div>
)
}
export default function FinalReviewPage() {
const router = useRouter()
const [selectedItem, setSelectedItem] = useState(mockReviewItems[0])
const [feedback, setFeedback] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const platform = getPlatformInfo(selectedItem.platform)
const handleApprove = async () => {
setIsSubmitting(true)
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
alert('已通过审核')
setIsSubmitting(false)
}
const handleReject = async () => {
if (!feedback.trim()) {
alert('请填写驳回原因')
return
}
setIsSubmitting(true)
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
alert('已驳回')
setIsSubmitting(false)
setFeedback('')
}
return (
<div className="flex flex-col gap-6 h-full min-h-0">
{/* 顶部栏 */}
<div className="flex items-center justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold text-text-primary"></h1>
{platform && (
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-sm font-medium ${platform.bgColor} ${platform.textColor} border ${platform.borderColor}`}>
<span>{platform.icon}</span>
{platform.name}
</span>
)}
</div>
<p className="text-sm text-text-secondary">
{selectedItem.title} · : {selectedItem.creator}
</p>
</div>
<button
type="button"
onClick={() => router.back()}
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-bg-elevated text-text-secondary text-sm font-medium"
>
<ArrowLeft className="w-4 h-4" />
</button>
</div>
{/* 审核流程进度 */}
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-semibold text-text-primary"></span>
<span className="text-xs text-accent-indigo font-medium"></span>
</div>
<ReviewProgressBar currentStep={selectedItem.currentStep} />
</div>
{/* 主内容区 - 两栏布局 */}
<div className="flex gap-6 flex-1 min-h-0">
{/* 左侧 - 视频播放器 */}
<div className="flex-1 flex flex-col gap-4">
<div className="flex-1 bg-bg-card rounded-2xl card-shadow flex items-center justify-center">
<div className="w-[640px] h-[360px] rounded-xl bg-black flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="w-20 h-20 rounded-full bg-[#1A1A1E] flex items-center justify-center">
<Video className="w-10 h-10 text-text-tertiary" />
</div>
<p className="text-sm text-text-tertiary"></p>
</div>
</div>
</div>
</div>
{/* 右侧 - 分析面板 */}
<div className="w-[380px] flex flex-col gap-4 overflow-y-auto overflow-x-hidden">
{/* 代理商初审意见 */}
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
<div className="flex items-center justify-between mb-3">
<span className="text-base font-semibold text-text-primary"></span>
<span className={cn(
'px-3 py-1.5 rounded-lg text-[13px] font-semibold',
selectedItem.agencyStatus === 'passed' ? 'bg-accent-green/15 text-accent-green' : 'bg-accent-coral/15 text-accent-coral'
)}>
{selectedItem.agencyStatus === 'passed' ? '已通过' : '需修改'}
</span>
</div>
<div className="bg-bg-elevated rounded-[10px] p-3 flex flex-col gap-2">
<span className="text-xs text-text-tertiary">
{selectedItem.agency} - {selectedItem.reviewer} · {selectedItem.reviewTime}
</span>
<p className="text-[13px] text-text-secondary">{selectedItem.agencyOpinion}</p>
</div>
</div>
{/* AI 分析结果 */}
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
<div className="flex items-center justify-between mb-4">
<span className="text-base font-semibold text-text-primary">AI </span>
<span className={cn(
'px-3 py-1.5 rounded-lg text-[13px] font-semibold',
selectedItem.aiScore < 30 ? 'bg-accent-green/15 text-accent-green' : 'bg-accent-amber/15 text-accent-amber'
)}>
: {selectedItem.aiScore}
</span>
</div>
<div className="flex flex-col gap-3">
{selectedItem.aiChecks.map((check, index) => (
<div key={index} className="bg-bg-elevated rounded-[10px] p-3 flex flex-col gap-2">
<div className="flex items-center gap-2">
<CheckSquare className={cn(
'w-4 h-4',
check.status === 'passed' ? 'text-accent-green' : 'text-accent-amber'
)} />
<span className={cn(
'text-sm font-semibold',
check.status === 'passed' ? 'text-accent-green' : 'text-accent-amber'
)}>
{check.label} · {check.status === 'passed' ? '通过' : '警告'}
</span>
</div>
<p className="text-[13px] text-text-secondary">{check.description}</p>
</div>
))}
</div>
</div>
{/* 终审决策 */}
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
<h3 className="text-base font-semibold text-text-primary mb-4"></h3>
{/* 决策按钮 */}
<div className="flex gap-3 mb-4">
<button
type="button"
onClick={handleApprove}
disabled={isSubmitting}
className="flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl bg-accent-green text-white font-semibold disabled:opacity-50"
>
<Check className="w-[18px] h-[18px]" />
</button>
<button
type="button"
onClick={handleReject}
disabled={isSubmitting}
className="flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl bg-accent-coral text-white font-semibold disabled:opacity-50"
>
<X className="w-[18px] h-[18px]" />
</button>
</div>
{/* 终审意见 */}
<div className="flex flex-col gap-2">
<label className="text-[13px] font-medium text-text-secondary">
</label>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
placeholder="输入终审意见或修改建议..."
className="w-full h-20 p-3.5 rounded-xl bg-bg-elevated border border-border-subtle text-sm text-text-primary placeholder-text-tertiary resize-none focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
</div>
</div>
</div>
</div>
)
}