'use client' import { useState, useEffect, useCallback } from 'react' import { useParams, useRouter } from 'next/navigation' import { useToast } from '@/components/ui/Toast' import { Upload, Check, X, Folder, Bell, MessageCircle, XCircle, CheckCircle, Loader2, Scan, ArrowLeft, Bot, Users, Building2, Clock, FileText, Video, ChevronRight, AlertTriangle, Download, Eye, Target, Ban, ChevronDown, ChevronUp, File } from 'lucide-react' import { Modal } from '@/components/ui/Modal' import { Button } from '@/components/ui/Button' import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' import { cn } from '@/lib/utils' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import { useSSE } from '@/contexts/SSEContext' import type { TaskResponse, AIReviewResult, ReviewDimensions, SellingPointMatchResult, BriefMatchDetail } from '@/types/task' import type { BriefResponse } from '@/types/brief' // 前端 UI 使用的任务阶段类型 type TaskPhase = 'script' | 'video' type TaskStage = | 'upload' | 'ai_reviewing' | 'ai_result' | 'agency_reviewing' | 'agency_rejected' | 'brand_reviewing' | 'brand_approved' | 'brand_rejected' type Issue = { title: string description: string timestamp?: string severity?: 'error' | 'warning' } type ReviewLog = { time: string message: string status: 'done' | 'loading' | 'pending' } type TaskData = { id: string title: string subtitle: string phase: TaskPhase stage: TaskStage progress?: number issues?: Issue[] reviewLogs?: ReviewLog[] rejectionReason?: string submittedAt?: string scriptContent?: string aiResult?: { score: number dimensions?: ReviewDimensions sellingPointMatches?: SellingPointMatchResult[] briefMatchDetail?: BriefMatchDetail violations: Array<{ type: string; content: string; suggestion: string; dimension?: string }> } agencyReview?: { result: 'approved' | 'rejected'; comment: string; reviewer: string; time: string } brandReview?: { result: 'approved' | 'rejected'; comment: string; reviewer: string; time: string } } type AgencyBriefFile = { id: string name: string size: string uploadedAt: string description?: string } // 将后端 TaskResponse 映射为前端 UI 的 TaskData function mapApiTaskToTaskData(task: TaskResponse): TaskData { const stage = task.stage let phase: TaskPhase = 'script' let uiStage: TaskStage = 'upload' let issues: Issue[] = [] let rejectionReason: string | undefined let submittedAt: string | undefined // 判断阶段 if (stage.startsWith('video_') || stage === 'completed') { phase = 'video' } // 映射阶段 switch (stage) { case 'script_upload': uiStage = 'upload'; break case 'script_ai_review': uiStage = 'ai_reviewing'; break case 'script_agency_review': uiStage = 'agency_reviewing'; submittedAt = task.script_uploaded_at || undefined; break case 'script_brand_review': uiStage = 'brand_reviewing'; submittedAt = task.script_uploaded_at || undefined; break case 'video_upload': uiStage = 'upload'; phase = 'video'; break case 'video_ai_review': uiStage = 'ai_reviewing'; phase = 'video'; break case 'video_agency_review': uiStage = 'agency_reviewing'; phase = 'video'; submittedAt = task.video_uploaded_at || undefined; break case 'video_brand_review': uiStage = 'brand_reviewing'; phase = 'video'; submittedAt = task.video_uploaded_at || undefined; break case 'completed': uiStage = 'brand_approved'; phase = 'video'; submittedAt = task.video_uploaded_at || undefined; break case 'rejected': { // 判断是哪个阶段被驳回 if (task.video_brand_status === 'rejected') { phase = 'video'; uiStage = 'brand_rejected' rejectionReason = task.video_brand_comment || undefined } else if (task.video_agency_status === 'rejected') { phase = 'video'; uiStage = 'agency_rejected' rejectionReason = task.video_agency_comment || undefined } else if (task.script_brand_status === 'rejected') { phase = 'script'; uiStage = 'brand_rejected' rejectionReason = task.script_brand_comment || undefined } else if (task.script_agency_status === 'rejected') { phase = 'script'; uiStage = 'agency_rejected' rejectionReason = task.script_agency_comment || undefined } else { uiStage = 'ai_result' } break } } // 处理驳回状态(非 rejected stage 但有驳回) if (task.script_agency_status === 'rejected' && stage !== 'rejected') { phase = 'script'; uiStage = 'agency_rejected' rejectionReason = task.script_agency_comment || undefined } if (task.script_brand_status === 'rejected' && stage !== 'rejected') { phase = 'script'; uiStage = 'brand_rejected' rejectionReason = task.script_brand_comment || undefined } if (task.video_agency_status === 'rejected' && stage !== 'rejected') { phase = 'video'; uiStage = 'agency_rejected' rejectionReason = task.video_agency_comment || undefined } if (task.video_brand_status === 'rejected' && stage !== 'rejected') { phase = 'video'; uiStage = 'brand_rejected' rejectionReason = task.video_brand_comment || undefined } // 提取 AI 审核结果中的 issues const aiResult = phase === 'script' ? task.script_ai_result : task.video_ai_result if (aiResult?.violations) { const dimLabels: Record = { legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' } issues = aiResult.violations.map(v => ({ title: v.dimension ? `[${dimLabels[v.dimension] || v.dimension}] ${v.type}` : v.type, description: `${v.content}${v.suggestion ? ` — ${v.suggestion}` : ''}`, timestamp: v.timestamp ? `${v.timestamp}s` : undefined, severity: v.severity === 'warning' ? 'warning' as const : 'error' as const, })) } const subtitle = `${task.project.name} · ${task.project.brand_name || ''}` // AI 审核结果(完整,含维度) const aiResultData = aiResult ? { score: aiResult.score, dimensions: aiResult.dimensions, sellingPointMatches: aiResult.selling_point_matches, briefMatchDetail: aiResult.brief_match_detail, violations: aiResult.violations.map(v => ({ type: v.type, content: v.content, suggestion: v.suggestion, dimension: v.dimension })), } : undefined // 代理商审核反馈 const agencyStatus = phase === 'script' ? task.script_agency_status : task.video_agency_status const agencyComment = phase === 'script' ? task.script_agency_comment : task.video_agency_comment const agencyReview = agencyStatus && agencyStatus !== 'pending' ? { result: (agencyStatus === 'passed' || agencyStatus === 'force_passed' ? 'approved' : 'rejected') as 'approved' | 'rejected', comment: agencyComment || '', reviewer: task.agency?.name || '代理商', time: task.updated_at, } : undefined // 品牌方审核反馈 const brandStatus = phase === 'script' ? task.script_brand_status : task.video_brand_status const brandComment = phase === 'script' ? task.script_brand_comment : task.video_brand_comment const brandReview = brandStatus && brandStatus !== 'pending' ? { result: (brandStatus === 'passed' || brandStatus === 'force_passed' ? 'approved' : 'rejected') as 'approved' | 'rejected', comment: brandComment || '', reviewer: '品牌方审核员', time: task.updated_at, } : undefined return { id: task.id, title: task.name, subtitle, phase, stage: uiStage, issues: issues.length > 0 ? issues : undefined, rejectionReason, submittedAt, aiResult: aiResultData, agencyReview, brandReview, } } // Mock Brief 数据 const mockBriefData = { files: [ { id: 'af1', name: '达人拍摄指南.pdf', size: '1.5MB', uploadedAt: '2026-02-02', description: '详细的拍摄流程和注意事项' }, { id: 'af2', name: '产品卖点话术.docx', size: '800KB', uploadedAt: '2026-02-02', description: '推荐使用的话术和表达方式' }, { id: 'af3', name: '品牌视觉参考.pdf', size: '3.2MB', uploadedAt: '2026-02-02', description: '视觉风格和拍摄参考示例' }, ] as AgencyBriefFile[], sellingPoints: [ { id: 'sp1', content: 'SPF50+ PA++++', priority: 'core' as const }, { id: 'sp2', content: '轻薄质地,不油腻', priority: 'core' as const }, { id: 'sp3', content: '延展性好,易推开', priority: 'recommended' as const }, { id: 'sp4', content: '适合敏感肌', priority: 'recommended' as const }, { id: 'sp5', content: '夏日必备防晒', priority: 'core' as const }, { id: 'sp6', content: '产品成分天然', priority: 'reference' as const }, ], blacklistWords: [ { id: 'bw1', word: '最好', reason: '绝对化用语' }, { id: 'bw2', word: '第一', reason: '绝对化用语' }, { id: 'bw3', word: '神器', reason: '夸大宣传' }, { id: 'bw4', word: '完美', reason: '绝对化用语' }, ], } // Mock 任务数据 const mockTasksData: Record = { 'task-001': { id: 'task-001', title: 'XX品牌618推广', subtitle: '产品种草视频 · 当前步骤:上传脚本', phase: 'script', stage: 'upload' }, 'task-002': { id: 'task-002', title: 'YY美妆新品', subtitle: '口播测评 · 当前步骤:AI审核中', phase: 'script', stage: 'ai_reviewing', progress: 62, reviewLogs: [ { time: '14:32:01', message: '脚本上传完成', status: 'done' }, { time: '14:32:15', message: '任务规则已加载', status: 'done' }, { time: '14:33:45', message: '正在分析品牌调性匹配度...', status: 'loading' }, ]}, 'task-003': { id: 'task-003', title: 'ZZ饮品夏日', subtitle: '探店Vlog · 发现2处问题', phase: 'script', stage: 'ai_result', issues: [ { title: '检测到竞品提及', description: '脚本第3段提及了竞品「百事可乐」', severity: 'error' }, { title: '禁用词语出现', description: '脚本中出现「最好喝」等绝对化用语', severity: 'error' }, ]}, 'task-004': { id: 'task-004', title: 'AA数码新品发布', subtitle: '开箱测评 · 审核通过', phase: 'video', stage: 'brand_approved', submittedAt: '2026-02-01 10:30' }, 'task-008': { id: 'task-008', title: 'EE食品试吃', subtitle: '美食测评 · 脚本通过 · 待上传视频', phase: 'video', stage: 'upload' }, } // ========== UI 组件 ========== function StepIcon({ status, icon }: { status: 'done' | 'current' | 'error' | 'pending'; icon: 'upload' | 'bot' | 'users' | 'building' }) { const IconMap = { upload: Upload, bot: Bot, users: Users, building: Building2 } const Icon = IconMap[icon] const getStyle = () => { switch (status) { case 'done': return 'bg-accent-green' case 'current': return 'bg-accent-indigo' case 'error': return 'bg-accent-coral' default: return 'bg-bg-elevated border-[1.5px] border-border-subtle' } } const iconColor = status === 'pending' ? 'text-text-tertiary' : 'text-white' return (
{status === 'done' && } {status === 'current' && } {status === 'error' && } {status === 'pending' && }
) } function ReviewProgressBar({ task }: { task: TaskData }) { const { stage } = task const getStepStatus = (stepIndex: number): 'done' | 'current' | 'error' | 'pending' => { const stageMap: Record = { 'upload': 0, 'ai_reviewing': 1, 'ai_result': 1, 'agency_reviewing': 2, 'agency_rejected': 2, 'brand_reviewing': 3, 'brand_approved': 4, 'brand_rejected': 3, } const currentStepIndex = stageMap[stage] const isError = stage === 'ai_result' || stage === 'agency_rejected' || stage === 'brand_rejected' if (stepIndex < currentStepIndex) return 'done' if (stepIndex === currentStepIndex) { if (isError) return 'error' if (stage === 'brand_approved') return 'done' return 'current' } return 'pending' } const steps = [ { label: '已提交', icon: 'upload' as const }, { label: 'AI审核', icon: 'bot' as const }, { label: '代理商', icon: 'users' as const }, { label: '品牌方', icon: 'building' as const }, ] return (

{task.phase === 'script' ? '脚本审核流程' : '视频审核流程'}

{steps.map((step, index) => { const status = getStepStatus(index) return (
{step.label}
{index < steps.length - 1 && (
)}
) })}
) } // Brief 组件 function AgencyBriefSection({ toast, briefData }: { toast: ReturnType briefData: { files: AgencyBriefFile[]; sellingPoints: { id: string; content: string; priority: 'core' | 'recommended' | 'reference' }[]; blacklistWords: { id: string; word: string; reason: string }[] } }) { const [isExpanded, setIsExpanded] = useState(true) const [previewFile, setPreviewFile] = useState(null) const handleDownload = (file: AgencyBriefFile) => { toast.info(`下载文件: ${file.name}`) } const corePoints = briefData.sellingPoints.filter(sp => sp.priority === 'core') const recommendedPoints = briefData.sellingPoints.filter(sp => sp.priority === 'recommended') const referencePoints = briefData.sellingPoints.filter(sp => sp.priority === 'reference') return ( <>
Brief 文档与要求
{isExpanded && (

参考文档

{briefData.files.map((file) => (

{file.name}

{file.size}

))}

卖点要求

{corePoints.length > 0 && (

核心卖点(建议优先提及)

{corePoints.map((sp) => ( {sp.content} ))}
)} {recommendedPoints.length > 0 && (

推荐卖点(建议提及)

{recommendedPoints.map((sp) => ( {sp.content} ))}
)} {referencePoints.length > 0 && (

参考信息

{referencePoints.map((sp) => ( {sp.content} ))}
)}

违禁词(请勿使用)

{briefData.blacklistWords.map((bw) => ( 「{bw.word}」 ))}
)}
setPreviewFile(null)} title={previewFile?.name || '文件预览'} size="lg">

