'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter, useParams } from 'next/navigation' import { useToast } from '@/components/ui/Toast' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { Modal, ConfirmModal } from '@/components/ui/Modal' import { SuccessTag, WarningTag, ErrorTag } from '@/components/ui/Tag' import { ReviewSteps, getAgencyReviewSteps } from '@/components/ui/ReviewSteps' import { ArrowLeft, FileText, CheckCircle, XCircle, AlertTriangle, User, Clock, Eye, Shield, Download, MessageSquareWarning, Loader2 } from 'lucide-react' import { FilePreview, FileInfoCard, FilePreviewModal, type FileInfo } from '@/components/ui/FilePreview' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import { getPlatformInfo } from '@/lib/platforms' import type { TaskResponse } from '@/types/task' // 模拟脚本任务数据 const mockScriptTask = { id: 'script-001', title: '夏日护肤推广脚本', creatorName: '小美护肤', projectName: 'XX品牌618推广', submittedAt: '2026-02-06 14:30', aiScore: 88, status: 'agent_reviewing', // 文件信息 file: { id: 'file-001', fileName: '夏日护肤推广_脚本v2.docx', fileSize: '245 KB', fileType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', fileUrl: '/demo/scripts/script-001.docx', // 实际开发时替换为真实URL uploadedAt: '2026-02-06 14:30', } as FileInfo, // 申诉信息 isAppeal: false, appealReason: '', // 脚本内容(AI解析后的结构化内容,用于展示) scriptContent: { opening: '大家好!今天给大家分享一款超级好用的夏日护肤神器~', productIntro: '这款XX品牌的防晒霜,SPF50+,PA++++,真的是夏天出门必备!质地轻薄不油腻,涂上去清清爽爽的。', demo: '我先在手背上试一下,大家看,延展性特别好,轻轻一抹就推开了,完全不会搓泥。', closing: '夏天防晒真的很重要,姐妹们赶紧冲!链接在小黄车1号链接~', }, aiAnalysis: { violations: [ { id: 'v1', type: '违禁词', content: '神器', suggestion: '建议替换为"好物"或"必备品"', severity: 'medium' }, ], complianceChecks: [ { item: '品牌名称正确', passed: true }, { item: 'SPF标注准确', passed: true }, { item: '无绝对化用语', passed: false, note: '"超级好用"建议修改' }, { item: '引导语规范', passed: true }, ], sellingPoints: [ { point: 'SPF50+ PA++++', covered: true }, { point: '轻薄质地', covered: true }, { point: '不油腻', covered: true }, { point: '延展性好', covered: true }, ], }, } // 从 TaskResponse 映射到页面视图模型 function mapTaskToViewModel(task: TaskResponse) { return { id: task.id, title: task.name, creatorName: task.creator?.name || '未知达人', projectName: task.project?.name || '未知项目', brandName: task.project?.brand_name || '', platform: task.project?.platform || '', submittedAt: task.script_uploaded_at || task.created_at, aiScore: task.script_ai_score ?? 0, status: task.stage, file: { id: `file-${task.id}`, fileName: task.script_file_name || '未知文件', fileSize: '', fileType: 'application/octet-stream', fileUrl: task.script_file_url || '', uploadedAt: task.script_uploaded_at || task.created_at, } as FileInfo, isAppeal: task.is_appeal, appealReason: task.appeal_reason || '', scriptContent: { opening: '', productIntro: '', demo: '', closing: '', }, aiAnalysis: { violations: (task.script_ai_result?.violations || []).map((v, idx) => ({ id: `v${idx + 1}`, type: v.type, content: v.content, suggestion: v.suggestion, severity: v.severity, dimension: v.dimension, })), complianceChecks: (task.script_ai_result?.soft_warnings || []).map((w: any) => { const codeLabels: Record = { missing_selling_points: '卖点缺失', tone_mismatch: '语气不符', length_warning: '时长提示', style_warning: '风格提示', sensitive_topic: '敏感话题', audience_mismatch: '受众偏差', } const rawLabel = w.type || w.code || '提示' return { item: codeLabels[rawLabel] || rawLabel, passed: false, note: w.content || w.message || '', } }), dimensions: task.script_ai_result?.dimensions, sellingPointMatches: task.script_ai_result?.selling_point_matches || [], sellingPoints: [] as Array<{ point: string; covered: boolean }>, }, aiSummary: task.script_ai_result?.summary || '', } } type ScriptTaskViewModel = ReturnType function ReviewProgressBar({ taskStatus }: { taskStatus: string }) { const steps = getAgencyReviewSteps(taskStatus) const currentStep = steps.find(s => s.status === 'current') return (
审核流程 当前:{currentStep?.label || '代理商审核'}
) } function LoadingSkeleton() { return (
) } export default function AgencyScriptReviewPage() { const router = useRouter() const toast = useToast() const params = useParams() const taskId = params.id as string const [loading, setLoading] = useState(!USE_MOCK) const [submitting, setSubmitting] = useState(false) const [showApproveModal, setShowApproveModal] = useState(false) const [showRejectModal, setShowRejectModal] = useState(false) const [showForcePassModal, setShowForcePassModal] = useState(false) const [rejectReason, setRejectReason] = useState('') const [forcePassReason, setForcePassReason] = useState('') const [viewMode, setViewMode] = useState<'file' | 'parsed'>('file') // 'file' 显示原文件, 'parsed' 显示解析内容 const [showFilePreview, setShowFilePreview] = useState(false) const [task, setTask] = useState(mockScriptTask as unknown as ScriptTaskViewModel) const loadTask = useCallback(async () => { if (USE_MOCK) return setLoading(true) try { const data = await api.getTask(taskId) setTask(mapTaskToViewModel(data)) } catch (err: unknown) { const message = err instanceof Error ? err.message : '加载任务详情失败' toast.error(message) } finally { setLoading(false) } }, [taskId, toast]) useEffect(() => { loadTask() }, [loadTask]) const handleApprove = async () => { if (USE_MOCK) { setShowApproveModal(false) toast.success('已提交品牌方终审') router.push('/agency/review') return } setSubmitting(true) try { await api.reviewScript(taskId, { action: 'pass' }) setShowApproveModal(false) toast.success('已提交品牌方终审') router.push('/agency/review') } catch (err: unknown) { const message = err instanceof Error ? err.message : '操作失败' toast.error(message) } finally { setSubmitting(false) } } const handleReject = async () => { if (!rejectReason.trim()) { toast.error('请填写驳回原因') return } if (USE_MOCK) { setShowRejectModal(false) toast.success('已驳回') router.push('/agency/review') return } setSubmitting(true) try { await api.reviewScript(taskId, { action: 'reject', comment: rejectReason }) setShowRejectModal(false) toast.success('已驳回') router.push('/agency/review') } catch (err: unknown) { const message = err instanceof Error ? err.message : '操作失败' toast.error(message) } finally { setSubmitting(false) } } const handleForcePass = async () => { if (!forcePassReason.trim()) { toast.error('请填写强制通过原因') return } if (USE_MOCK) { setShowForcePassModal(false) toast.success('已强制通过并提交品牌方终审') router.push('/agency/review') return } setSubmitting(true) try { await api.reviewScript(taskId, { action: 'force_pass', comment: forcePassReason }) setShowForcePassModal(false) toast.success('已强制通过并提交品牌方终审') router.push('/agency/review') } catch (err: unknown) { const message = err instanceof Error ? err.message : '操作失败' toast.error(message) } finally { setSubmitting(false) } } if (loading) { return } return (
{/* 顶部导航 */}

{task.title}

{task.isAppeal && ( 申诉 )}
{task.creatorName} {task.brandName && {task.brandName}} {task.platform && {getPlatformInfo(task.platform)?.name || task.platform}} {task.submittedAt}
{/* 申诉理由 */} {task.isAppeal && task.appealReason && (

申诉理由

{task.appealReason}

)} {/* 审核流程进度条 */}
{/* 左侧:脚本内容 */}
{/* 文件信息卡片 */} setShowFilePreview(true)} /> {viewMode === 'file' ? ( 文件预览 ) : ( AI 审核分析 {task.aiSummary ? (
AI 总结

{task.aiSummary}

) : (

暂无 AI 分析总结

)} {task.aiAnalysis.violations.length > 0 && (
发现问题 ({task.aiAnalysis.violations.length})
{task.aiAnalysis.violations.map((v) => (
[{v.type}] {v.content}

{v.suggestion}

))}
)} {task.aiAnalysis.sellingPointMatches.length > 0 && (
卖点匹配概览
{task.aiAnalysis.sellingPointMatches.map((sp: { content: string; priority: string; matched: boolean; evidence?: string }, idx: number) => (
{sp.matched ? : } {sp.content} {sp.priority === 'core' ? '核心' : sp.priority === 'recommended' ? '推荐' : '参考'}
))}
)}
)}
{/* 右侧:AI 分析面板 */}
{/* AI 评分 */}
AI 综合评分 = 85 ? 'text-accent-green' : task.aiScore >= 70 ? 'text-yellow-400' : 'text-accent-coral'}`}> {task.aiScore}
{/* 维度评分 */} {task.aiAnalysis.dimensions && ( 维度评分 {(['legal', 'platform', 'brand_safety', 'brief_match'] as const).map(key => { const dim = (task.aiAnalysis.dimensions as unknown as Record)?.[key] if (!dim) return null const label = { legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' }[key] return (
{label}
{dim.score} {dim.passed ? : }
) })}
)} {/* 违规检测 */} 违规检测 ({task.aiAnalysis.violations.length}) {task.aiAnalysis.violations.map((v) => (
{v.type} {v.dimension && {{ legal: '法规合规', platform: '平台规则', brand_safety: '品牌安全', brief_match: 'Brief 匹配' }[v.dimension as string]}}

{v.content}

{v.suggestion}

))} {task.aiAnalysis.violations.length === 0 && (

未发现违规内容

)}
{/* 舆情提示 */} {task.aiAnalysis.complianceChecks.length > 0 && ( 舆情提示(仅参考) {task.aiAnalysis.complianceChecks.map((check, idx) => (
{check.item}
{check.note && (

{check.note}

)}

软性风险仅作提示,不影响审核结果

))}
)} {/* 卖点匹配 */} 卖点匹配 {task.aiAnalysis.sellingPointMatches && task.aiAnalysis.sellingPointMatches.length > 0 ? ( task.aiAnalysis.sellingPointMatches.map((sp: { content: string; priority: string; matched: boolean; evidence?: string }, idx: number) => (
{sp.matched ? : }
{sp.content} {sp.priority === 'core' ? '核心' : sp.priority === 'recommended' ? '推荐' : '参考'}
{sp.evidence &&

{sp.evidence}

}
)) ) : (

暂无卖点数据

)}
{/* 底部决策栏 */}
{task.brandName && {task.brandName} · } {task.projectName} {task.platform && · {getPlatformInfo(task.platform)?.name || task.platform}}
{/* 通过确认弹窗 */} setShowApproveModal(false)} onConfirm={handleApprove} title="确认通过" message="确定要通过此脚本的审核吗?通过后将提交给品牌方进行终审。" confirmText="确认通过" /> {/* 驳回弹窗 */} setShowRejectModal(false)} title="驳回审核">

请填写驳回原因,达人将收到通知并根据您的反馈进行修改。