'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 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 || '未知项目', 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, })), complianceChecks: (task.script_ai_result?.soft_warnings || []).map((w) => ({ item: w.type, passed: false, note: w.content, })), 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.submittedAt}
{/* 申诉理由 */} {task.isAppeal && task.appealReason && (

申诉理由

{task.appealReason}

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

{task.aiSummary}

) : null}
开场白

{task.scriptContent.opening || '(无内容)'}

产品介绍

{task.scriptContent.productIntro || '(无内容)'}

使用演示

{task.scriptContent.demo || '(无内容)'}

结尾引导

{task.scriptContent.closing || '(无内容)'}

)}
{/* 右侧:AI 分析面板 */}
{/* AI 评分 */}
AI 综合评分 = 85 ? 'text-accent-green' : task.aiScore >= 70 ? 'text-yellow-400' : 'text-accent-coral'}`}> {task.aiScore}
{/* 违规检测 */} 违规检测 ({task.aiAnalysis.violations.length}) {task.aiAnalysis.violations.map((v) => (
{v.type}

{v.content}

{v.suggestion}

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

未发现违规内容

)}
{/* 合规检查 */} 合规检查 {task.aiAnalysis.complianceChecks.map((check, idx) => (
{check.passed ? ( ) : ( )}
{check.item} {check.note && (

{check.note}

)}
))}
{/* 卖点覆盖 */} 卖点覆盖 {task.aiAnalysis.sellingPoints.map((sp, idx) => (
{sp.covered ? ( ) : ( )} {sp.point}
))} {task.aiAnalysis.sellingPoints.length === 0 && (

暂无卖点数据

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

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