文件预览区域

{previewFile && }
) } function FileUploadSection({ taskId, phase, onUploaded }: { taskId: string; phase: 'script' | 'video'; onUploaded: () => void }) { const [file, setFile] = useState(null) const [isUploading, setIsUploading] = useState(false) const [progress, setProgress] = useState(0) const [uploadError, setUploadError] = useState(null) const toast = useToast() const isScript = phase === 'script' const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0] if (selectedFile) { setFile(selectedFile); setUploadError(null) } } const handleSubmit = async () => { if (!file) return setIsUploading(true); setProgress(0); setUploadError(null) try { if (USE_MOCK) { for (let i = 0; i <= 100; i += 20) { await new Promise(r => setTimeout(r, 400)); setProgress(i) } toast.success(isScript ? '脚本已提交,等待 AI 审核' : '视频已提交,等待 AI 审核') onUploaded() } else { const result = await api.proxyUpload(file, isScript ? 'script' : 'video', (pct) => { setProgress(Math.min(90, Math.round(pct * 0.9))) }) setProgress(95) if (isScript) { await api.uploadTaskScript(taskId, { file_url: result.url, file_name: result.file_name }) } else { await api.uploadTaskVideo(taskId, { file_url: result.url, file_name: result.file_name }) } setProgress(100) toast.success(isScript ? '脚本已提交,等待 AI 审核' : '视频已提交,等待 AI 审核') onUploaded() } } catch (err) { const msg = err instanceof Error ? err.message : '上传失败' setUploadError(msg); toast.error(msg) } finally { setIsUploading(false) } } const formatSize = (bytes: number) => { if (bytes < 1024) return bytes + 'B' if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB' return (bytes / (1024 * 1024)).toFixed(1) + 'MB' } const acceptTypes = isScript ? '.doc,.docx,.pdf,.txt,.xls,.xlsx' : '.mp4,.mov,.avi,.mkv' const acceptHint = isScript ? '支持 Word、PDF、TXT、Excel 格式' : '支持 MP4/MOV 格式,≤ 100MB' return (
{isScript ? '上传脚本' : '上传视频'} 待提交
{!file ? ( ) : (
已选文件
{isUploading ? ( ) : uploadError ? ( ) : ( )} {file.name} {formatSize(file.size)} {!isUploading && ( )}
{isUploading && ( <>

上传中 {progress}%

)} {uploadError &&

{uploadError}

}
)}
) } function getDimensionLabel(key: string) { const labels: Record = { legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' } return labels[key] || key } function AIResultDetailSection({ task }: { task: TaskData }) { if (!task.aiResult) return null const { dimensions, sellingPointMatches, briefMatchDetail, violations } = task.aiResult return (
AI 审核结果
= 85 ? 'text-accent-green' : task.aiResult.score >= 70 ? 'text-yellow-400' : 'text-accent-coral')}> {task.aiResult.score}分
{dimensions && (
{(['legal', 'platform', 'brand_safety', 'brief_match'] as const).map(key => { const dim = dimensions[key] if (!dim) return null return (
{getDimensionLabel(key)} {dim.passed ? : }
= 85 ? 'text-accent-green' : 'text-yellow-400') : 'text-accent-coral')}>{dim.score} {dim.issue_count > 0 && ({dim.issue_count} 项问题)}
) })}
)} {violations.length > 0 && (

违规检测 ({violations.length})

{violations.map((v, idx) => (
{v.type} {v.dimension && {getDimensionLabel(v.dimension)}}

「{v.content}」

{v.suggestion}

))}
)} {/* Brief 匹配度详情 */} {briefMatchDetail && (

Brief 匹配度分析

{/* 评分说明 */}

{briefMatchDetail.explanation}

{/* 覆盖率进度条 */} {briefMatchDetail.total_points > 0 && (
卖点覆盖率 {briefMatchDetail.matched_points}/{briefMatchDetail.required_points > 0 ? briefMatchDetail.required_points : briefMatchDetail.total_points} 条
= 80 ? 'bg-accent-green' : briefMatchDetail.coverage_score >= 50 ? 'bg-accent-amber' : 'bg-accent-coral')} style={{ width: `${briefMatchDetail.coverage_score}%` }} />
)} {/* 亮点 */} {briefMatchDetail.highlights.length > 0 && (

亮点

{briefMatchDetail.highlights.map((h, i) => (
{h}
))}
)} {/* 问题点 */} {briefMatchDetail.issues.length > 0 && (

可改进

{briefMatchDetail.issues.map((issue, i) => (
{issue}
))}
)}
)} {/* 卖点匹配列表 */} {sellingPointMatches && sellingPointMatches.length > 0 && (

卖点匹配详情

{sellingPointMatches.map((sp, idx) => (
{sp.matched ? : }
{sp.content} {sp.priority === 'core' ? '核心' : sp.priority === 'recommended' ? '推荐' : '参考'}
{sp.evidence &&

{sp.evidence}

}
))}
)}
) } function ReviewFeedbackCard({ review, type }: { review: { result: string; comment: string; reviewer: string; time: string }; type: 'agency' | 'brand' }) { const isApproved = review.result === 'approved' const title = type === 'agency' ? '代理商审核意见' : '品牌方终审意见' return (
{isApproved ? : } {title}
{review.reviewer} {isApproved ? '通过' : '驳回'}
{review.comment &&

{review.comment}

}

{review.time}

) } function UploadView({ task, toast, briefData, onUploaded }: { task: TaskData; toast: ReturnType; briefData: typeof mockBriefData; onUploaded: () => void }) { const isScript = task.phase === 'script' return (
{isScript && }
) } function AIReviewingView({ task }: { task: TaskData }) { return (
{task.phase === 'script' ? '脚本内容审核' : '视频内容审核'} · 智能分析中

AI 正在审核您的{task.phase === 'script' ? '脚本' : '视频'}

预计还需 2-3 分钟,可先离开页面

{task.progress || 0}%
{task.reviewLogs && task.reviewLogs.length > 0 && (
处理日志
{task.reviewLogs.map((log, index) => (
{log.time} {log.message} {log.status === 'loading' && }
))}
)}
) } function RejectionView({ task, onAppeal, onReupload }: { task: TaskData; onAppeal: () => void; onReupload: () => void }) { const getTitle = () => { switch (task.stage) { case 'ai_result': return 'AI 审核结果' case 'agency_rejected': return '代理商审核驳回' case 'brand_rejected': return '品牌方审核驳回' default: return '审核结果' } } const getStatusText = () => { switch (task.stage) { case 'ai_result': return 'AI 检测到问题,请修改后重新上传' case 'agency_rejected': return '代理商审核驳回,请根据意见修改' case 'brand_rejected': return '品牌方审核驳回,请根据意见修改' default: return '需要修改' } } return (
{getTitle()} {getStatusText()}
{task.rejectionReason && (

{task.rejectionReason}

)}
{task.stage === 'agency_rejected' && task.agencyReview && } {task.stage === 'brand_rejected' && task.brandReview && } {task.stage === 'brand_rejected' && task.agencyReview && }
) } function WaitingReviewView({ task }: { task: TaskData }) { const isAgency = task.stage === 'agency_reviewing' const title = isAgency ? '等待代理商审核' : '等待品牌方终审' const description = isAgency ? '您的内容已进入代理商审核环节,请耐心等待' : '您的内容已进入品牌方终审环节,这是最后一步' return (
{title} {description}
提交时间 {task.submittedAt || '刚刚'}
AI审核 已通过
{isAgency && (
代理商审核 审核中...
)} {!isAgency && ( <>
代理商审核 已通过
品牌方终审 审核中...
)}
{!isAgency && task.agencyReview && }
) } function ApprovedView({ task }: { task: TaskData }) { const isVideoPhase = task.phase === 'video' return (
{isVideoPhase ? '全部审核通过' : '脚本审核通过'} {isVideoPhase ? '可以安排发布了' : '请在 7 天内上传视频'}
{isVideoPhase ? '恭喜完成!' : '下一步'} {isVideoPhase ? '您的视频已通过全部审核流程,可以在平台发布了。' : '脚本已通过审核,请在 7 天内上传对应视频。'}
{task.brandReview && } {task.agencyReview && } {!isVideoPhase && (
)}
) } // ========== 主页面 ========== export default function TaskDetailPage() { const params = useParams() const router = useRouter() const toast = useToast() const { subscribe } = useSSE() const taskId = params.id as string const [taskData, setTaskData] = useState(null) const [briefData, setBriefData] = useState(mockBriefData) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [showReupload, setShowReupload] = useState(false) const loadTask = useCallback(async () => { if (USE_MOCK) { const mock = mockTasksData[taskId] setTaskData(mock || null) setIsLoading(false) return } try { const task = await api.getTask(taskId) setTaskData(mapApiTaskToTaskData(task)) // 加载 Brief if (task.project?.id) { try { const brief = await api.getBrief(task.project.id) setBriefData({ files: (brief.attachments || []).map((a, i) => ({ id: a.id || `att-${i}`, name: a.name, size: a.size || '', uploadedAt: brief.updated_at || '', })), sellingPoints: (brief.selling_points || []).map((sp, i) => ({ id: `sp-${i}`, content: sp.content, priority: (sp.priority || (sp.required ? 'core' : 'recommended')) as 'core' | 'recommended' | 'reference', })), blacklistWords: (brief.blacklist_words || []).map((bw, i) => ({ id: `bw-${i}`, word: bw.word, reason: bw.reason, })), }) } catch { // Brief 可能不存在,不影响任务展示 } } } catch (err) { setError(err instanceof Error ? err.message : '加载失败') } finally { setIsLoading(false) } }, [taskId]) useEffect(() => { loadTask() }, [loadTask]) // SSE 实时更新 useEffect(() => { const unsub1 = subscribe('task_updated', (data) => { if ((data as { task_id?: string }).task_id === taskId) loadTask() }) const unsub2 = subscribe('review_completed', (data) => { if ((data as { task_id?: string }).task_id === taskId) loadTask() }) return () => { unsub1(); unsub2() } }, [subscribe, taskId, loadTask]) // AI 审核中时轮询(SSE 后备方案) useEffect(() => { if (!taskData || (taskData.stage !== 'ai_reviewing') || USE_MOCK) return const interval = setInterval(() => { loadTask() }, 5000) return () => clearInterval(interval) // eslint-disable-next-line react-hooks/exhaustive-deps }, [taskData?.stage, loadTask]) if (isLoading) { return (
) } if (error || !taskData) { return (

{error || '任务不存在'}

) } const handleAppeal = () => { router.push(`/creator/appeals/new?taskId=${taskId}`) } const renderContent = () => { // 驳回状态下选择重新上传时,显示上传界面 if (showReupload && (taskData.stage === 'ai_result' || taskData.stage === 'agency_rejected' || taskData.stage === 'brand_rejected')) { return (
{taskData.phase === 'script' && } { setShowReupload(false); loadTask() }} />
) } switch (taskData.stage) { case 'upload': return case 'ai_reviewing': return case 'ai_result': case 'agency_rejected': case 'brand_rejected': return setShowReupload(true)} /> case 'agency_reviewing': case 'brand_reviewing': return case 'brand_approved': return default: return
未知状态
} } const getPageTitle = () => { switch (taskData.stage) { case 'upload': return taskData.phase === 'script' ? '上传脚本' : '上传视频' case 'ai_reviewing': return 'AI 智能审核' case 'ai_result': return 'AI 审核结果' case 'agency_reviewing': return '等待代理商审核' case 'agency_rejected': return '代理商审核驳回' case 'brand_reviewing': return '等待品牌方终审' case 'brand_approved': return '审核通过' case 'brand_rejected': return '品牌方审核驳回' default: return '任务详情' } } return (

{taskData.title}

{taskData.subtitle}

{renderContent()}
) }