- {isLoading && (
-
- 正在加载任务...
+ {/* 任务列表 - 可滚动 */}
+
+ {filteredTasks.length === 0 ? (
+
+
+
没有找到匹配的任务
+
尝试调整搜索条件或筛选状态
+ ) : (
+ filteredTasks.map((task) => (
+
handleTaskClick(task.id)}
+ />
+ ))
)}
- {!isLoading && tasks.length === 0 && (
-
- 暂无任务
-
- )}
- {tasks.map((task) => (
- handleTaskClick(task.id)}
- />
- ))}
-
+
)
-
- // 移动端内容
- const MobileContent = (
-
-
- {/* 头部 */}
-
-
我的任务
-
共 {pendingCount} 个进行中任务
-
-
- {/* 任务列表 */}
-
- {isLoading && (
-
- 正在加载任务...
-
- )}
- {!isLoading && tasks.length === 0 && (
-
- 暂无任务
-
- )}
- {tasks.map((task) => (
-
handleTaskClick(task.id)}
- />
- ))}
-
-
-
- )
-
- return isMobile ? MobileContent : DesktopContent
}
diff --git a/frontend/app/creator/profile/edit/page.tsx b/frontend/app/creator/profile/edit/page.tsx
new file mode 100644
index 0000000..b3b2b8d
--- /dev/null
+++ b/frontend/app/creator/profile/edit/page.tsx
@@ -0,0 +1,249 @@
+'use client'
+
+import React, { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import { ArrowLeft, Camera, Check, Copy } from 'lucide-react'
+import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
+import { Button } from '@/components/ui/Button'
+import { Input } from '@/components/ui/Input'
+import { cn } from '@/lib/utils'
+
+// 模拟用户数据
+const mockUser = {
+ name: '李小红',
+ initial: '李',
+ creatorId: 'CR123456', // 达人ID(系统生成,不可修改)
+ phone: '138****8888',
+ email: 'lixiaohong@example.com',
+ douyinAccount: '@xiaohong_creator',
+ bio: '专注美妆护肤分享,与你一起变美~',
+}
+
+export default function ProfileEditPage() {
+ const router = useRouter()
+ const [isSaving, setIsSaving] = useState(false)
+ const [idCopied, setIdCopied] = useState(false)
+ const [formData, setFormData] = useState({
+ name: mockUser.name,
+ phone: mockUser.phone,
+ email: mockUser.email,
+ douyinAccount: mockUser.douyinAccount,
+ bio: mockUser.bio,
+ })
+
+ // 复制达人ID
+ const handleCopyId = async () => {
+ try {
+ await navigator.clipboard.writeText(mockUser.creatorId)
+ setIdCopied(true)
+ setTimeout(() => setIdCopied(false), 2000)
+ } catch (err) {
+ console.error('复制失败:', err)
+ }
+ }
+
+ // 处理输入变化
+ const handleInputChange = (field: string, value: string) => {
+ setFormData(prev => ({ ...prev, [field]: value }))
+ }
+
+ // 保存
+ const handleSave = async () => {
+ setIsSaving(true)
+ // 模拟保存
+ await new Promise(resolve => setTimeout(resolve, 1000))
+ setIsSaving(false)
+ router.back()
+ }
+
+ return (
+
+
+ {/* 顶部栏 */}
+
+
+
+
+
+ {/* 内容区 */}
+
+ {/* 头像编辑卡片 */}
+
+
+ {/* 头像 */}
+
+
+ {mockUser.initial}
+
+ {/* 相机按钮 */}
+
+
+
点击更换头像
+
+ {/* 提示 */}
+
+
+ 支持 JPG、PNG 格式,文件大小不超过 5MB,建议使用正方形图片以获得最佳显示效果。
+
+
+
+
+
+ {/* 表单卡片 */}
+
+
+ {/* 达人ID(只读) */}
+
+
+
+
+ {mockUser.creatorId}
+
+
+
+
系统自动生成的唯一标识,供代理商邀请时使用
+
+
+ {/* 昵称 */}
+
+
+ handleInputChange('name', e.target.value)}
+ placeholder="请输入昵称"
+ />
+
+
+ {/* 手机号 */}
+
+
+
+ handleInputChange('phone', e.target.value)}
+ placeholder="请输入手机号"
+ className="flex-1"
+ disabled
+ />
+
+
+
手机号用于登录和接收重要通知
+
+
+ {/* 邮箱 */}
+
+
+ handleInputChange('email', e.target.value)}
+ placeholder="请输入邮箱"
+ />
+
+
+ {/* 抖音账号 */}
+
+
+
+ handleInputChange('douyinAccount', e.target.value)}
+ placeholder="请输入抖音账号"
+ className="flex-1"
+ disabled
+ />
+
+
+
已认证的抖音账号,用于发布视频
+
+
+ {/* 个人简介 */}
+
+
+
+ {/* 保存按钮 */}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/app/creator/profile/page.tsx b/frontend/app/creator/profile/page.tsx
new file mode 100644
index 0000000..1e36b55
--- /dev/null
+++ b/frontend/app/creator/profile/page.tsx
@@ -0,0 +1,275 @@
+'use client'
+
+import React, { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import {
+ CircleUser,
+ Settings,
+ BellRing,
+ History,
+ MessageCircleQuestion,
+ PlusCircle,
+ ChevronRight,
+ LogOut,
+ Copy,
+ Check
+} from 'lucide-react'
+import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
+import { cn } from '@/lib/utils'
+
+// 用户数据
+const mockUser = {
+ name: '李小红',
+ initial: '李',
+ creatorId: 'CR123456', // 达人ID
+ role: '抖音达人 · 已认证',
+ stats: {
+ completed: 28,
+ passRate: 92,
+ inProgress: 3,
+ },
+}
+
+// 菜单项数据
+const menuItems = [
+ {
+ id: 'personal',
+ icon: CircleUser,
+ iconColor: 'text-accent-indigo',
+ bgColor: 'bg-accent-indigo',
+ title: '个人信息',
+ subtitle: '头像、昵称、绑定账号',
+ },
+ {
+ id: 'account',
+ icon: Settings,
+ iconColor: 'text-accent-green',
+ bgColor: 'bg-accent-green',
+ title: '账户设置',
+ subtitle: '修改密码、账号安全',
+ },
+ {
+ id: 'notification',
+ icon: BellRing,
+ iconColor: 'text-accent-blue',
+ bgColor: 'bg-accent-blue',
+ title: '消息设置',
+ subtitle: '通知开关、提醒偏好',
+ },
+ {
+ id: 'history',
+ icon: History,
+ iconColor: 'text-accent-coral',
+ bgColor: 'bg-accent-coral',
+ title: '历史记录',
+ subtitle: '已完成和过期的任务',
+ },
+ {
+ id: 'help',
+ icon: MessageCircleQuestion,
+ iconColor: 'text-text-secondary',
+ bgColor: 'bg-bg-elevated',
+ title: '帮助与反馈',
+ subtitle: '常见问题、联系客服',
+ },
+ {
+ id: 'appeal',
+ icon: PlusCircle,
+ iconColor: 'text-accent-indigo',
+ bgColor: 'bg-accent-indigo',
+ title: '申诉次数',
+ subtitle: '查看各任务申诉次数 · 申请增加',
+ },
+]
+
+// 用户卡片组件
+function UserCard() {
+ const [copied, setCopied] = useState(false)
+
+ // 复制达人ID
+ const handleCopyId = async () => {
+ try {
+ await navigator.clipboard.writeText(mockUser.creatorId)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch (err) {
+ console.error('复制失败:', err)
+ }
+ }
+
+ return (
+
+ {/* 头像和信息 */}
+
+ {/* 头像 */}
+
+ {mockUser.initial}
+
+ {/* 用户信息 */}
+
+
{mockUser.name}
+
{mockUser.role}
+ {/* 达人ID */}
+
+
达人ID:
+
+ {mockUser.creatorId}
+
+
+ {copied && (
+
已复制
+ )}
+
+
+
+
+ {/* 统计数据 */}
+
+
+ {mockUser.stats.completed}
+ 完成任务
+
+
+ {mockUser.stats.passRate}%
+ 通过率
+
+
+ {mockUser.stats.inProgress}
+ 进行中
+
+
+
+ )
+}
+
+// 菜单项组件
+function MenuItem({ item, onClick }: { item: typeof menuItems[0]; onClick: () => void }) {
+ const Icon = item.icon
+ const isPlainBg = item.bgColor === 'bg-bg-elevated'
+
+ return (
+
+ )
+}
+
+// 菜单卡片组件
+function MenuCard({ onMenuClick }: { onMenuClick: (id: string) => void }) {
+ return (
+
+ {menuItems.map((item, index) => (
+
+
+ ))}
+
+ )
+}
+
+// 退出卡片组件
+function LogoutCard({ onLogout }: { onLogout: () => void }) {
+ return (
+
+
+
+ )
+}
+
+export default function CreatorProfilePage() {
+ const router = useRouter()
+
+ // 菜单项点击处理
+ const handleMenuClick = (menuId: string) => {
+ const routes: Record
= {
+ personal: '/creator/profile/edit',
+ account: '/creator/settings/account',
+ notification: '/creator/settings/notification',
+ history: '/creator/history',
+ help: '/creator/help',
+ appeal: '/creator/appeal-quota',
+ }
+ const route = routes[menuId]
+ if (route) {
+ router.push(route)
+ }
+ }
+
+ // 退出登录
+ const handleLogout = () => {
+ // TODO: 实际退出逻辑
+ router.push('/login')
+ }
+
+ return (
+
+
+ {/* 顶部栏 */}
+
+
+ {/* 内容区 - 响应式布局 */}
+
+ {/* 用户卡片 */}
+
+
+
+
+ {/* 菜单和退出 */}
+
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/app/creator/settings/account/page.tsx b/frontend/app/creator/settings/account/page.tsx
new file mode 100644
index 0000000..38713e6
--- /dev/null
+++ b/frontend/app/creator/settings/account/page.tsx
@@ -0,0 +1,383 @@
+'use client'
+
+import React, { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import {
+ ArrowLeft,
+ Key,
+ ShieldCheck,
+ Smartphone,
+ ChevronRight,
+ Check,
+ X,
+ Eye,
+ EyeOff,
+} from 'lucide-react'
+import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
+import { Button } from '@/components/ui/Button'
+import { Input } from '@/components/ui/Input'
+import { Modal } from '@/components/ui/Modal'
+import { cn } from '@/lib/utils'
+
+// 模拟登录设备数据
+const mockDevices = [
+ {
+ id: '1',
+ name: 'iPhone 15 Pro',
+ type: 'mobile',
+ location: '北京',
+ lastActive: '当前设备',
+ isCurrent: true,
+ },
+ {
+ id: '2',
+ name: 'MacBook Pro',
+ type: 'desktop',
+ location: '北京',
+ lastActive: '2小时前',
+ isCurrent: false,
+ },
+ {
+ id: '3',
+ name: 'iPad Air',
+ type: 'tablet',
+ location: '上海',
+ lastActive: '3天前',
+ isCurrent: false,
+ },
+]
+
+// 设置项组件
+function SettingItem({
+ icon: Icon,
+ iconColor,
+ title,
+ description,
+ badge,
+ onClick,
+ showArrow = true,
+}: {
+ icon: React.ElementType
+ iconColor: string
+ title: string
+ description: string
+ badge?: React.ReactNode
+ onClick?: () => void
+ showArrow?: boolean
+}) {
+ return (
+
+ )
+}
+
+// 修改密码弹窗
+function ChangePasswordModal({
+ isOpen,
+ onClose,
+}: {
+ isOpen: boolean
+ onClose: () => void
+}) {
+ const [step, setStep] = useState(1)
+ const [showPasswords, setShowPasswords] = useState({
+ current: false,
+ new: false,
+ confirm: false,
+ })
+ const [formData, setFormData] = useState({
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ })
+ const [isSaving, setIsSaving] = useState(false)
+
+ const handleSubmit = async () => {
+ setIsSaving(true)
+ await new Promise(resolve => setTimeout(resolve, 1500))
+ setIsSaving(false)
+ setStep(2)
+ }
+
+ const handleClose = () => {
+ setStep(1)
+ setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
+ onClose()
+ }
+
+ return (
+
+ {step === 1 ? (
+
+ {/* 当前密码 */}
+
+
+
+ setFormData(prev => ({ ...prev, currentPassword: e.target.value }))}
+ placeholder="请输入当前密码"
+ />
+
+
+
+
+ {/* 新密码 */}
+
+
+
+ setFormData(prev => ({ ...prev, newPassword: e.target.value }))}
+ placeholder="请输入新密码(8-20位)"
+ />
+
+
+
密码需包含字母和数字,长度8-20位
+
+
+ {/* 确认密码 */}
+
+
+
+ setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))}
+ placeholder="请再次输入新密码"
+ />
+
+
+
+
+ {/* 按钮 */}
+
+
+
+
+
+ ) : (
+
+ )}
+
+ )
+}
+
+// 设备管理弹窗
+function DeviceManageModal({
+ isOpen,
+ onClose,
+}: {
+ isOpen: boolean
+ onClose: () => void
+}) {
+ const [devices, setDevices] = useState(mockDevices)
+
+ const handleRemoveDevice = (deviceId: string) => {
+ setDevices(prev => prev.filter(d => d.id !== deviceId))
+ }
+
+ return (
+
+
+
管理已登录此账号的设备,可移除不再使用的设备以保障账户安全。
+
+
+ {devices.map((device) => (
+
+
+
+
+
+
+
+ {device.name}
+ {device.isCurrent && (
+
+ 当前
+
+ )}
+
+
+ {device.location} · {device.lastActive}
+
+
+
+ {!device.isCurrent && (
+
+ )}
+
+ ))}
+
+
+
+
+
+ )
+}
+
+export default function AccountSettingsPage() {
+ const router = useRouter()
+ const [showPasswordModal, setShowPasswordModal] = useState(false)
+ const [showDeviceModal, setShowDeviceModal] = useState(false)
+ const [twoFactorEnabled, setTwoFactorEnabled] = useState(true)
+
+ return (
+
+
+ {/* 顶部栏 */}
+
+
+
+
+
+ {/* 内容区 */}
+
+ {/* 账户安全 */}
+
+
账户安全
+
+ setShowPasswordModal(true)}
+ />
+
+ {twoFactorEnabled ? '已开启' : '未开启'}
+
+ }
+ onClick={() => setTwoFactorEnabled(!twoFactorEnabled)}
+ />
+ setShowDeviceModal(true)}
+ />
+
+
+
+ {/* 危险区域 */}
+
+
危险区域
+
+
+
+ 注销账户
+
+ 永久删除您的账户和所有相关数据,此操作不可撤销
+
+
+
+
+
+
+
+
+
+ {/* 弹窗 */}
+ setShowPasswordModal(false)} />
+ setShowDeviceModal(false)} />
+
+ )
+}
diff --git a/frontend/app/creator/settings/notification/page.tsx b/frontend/app/creator/settings/notification/page.tsx
new file mode 100644
index 0000000..4afbdbd
--- /dev/null
+++ b/frontend/app/creator/settings/notification/page.tsx
@@ -0,0 +1,279 @@
+'use client'
+
+import React, { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import {
+ ArrowLeft,
+ Mail,
+ Bell,
+ MessageSquare,
+ Clock,
+ AlertTriangle,
+} from 'lucide-react'
+import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
+import { cn } from '@/lib/utils'
+
+// 开关组件
+function Toggle({
+ enabled,
+ onChange,
+}: {
+ enabled: boolean
+ onChange: (enabled: boolean) => void
+}) {
+ return (
+
+ )
+}
+
+// 设置项组件
+function NotificationSettingItem({
+ icon: Icon,
+ iconColor,
+ title,
+ description,
+ enabled,
+ onChange,
+ isLast = false,
+}: {
+ icon: React.ElementType
+ iconColor: string
+ title: string
+ description: string
+ enabled: boolean
+ onChange: (enabled: boolean) => void
+ isLast?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {title}
+ {description}
+
+
+
+
+ )
+}
+
+// 时段选择组件
+function TimeRangeSelector({
+ startTime,
+ endTime,
+ onChange,
+ disabled,
+}: {
+ startTime: string
+ endTime: string
+ onChange: (start: string, end: string) => void
+ disabled: boolean
+}) {
+ return (
+
+
+ 至
+
+
+ )
+}
+
+export default function NotificationSettingsPage() {
+ const router = useRouter()
+
+ // 通知设置状态
+ const [settings, setSettings] = useState({
+ emailNotification: true,
+ pushNotification: true,
+ smsNotification: false,
+ reviewResult: true,
+ taskReminder: true,
+ urgentAlert: true,
+ quietMode: false,
+ quietStart: '22:00',
+ quietEnd: '08:00',
+ })
+
+ const handleToggle = (key: keyof typeof settings) => {
+ setSettings(prev => ({ ...prev, [key]: !prev[key] }))
+ }
+
+ const handleTimeChange = (start: string, end: string) => {
+ setSettings(prev => ({ ...prev, quietStart: start, quietEnd: end }))
+ }
+
+ return (
+
+
+ {/* 顶部栏 */}
+
+
+
+
+
+ {/* 内容区 */}
+
+ {/* 通知渠道 */}
+
+
通知渠道
+
+ handleToggle('emailNotification')}
+ />
+ handleToggle('pushNotification')}
+ />
+ handleToggle('smsNotification')}
+ isLast
+ />
+
+
+
+ {/* 通知类型 */}
+
+
通知类型
+
+ handleToggle('reviewResult')}
+ />
+ handleToggle('taskReminder')}
+ />
+ handleToggle('urgentAlert')}
+ isLast
+ />
+
+
+
+ {/* 免打扰模式 */}
+
+
免打扰模式
+
+
+
+ 开启免打扰
+ 在指定时段内静音所有通知
+
+
handleToggle('quietMode')} />
+
+
+ {/* 时间段选择 */}
+
+ 免打扰时段
+
+
+
+
+
+ {/* 提示卡片 */}
+
+
+
+
+
+ 关于通知
+
+ 建议保持审核结果和紧急通知开启,以便及时了解任务进度和重要信息。您可以随时调整通知偏好。
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/app/creator/task/[id]/page.tsx b/frontend/app/creator/task/[id]/page.tsx
index 2300118..c63d261 100644
--- a/frontend/app/creator/task/[id]/page.tsx
+++ b/frontend/app/creator/task/[id]/page.tsx
@@ -1,227 +1,375 @@
'use client'
-import { useState, useEffect } from 'react'
+import { useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import {
- Upload, Check, X, Folder, Bell, Play, MessageCircle,
- XCircle, CheckCircle, Loader2, Scan, ArrowLeft
+ Upload, Check, X, Folder, Bell, MessageCircle,
+ XCircle, CheckCircle, Loader2, Scan, ArrowLeft,
+ Bot, Users, Building2, Clock, FileText, Video,
+ ChevronRight, AlertTriangle
} from 'lucide-react'
-import { DesktopLayout } from '@/components/layout/DesktopLayout'
-import { MobileLayout } from '@/components/layout/MobileLayout'
+import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
import { cn } from '@/lib/utils'
-import { api } from '@/lib/api'
-import type { TaskResponse } from '@/types/task'
-// 任务状态类型
-type TaskStatus = 'pending_script' | 'pending_video' | 'ai_reviewing' | 'agency_reviewing' | 'need_revision' | 'passed'
+// 详细的任务状态类型
+type TaskPhase = 'script' | 'video'
+type TaskStage =
+ | 'upload' // 待上传
+ | 'ai_reviewing' // AI审核中
+ | 'ai_result' // AI审核结果(有问题需修改)
+ | 'agency_reviewing' // 代理商审核中
+ | 'agency_rejected' // 代理商驳回
+ | 'brand_reviewing' // 品牌终审中
+ | 'brand_approved' // 品牌通过
+ | 'brand_rejected' // 品牌驳回
-type RequirementProfile = {
- title?: string
- platform?: string
- deadline?: string
- progress?: number
- statusHint?: TaskStatus
- issues?: Array<{ title: string; description: string; timestamp?: string }>
- reviewLogs?: Array<{ time: string; message: string; status: 'done' | 'loading' | 'pending' }>
+type Issue = {
+ title: string
+ description: string
+ timestamp?: string
+ severity?: 'error' | 'warning'
}
-type TaskDetail = {
+type ReviewLog = {
+ time: string
+ message: string
+ status: 'done' | 'loading' | 'pending'
+}
+
+type TaskData = {
id: string
title: string
- platform: string
- deadline: string
- status: TaskStatus
- currentStep: number
+ subtitle: string
+ phase: TaskPhase
+ stage: TaskStage
progress?: number
- issues?: Array<{ title: string; description: string; timestamp?: string }>
- reviewLogs?: Array<{ time: string; message: string; status: 'done' | 'loading' | 'pending' }>
+ issues?: Issue[]
+ reviewLogs?: ReviewLog[]
+ rejectionReason?: string
+ submittedAt?: string
+ scriptContent?: string
}
-// 任务配置(占位数据)
-const taskRequirementProfiles: Record = {
+// 所有15个任务的详细数据
+const allTasksData: Record = {
'task-001': {
+ id: 'task-001',
title: 'XX品牌618推广',
- platform: '抖音',
- deadline: '2026-02-10',
- statusHint: 'pending_script',
+ subtitle: '产品种草视频 · 时长要求 60-90秒 · 当前步骤:上传脚本',
+ phase: 'script',
+ stage: 'upload',
},
'task-002': {
+ id: 'task-002',
title: 'YY美妆新品',
- platform: '小红书',
- deadline: '2026-02-15',
+ subtitle: '口播测评 · 时长要求 60-90秒 · 当前步骤:AI审核中',
+ phase: 'script',
+ stage: 'ai_reviewing',
progress: 62,
- statusHint: 'ai_reviewing',
reviewLogs: [
- { time: '14:32:01', message: '视频上传完成', status: 'done' },
+ { time: '14:32:01', message: '脚本上传完成', status: 'done' },
{ time: '14:32:15', message: '任务规则已加载', status: 'done' },
- { time: '14:32:28', message: '开始 ASR 语音识别', status: 'done' },
- { time: '14:33:45', message: '正在分析视觉合规性问题...', status: 'loading' },
+ { time: '14:32:28', message: '开始内容合规性检测', status: 'done' },
+ { time: '14:33:45', message: '正在分析品牌调性匹配度...', status: 'loading' },
],
},
'task-003': {
+ id: 'task-003',
title: 'ZZ饮品夏日',
- platform: '抖音',
- deadline: '2026-02-08',
- statusHint: 'need_revision',
+ subtitle: '探店Vlog · 发现2处问题 · 需修改后重新提交',
+ phase: 'script',
+ stage: 'ai_result',
issues: [
{
- title: '检测到竞品 Logo',
- description: '画面中 0:15-0:18 出现竞品「百事可乐」的 Logo,可能造成合规风险。',
- timestamp: '0:15',
+ title: '检测到竞品提及',
+ description: '脚本第3段提及了竞品「百事可乐」,可能造成品牌冲突风险。',
+ severity: 'error',
},
{
title: '禁用词语出现',
- description: '视频中出现「最好喝」「第一」等绝对化用语,可能违反广告法。',
- timestamp: '0:42',
+ description: '脚本中出现「最好喝」「第一」等绝对化用语,可能违反广告法。',
+ severity: 'error',
},
],
},
'task-004': {
+ id: 'task-004',
title: 'AA数码新品发布',
- platform: '抖音',
- deadline: '2026-02-20',
- statusHint: 'passed',
+ subtitle: '开箱测评 · 时长要求 60-90秒 · 当前步骤:审核通过',
+ phase: 'video',
+ stage: 'brand_approved',
+ submittedAt: '2026-02-01 10:30',
},
'task-005': {
+ id: 'task-005',
title: 'BB运动饮料',
- platform: '抖音',
- deadline: '2026-02-12',
- statusHint: 'pending_video',
+ subtitle: '运动场景 · 时长要求 60-90秒 · 当前步骤:AI审核中',
+ phase: 'script',
+ stage: 'ai_reviewing',
+ progress: 35,
+ reviewLogs: [
+ { time: '15:10:22', message: '脚本上传完成', status: 'done' },
+ { time: '15:10:35', message: '任务规则已加载', status: 'done' },
+ { time: '15:10:48', message: '正在进行内容分析...', status: 'loading' },
+ ],
+ },
+ 'task-006': {
+ id: 'task-006',
+ title: 'CC服装春季款',
+ subtitle: '穿搭展示 · 时长要求 60-90秒 · 当前步骤:等待代理商审核',
+ phase: 'script',
+ stage: 'agency_reviewing',
+ submittedAt: '2026-02-03 09:15',
+ scriptContent: '春季新品穿搭指南,展示三套不同风格的搭配...',
+ },
+ 'task-007': {
+ id: 'task-007',
+ title: 'DD家电测评',
+ subtitle: '开箱视频 · 时长要求 60-90秒 · 当前步骤:等待品牌方终审',
+ phase: 'script',
+ stage: 'brand_reviewing',
+ submittedAt: '2026-02-02 14:20',
+ scriptContent: '智能家电开箱测评,详细介绍产品功能特点...',
+ },
+ 'task-008': {
+ id: 'task-008',
+ title: 'EE食品试吃',
+ subtitle: '美食测评 · 脚本已通过 · 当前步骤:下一步上传视频',
+ phase: 'video',
+ stage: 'upload',
+ },
+ 'task-009': {
+ id: 'task-009',
+ title: 'FF护肤品',
+ subtitle: '使用教程 · 时长要求 60-90秒 · 当前步骤:视频AI审核中',
+ phase: 'video',
+ stage: 'ai_reviewing',
+ progress: 78,
+ reviewLogs: [
+ { time: '16:20:11', message: '视频上传完成', status: 'done' },
+ { time: '16:20:25', message: '任务规则已加载', status: 'done' },
+ { time: '16:20:38', message: '开始 ASR 语音识别', status: 'done' },
+ { time: '16:21:52', message: '视觉合规性检测完成', status: 'done' },
+ { time: '16:22:30', message: '正在生成审核报告...', status: 'loading' },
+ ],
+ },
+ 'task-010': {
+ id: 'task-010',
+ title: 'GG智能手表',
+ subtitle: '功能展示 · 时长要求 60-90秒 · 当前步骤:代理商驳回',
+ phase: 'script',
+ stage: 'agency_rejected',
+ rejectionReason: '脚本内容与品牌调性不符,产品卖点描述不够突出,建议重新调整创意方向。',
+ issues: [
+ {
+ title: '品牌调性不符',
+ description: '脚本整体风格偏向娱乐化,与品牌科技专业形象不匹配。',
+ severity: 'error',
+ },
+ {
+ title: '卖点不突出',
+ description: '产品核心功能(健康监测、长续航)未在脚本中重点体现。',
+ severity: 'warning',
+ },
+ ],
+ },
+ 'task-011': {
+ id: 'task-011',
+ title: 'HH美妆代言',
+ subtitle: '广告拍摄 · 时长要求 60-90秒 · 当前步骤:品牌驳回',
+ phase: 'script',
+ stage: 'brand_rejected',
+ rejectionReason: '品牌方认为脚本创意不够新颖,希望加入更多互动元素。',
+ issues: [
+ {
+ title: '创意不够新颖',
+ description: '脚本采用的是常见的口播形式,缺乏创新点和记忆点。',
+ severity: 'error',
+ },
+ {
+ title: '缺少互动元素',
+ description: '建议加入用户互动环节,如问答、挑战等,增加观众参与感。',
+ severity: 'warning',
+ },
+ ],
+ },
+ 'task-012': {
+ id: 'task-012',
+ title: 'II数码配件',
+ subtitle: '配件展示 · 时长要求 60-90秒 · 当前步骤:等待代理商审核',
+ phase: 'video',
+ stage: 'agency_reviewing',
+ submittedAt: '2026-02-04 11:30',
+ },
+ 'task-013': {
+ id: 'task-013',
+ title: 'JJ旅行vlog',
+ subtitle: '旅行记录 · 时长要求 60-90秒 · 当前步骤:代理商驳回',
+ phase: 'video',
+ stage: 'agency_rejected',
+ rejectionReason: '视频背景音乐存在版权问题,需要更换为无版权音乐后重新提交。',
+ issues: [
+ {
+ title: '背景音乐版权问题',
+ description: '视频中使用的背景音乐「XXX」存在版权风险,平台可能会限流或下架。',
+ severity: 'error',
+ },
+ ],
+ },
+ 'task-014': {
+ id: 'task-014',
+ title: 'KK宠物用品',
+ subtitle: '萌宠日常 · 时长要求 60-90秒 · 当前步骤:等待品牌方终审',
+ phase: 'video',
+ stage: 'brand_reviewing',
+ submittedAt: '2026-02-05 09:45',
+ },
+ 'task-015': {
+ id: 'task-015',
+ title: 'LL厨房电器',
+ subtitle: '使用演示 · 时长要求 60-90秒 · 当前步骤:品牌驳回',
+ phase: 'video',
+ stage: 'brand_rejected',
+ rejectionReason: '产品使用场景不够真实,希望展示更多日常使用细节。',
+ issues: [
+ {
+ title: '使用场景不真实',
+ description: '视频中的厨房场景过于整洁,缺乏真实感,建议在真实家庭环境中拍摄。',
+ severity: 'error',
+ },
+ {
+ title: '产品功能展示不完整',
+ description: '仅展示了基础功能,建议补充展示智能预约、自清洁等特色功能。',
+ severity: 'warning',
+ },
+ ],
},
}
-const platformLabelMap: Record = {
- douyin: '抖音',
- xiaohongshu: '小红书',
- bilibili: 'B站',
- kuaishou: '快手',
+// 进度条步骤图标组件
+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' && }
+
+ )
}
-const getPlatformLabel = (platform?: string) => {
- if (!platform) return '未知平台'
- return platformLabelMap[platform] || platform
-}
+// 审核流程进度条
+function ReviewProgressBar({ task }: { task: TaskData }) {
+ const { phase, stage } = task
-const deriveTaskStatus = (task: TaskResponse): TaskStatus => {
- if (!task.has_script) {
- return 'pending_script'
- }
- if (!task.has_video) {
- return 'pending_video'
- }
- if (task.status === 'approved') {
- return 'passed'
- }
- if (task.status === 'rejected' || task.status === 'failed') {
- return 'need_revision'
- }
- if (task.status === 'pending' || task.status === 'processing') {
- return 'ai_reviewing'
- }
- return 'agency_reviewing'
-}
+ const getStepStatus = (stepIndex: number): 'done' | 'current' | 'error' | 'pending' => {
+ // 步骤顺序: 0-提交, 1-AI审核, 2-代理商, 3-品牌
+ 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 getCurrentStep = (status: TaskStatus) => {
- if (status === 'ai_reviewing' || status === 'need_revision') {
- return 2
- }
- if (status === 'agency_reviewing') {
- return 3
- }
- if (status === 'passed') {
- return 4
- }
- return 1
-}
+ const currentStepIndex = stageMap[stage]
+ const isError = stage === 'ai_result' || stage === 'agency_rejected' || stage === 'brand_rejected'
-const buildTaskDetail = (task: TaskResponse): TaskDetail => {
- const profile = taskRequirementProfiles[task.task_id]
- const status = deriveTaskStatus(task)
- const platformLabel = profile?.platform || getPlatformLabel(task.platform)
-
- return {
- id: task.task_id,
- title: profile?.title || `任务 ${task.task_id}`,
- platform: platformLabel,
- deadline: profile?.deadline || '待确认',
- status,
- currentStep: getCurrentStep(status),
- progress: profile?.progress,
- issues: profile?.issues,
- reviewLogs: profile?.reviewLogs,
+ if (stepIndex < currentStepIndex) return 'done'
+ if (stepIndex === currentStepIndex) {
+ if (isError) return 'error'
+ if (stage === 'brand_approved') return 'done'
+ return 'current'
+ }
+ return 'pending'
}
-}
-// 审核进度条组件
-function ReviewProgressBar({ currentStep, status }: { currentStep: number; status: TaskStatus }) {
const steps = [
- { label: '已提交', step: 1 },
- { label: 'AI审核', step: 2 },
- { label: '代理商审核', step: 3 },
- { label: '最终结果', step: 4 },
+ { label: '已提交', icon: 'upload' as const },
+ { label: 'AI审核', icon: 'bot' as const },
+ { label: '代理商', icon: 'users' as const },
+ { label: '品牌方', icon: 'building' as const },
]
return (
-
- {steps.map((s, index) => {
- const isCompleted = s.step < currentStep || (s.step === currentStep && status === 'passed')
- const isCurrent = s.step === currentStep && status !== 'passed'
- const isError = isCurrent && status === 'need_revision'
-
- return (
-
-
-
- {isCompleted &&
}
- {isCurrent && !isError &&
}
- {isError &&
}
+
+
+ {phase === 'script' ? '脚本审核流程' : '视频审核流程'}
+
+
+ {steps.map((step, index) => {
+ const status = getStepStatus(index)
+ return (
+
+
+
+
+ {step.label}
+
-
- {s.label}
-
+ {index < steps.length - 1 && (
+
+ )}
- {index < steps.length - 1 && (
-
- )}
-
- )
- })}
+ )
+ })}
+
)
}
// 上传界面
-function UploadView({ task }: { task: TaskDetail }) {
+function UploadView({ task }: { task: TaskData }) {
const [isDragging, setIsDragging] = useState(false)
- const isScriptStep = task.status === 'pending_script'
- const title = isScriptStep ? '上传脚本' : '上传视频'
- const subtitle = isScriptStep
- ? '支持粘贴文本或上传文档'
- : '支持 MP4/MOV 格式,≤ 100MB'
- const actionLabel = isScriptStep ? '选择脚本文档' : '选择视频文件'
- const hintText = isScriptStep ? '也可以直接粘贴脚本文本后提交' : '上传完成后将自动进入 AI 审核'
+ const isScript = task.phase === 'script'
return (
-
{title}
-
{subtitle}
+
+ {isScript ? '上传脚本' : '上传视频'}
+
+
+ {isScript ? '支持粘贴文本或上传文档' : '支持 MP4/MOV 格式,≤ 100MB'}
+
待提交
@@ -230,20 +378,22 @@ function UploadView({ task }: { task: TaskDetail }) {
{ e.preventDefault(); setIsDragging(true) }}
onDragLeave={() => setIsDragging(false)}
onDrop={(e) => { e.preventDefault(); setIsDragging(false) }}
>
-
+
点击或拖拽文件到此处
-
{subtitle}
+
+ {isScript ? '支持 .doc、.docx、.txt 格式' : '支持 MP4/MOV 格式,≤ 100MB'}
+
-
{hintText}
+
+ {isScript ? '也可以直接粘贴脚本文本后提交' : '上传完成后将自动进入 AI 审核'}
+
)
}
-// AI 审核中界面
-function ReviewingView({ task }: { task: TaskDetail }) {
+// AI审核中界面
+function AIReviewingView({ task }: { task: TaskData }) {
return (
-
- {/* 任务标签 */}
+
- 产品种草视频 · 时长 60-90秒
+
+ {task.phase === 'script' ? '脚本内容审核' : '视频内容审核'} · 智能分析中
+
- {/* 扫描动画 */}
- {/* 外圈渐变 */}
-
- {/* 中心圆 */}
+
- {/* 进度信息 */}
-
AI 正在审核您的视频
+
+ AI 正在审核您的{task.phase === 'script' ? '脚本' : '视频'}
+
预计还需 2-3 分钟,可先离开页面
- {/* 进度条 */}
- {/* 日志区 */}
-
-
-
-
处理日志
+ {task.reviewLogs && task.reviewLogs.length > 0 && (
+
+
+
+ 处理日志
+
+
+ {task.reviewLogs.map((log, index) => (
+
+ {log.time}
+
+ {log.message}
+
+ {log.status === 'loading' && }
+
+ ))}
+
-
- {task.reviewLogs?.map((log, index) => (
-
- {log.time}
-
- {log.message}
-
- {log.status === 'loading' && }
-
- ))}
-
-
+ )}
- {/* 通知按钮 */}