'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { Video, Search, SlidersHorizontal, ChevronDown, Upload, Bot, Users, Building2, Check, X, Loader2 } from 'lucide-react' import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' import { cn } from '@/lib/utils' import { getPlatformInfo } from '@/lib/platforms' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import { useSSE } from '@/contexts/SSEContext' import { mapTaskToUI, type StepStatus, type StageSteps } from '@/lib/taskStageMapper' import type { TaskResponse } from '@/types/task' // UI 用任务数据(从 API 数据映射而来) type Task = { id: string title: string description: string platform: string scriptStage: StageSteps videoStage: StageSteps buttonText: string buttonType: 'upload' | 'view' | 'fix' scriptColor: string videoColor: string filterCategory: 'pending' | 'reviewing' | 'rejected' | 'completed' } // Mock 数据(开发模式使用) const mockTasks: Task[] = [ { id: 'task-001', title: 'XX品牌618推广', description: '产品种草视频 · 时长要求 60-90秒 · 截止: 2026-02-10', platform: 'douyin', scriptStage: { submit: 'current', ai: 'pending', agency: 'pending', brand: 'pending' }, videoStage: { submit: 'pending', ai: 'pending', agency: 'pending', brand: 'pending' }, buttonText: '上传脚本', buttonType: 'upload', scriptColor: 'blue', videoColor: 'tertiary', filterCategory: 'pending', }, { id: 'task-002', title: 'YY美妆新品', description: '口播测评 · 已上传视频 · 提交于: 今天 14:30', platform: 'xiaohongshu', scriptStage: { submit: 'done', ai: 'current', agency: 'pending', brand: 'pending' }, videoStage: { submit: 'pending', ai: 'pending', agency: 'pending', brand: 'pending' }, buttonText: '查看详情', buttonType: 'view', scriptColor: 'indigo', videoColor: 'tertiary', filterCategory: 'reviewing', }, { id: 'task-003', title: 'ZZ饮品夏日', description: '探店Vlog · 发现2处问题 · 需修改后重新提交', platform: 'bilibili', scriptStage: { submit: 'done', ai: 'error', agency: 'pending', brand: 'pending' }, videoStage: { submit: 'pending', ai: 'pending', agency: 'pending', brand: 'pending' }, buttonText: '查看修改', buttonType: 'fix', scriptColor: 'coral', videoColor: 'tertiary', filterCategory: 'rejected', }, { id: 'task-004', title: 'AA数码新品发布', description: '开箱测评 · 审核通过 · 可发布', platform: 'douyin', scriptStage: { submit: 'done', ai: 'done', agency: 'done', brand: 'done' }, videoStage: { submit: 'done', ai: 'done', agency: 'done', brand: 'done' }, buttonText: '查看详情', buttonType: 'view', scriptColor: 'green', videoColor: 'green', filterCategory: 'completed', }, { id: 'task-008', title: 'EE食品试吃', description: '美食测评 · 脚本通过 · 待上传视频', platform: 'douyin', scriptStage: { submit: 'done', ai: 'done', agency: 'done', brand: 'done' }, videoStage: { submit: 'current', ai: 'pending', agency: 'pending', brand: 'pending' }, buttonText: '上传视频', buttonType: 'upload', scriptColor: 'green', videoColor: 'blue', filterCategory: 'pending', }, ] function mapTaskResponseToUI(task: TaskResponse): Task { const ui = mapTaskToUI(task) const buttonTypeMap: Record = { primary: 'upload', success: 'view', warning: 'fix', disabled: 'view', } return { id: task.id, title: task.name, description: `${task.project.name} · ${ui.statusLabel}`, platform: 'douyin', // 后端暂无平台字段,默认 scriptStage: ui.scriptStage, videoStage: ui.videoStage, buttonText: ui.buttonText, buttonType: buttonTypeMap[ui.buttonType] || 'view', scriptColor: ui.scriptColor, videoColor: ui.videoColor, filterCategory: ui.filterCategory, } } // 步骤图标组件 function StepIcon({ status, icon }: { status: StepStatus; icon: 'upload' | 'bot' | 'users' | 'building' }) { const IconComponent = { upload: Upload, bot: Bot, users: Users, building: Building2, }[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 getIconColor = () => { if (status === 'done' || status === 'current' || status === 'error') return 'text-white' return 'text-text-tertiary' } return (
{status === 'done' && } {status === 'current' && } {status === 'error' && } {status === 'pending' && }
) } // 进度条组件 function ProgressBar({ stage, color }: { stage: StageSteps color: string }) { const steps = [ { key: 'submit', label: '提交', icon: 'upload' as const, status: stage.submit }, { key: 'ai', label: 'AI审核', icon: 'bot' as const, status: stage.ai }, { key: 'agency', label: '代理商', icon: 'users' as const, status: stage.agency }, { key: 'brand', label: '品牌', icon: 'building' as const, status: stage.brand }, ] const getLineColor = (fromStatus: StepStatus) => { if (fromStatus === 'done') return 'bg-accent-green' return 'bg-border-subtle' } const getLabelColor = (status: StepStatus) => { if (status === 'done') return 'text-text-secondary' if (status === 'current') return 'text-accent-indigo font-semibold' if (status === 'error') return 'text-accent-coral font-semibold' return 'text-text-tertiary' } return (
{steps.map((step, index) => (
{step.label}
{index < steps.length - 1 && (
)}
))}
) } // 任务卡片组件 function TaskCard({ task, onClick }: { task: Task; onClick: () => void }) { const platform = getPlatformInfo(task.platform) const getStageColor = (color: string) => { switch (color) { case 'blue': return 'text-accent-blue' case 'indigo': return 'text-accent-indigo' case 'coral': return 'text-accent-coral' case 'green': return 'text-accent-green' case 'red': return 'text-accent-coral' default: return 'text-text-tertiary' } } const getButtonStyle = () => { switch (task.buttonType) { case 'upload': return 'bg-accent-green text-white' case 'fix': return 'bg-accent-coral text-white' default: return 'bg-transparent border-[1.5px] border-accent-indigo text-accent-indigo' } } return (
{platform && (
{platform.icon} {platform.name}
)}
{task.title} {task.description}
脚本
视频
) } type TaskFilter = 'all' | 'pending' | 'reviewing' | 'rejected' | 'completed' const filterOptions: { value: TaskFilter; label: string }[] = [ { value: 'all', label: '全部状态' }, { value: 'pending', label: '待提交' }, { value: 'reviewing', label: '审核中' }, { value: 'rejected', label: '已驳回' }, { value: 'completed', label: '已完成' }, ] // 骨架屏 function TaskSkeleton() { return (
) } export default function CreatorTasksPage() { const router = useRouter() const { subscribe } = useSSE() const [searchQuery, setSearchQuery] = useState('') const [filter, setFilter] = useState('all') const [showFilterDropdown, setShowFilterDropdown] = useState(false) const [tasks, setTasks] = useState([]) const [isLoading, setIsLoading] = useState(true) const [total, setTotal] = useState(0) const loadTasks = useCallback(async () => { if (USE_MOCK) { setTasks(mockTasks) setTotal(mockTasks.length) setIsLoading(false) return } try { setIsLoading(true) const response = await api.listTasks(1, 50) const mapped = response.items.map(mapTaskResponseToUI) setTasks(mapped) setTotal(response.total) } catch (err) { console.error('加载任务失败:', err) } finally { setIsLoading(false) } }, []) useEffect(() => { loadTasks() }, [loadTasks]) // SSE 实时更新 useEffect(() => { const unsub1 = subscribe('task_updated', () => { loadTasks() }) const unsub2 = subscribe('new_task', () => { loadTasks() }) return () => { unsub1(); unsub2() } }, [subscribe, loadTasks]) const handleTaskClick = (taskId: string) => { router.push(`/creator/task/${taskId}`) } const filteredTasks = tasks.filter(task => { const matchesSearch = searchQuery === '' || task.title.toLowerCase().includes(searchQuery.toLowerCase()) || task.description.toLowerCase().includes(searchQuery.toLowerCase()) const matchesFilter = filter === 'all' || task.filterCategory === filter return matchesSearch && matchesFilter }) const currentFilterLabel = filterOptions.find(opt => opt.value === filter)?.label || '全部状态' return (

我的任务

{filter === 'all' ? `共 ${total} 个任务` : `${currentFilterLabel} ${filteredTasks.length} 个`}

setSearchQuery(e.target.value)} className="bg-transparent text-sm text-text-primary placeholder-text-tertiary focus:outline-none w-32" />
{showFilterDropdown && ( <>
setShowFilterDropdown(false)} />
{filterOptions.map((option) => ( ))}
)}
{isLoading ? ( <> ) : filteredTasks.length === 0 ? (

没有找到匹配的任务

尝试调整搜索条件或筛选状态

) : ( filteredTasks.map((task) => ( handleTaskClick(task.id)} /> )) )}
) }