'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useToast } from '@/components/ui/Toast' import { Card, CardContent } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { Input } from '@/components/ui/Input' import { ArrowLeft, Upload, Calendar, FileText, CheckCircle, AlertCircle, Search, Building2, Loader2, Trash2, RotateCcw } from 'lucide-react' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' import { platformOptions } from '@/lib/platforms' import type { AgencyDetail } from '@/types/organization' import type { BriefAttachment } from '@/types/brief' // 单个文件的上传状态 interface UploadFileItem { id: string name: string size: string rawSize: number status: 'uploading' | 'success' | 'error' progress: number url?: string error?: string file?: File // 保留引用用于重试 } function formatFileSize(bytes: number): string { if (bytes < 1024) return bytes + 'B' if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB' if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + 'MB' return (bytes / (1024 * 1024 * 1024)).toFixed(1) + 'GB' } // ==================== Mock 数据 ==================== const mockAgencies: AgencyDetail[] = [ { id: 'AG789012', name: '星耀传媒', force_pass_enabled: true }, { id: 'AG456789', name: '创意无限', force_pass_enabled: false }, { id: 'AG123456', name: '美妆达人MCN', force_pass_enabled: false }, { id: 'AG111111', name: '蓝海科技', force_pass_enabled: true }, { id: 'AG222222', name: '云创网络', force_pass_enabled: false }, { id: 'AG333333', name: '天府传媒', force_pass_enabled: true }, ] export default function CreateProjectPage() { const router = useRouter() const toast = useToast() const [projectName, setProjectName] = useState('') const [description, setDescription] = useState('') const [platform, setPlatform] = useState('douyin') const [deadline, setDeadline] = useState('') const [uploadFiles, setUploadFiles] = useState([]) const [selectedAgencies, setSelectedAgencies] = useState([]) const [isSubmitting, setIsSubmitting] = useState(false) const [agencySearch, setAgencySearch] = useState('') const [agencies, setAgencies] = useState([]) const [loadingAgencies, setLoadingAgencies] = useState(true) // 从成功上传的文件中提取 BriefAttachment const briefFiles: BriefAttachment[] = uploadFiles .filter(f => f.status === 'success' && f.url) .map(f => ({ id: f.id, name: f.name, url: f.url!, size: f.size })) const hasUploading = uploadFiles.some(f => f.status === 'uploading') useEffect(() => { const loadAgencies = async () => { if (USE_MOCK) { setAgencies(mockAgencies) setLoadingAgencies(false) return } try { const data = await api.listBrandAgencies() setAgencies(data.items) } catch (err) { console.error('Failed to load agencies:', err) toast.error('加载代理商列表失败') } finally { setLoadingAgencies(false) } } loadAgencies() }, [toast]) const filteredAgencies = agencies.filter(agency => agencySearch === '' || agency.name.toLowerCase().includes(agencySearch.toLowerCase()) || agency.id.toLowerCase().includes(agencySearch.toLowerCase()) ) // 上传单个文件(独立跟踪进度) const uploadSingleFile = async (file: File, fileId: string) => { if (USE_MOCK) { // Mock:模拟进度 for (let p = 20; p <= 80; p += 20) { await new Promise(r => setTimeout(r, 300)) setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, progress: p } : f)) } await new Promise(r => setTimeout(r, 300)) setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, status: 'success', progress: 100, url: `mock://${file.name}` } : f )) toast.success(`${file.name} 上传完成`) return } try { const result = await api.proxyUpload(file, 'general', (pct) => { setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, progress: Math.min(95, Math.round(pct * 0.95)) } : f )) }) setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, status: 'success', progress: 100, url: result.url } : f )) toast.success(`${file.name} 上传完成`) } catch (err) { const msg = err instanceof Error ? err.message : '上传失败' setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, status: 'error', error: msg } : f )) toast.error(`${file.name} 上传失败: ${msg}`) } } const handleFileChange = (e: React.ChangeEvent) => { const files = e.target.files if (!files || files.length === 0) return const fileList = Array.from(files) e.target.value = '' toast.info(`已选择 ${fileList.length} 个文件,开始上传...`) // 立即添加所有文件到列表(uploading 状态) const newItems: UploadFileItem[] = fileList.map(file => ({ id: `att-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, name: file.name, size: formatFileSize(file.size), rawSize: file.size, status: 'uploading' as const, progress: 0, file, })) setUploadFiles(prev => [...prev, ...newItems]) // 并发上传所有文件 newItems.forEach(item => { uploadSingleFile(item.file!, item.id) }) } // 重试失败的上传 const retryUpload = (fileId: string) => { const item = uploadFiles.find(f => f.id === fileId) if (!item?.file) return setUploadFiles(prev => prev.map(f => f.id === fileId ? { ...f, status: 'uploading', progress: 0, error: undefined } : f )) uploadSingleFile(item.file, fileId) } const removeFile = (id: string) => { setUploadFiles(prev => prev.filter(f => f.id !== id)) } const toggleAgency = (agencyId: string) => { setSelectedAgencies(prev => prev.includes(agencyId) ? prev.filter(id => id !== agencyId) : [...prev, agencyId] ) } const handleSubmit = async () => { if (!projectName.trim() || !deadline || selectedAgencies.length === 0) { toast.error('请填写完整信息') return } setIsSubmitting(true) try { if (USE_MOCK) { await new Promise(resolve => setTimeout(resolve, 1000)) } else { const project = await api.createProject({ name: projectName.trim(), description: description.trim() || undefined, platform, deadline, agency_ids: selectedAgencies, }) // If brief files were uploaded, create brief with attachments if (briefFiles.length > 0) { await api.createBrief(project.id, { attachments: briefFiles, }) } } toast.success('项目创建成功!') router.push('/brand') } catch (err) { console.error('Failed to create project:', err) toast.error('创建失败,请重试') } finally { setIsSubmitting(false) } } const isValid = projectName.trim() && deadline && selectedAgencies.length > 0 return (

创建项目

{/* 项目名称 */}
setProjectName(e.target.value)} placeholder="例如:XX品牌618推广" className="w-full px-4 py-3 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo" />
{/* 项目描述 */}