Your Name 4753626e5a feat: 完成代理商/品牌方前端及文档更新
代理商端前端:
- 新增达人管理页面(含任务申诉次数管理)
- 新增消息中心(含申诉次数申请审批)
- 新增 Brief 管理(列表、详情)
- 新增审核中心(脚本审核、视频审核)
- 新增数据报表页面

品牌方端前端:
- 优化首页仪表盘布局
- 新增项目管理(列表、详情、创建)
- 新增代理商管理页面
- 新增审核中心(脚本终审、视频终审)
- 新增系统设置页面

文档更新:
- 申诉次数改为按任务分配(每任务初始1次)
- 更新 PRD、FeatureSummary、User_Role_Interfaces 等文档
- 更新 UI 设计规范和开发计划

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:39:23 +08:00

202 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import {
ArrowLeft,
Upload,
Calendar,
FileText,
CheckCircle,
X,
Users
} from 'lucide-react'
// 模拟代理商列表
const mockAgencies = [
{ id: 'agency-001', name: '星耀传媒', creatorCount: 50, passRate: 92 },
{ id: 'agency-002', name: '创意无限', creatorCount: 35, passRate: 88 },
{ id: 'agency-003', name: '美妆达人MCN', creatorCount: 28, passRate: 82 },
{ id: 'agency-004', name: '时尚风向标', creatorCount: 42, passRate: 85 },
]
export default function CreateProjectPage() {
const router = useRouter()
const [projectName, setProjectName] = useState('')
const [deadline, setDeadline] = useState('')
const [briefFile, setBriefFile] = useState<File | null>(null)
const [selectedAgencies, setSelectedAgencies] = useState<string[]>([])
const [isSubmitting, setIsSubmitting] = useState(false)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
setBriefFile(file)
}
}
const toggleAgency = (agencyId: string) => {
setSelectedAgencies(prev =>
prev.includes(agencyId)
? prev.filter(id => id !== agencyId)
: [...prev, agencyId]
)
}
const handleSubmit = async () => {
if (!projectName.trim() || !deadline || !briefFile || selectedAgencies.length === 0) {
alert('请填写完整信息')
return
}
setIsSubmitting(true)
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1500))
alert('项目创建成功!')
router.push('/brand')
}
const isValid = projectName.trim() && deadline && briefFile && selectedAgencies.length > 0
return (
<div className="space-y-6 max-w-4xl">
{/* 顶部导航 */}
<div className="flex items-center gap-4">
<button type="button" onClick={() => router.back()} className="p-2 hover:bg-bg-elevated rounded-full">
<ArrowLeft size={20} className="text-text-primary" />
</button>
<h1 className="text-2xl font-bold text-text-primary"></h1>
</div>
<Card>
<CardContent className="p-6 space-y-6">
{/* 项目名称 */}
<div>
<label className="block text-sm font-medium text-text-primary mb-2">
<span className="text-accent-coral">*</span>
</label>
<input
type="text"
value={projectName}
onChange={(e) => 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"
/>
</div>
{/* 截止日期 */}
<div>
<label className="block text-sm font-medium text-text-primary mb-2">
<span className="text-accent-coral">*</span>
</label>
<div className="relative">
<Calendar size={18} className="absolute left-4 top-1/2 -translate-y-1/2 text-text-tertiary" />
<input
type="date"
value={deadline}
onChange={(e) => setDeadline(e.target.value)}
className="w-full pl-12 pr-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"
/>
</div>
</div>
{/* Brief 上传 */}
<div>
<label className="block text-sm font-medium text-text-primary mb-2">
Brief <span className="text-accent-coral">*</span>
</label>
<div className="border-2 border-dashed border-border-subtle rounded-lg p-8 text-center hover:border-accent-indigo/50 transition-colors">
{briefFile ? (
<div className="flex items-center justify-center gap-3">
<FileText size={24} className="text-accent-indigo" />
<span className="text-text-primary">{briefFile.name}</span>
<button
type="button"
onClick={() => setBriefFile(null)}
className="p-1 hover:bg-bg-elevated rounded-full"
>
<X size={16} className="text-text-tertiary" />
</button>
</div>
) : (
<label className="cursor-pointer">
<Upload size={32} className="mx-auto text-text-tertiary mb-3" />
<p className="text-text-secondary mb-1"> Brief </p>
<p className="text-xs text-text-tertiary"> PDFWordExcel </p>
<input
type="file"
accept=".pdf,.doc,.docx,.xls,.xlsx"
onChange={handleFileChange}
className="hidden"
/>
</label>
)}
</div>
</div>
{/* 选择代理商 */}
<div>
<label className="block text-sm font-medium text-text-primary mb-2">
<span className="text-accent-coral">*</span>
<span className="text-text-tertiary font-normal ml-2">
{selectedAgencies.length}
</span>
</label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{mockAgencies.map((agency) => {
const isSelected = selectedAgencies.includes(agency.id)
return (
<button
key={agency.id}
type="button"
onClick={() => toggleAgency(agency.id)}
className={`p-4 rounded-lg border-2 text-left transition-all ${
isSelected
? 'border-accent-indigo bg-accent-indigo/10'
: 'border-border-subtle hover:border-accent-indigo/50'
}`}
>
<div className="flex items-start justify-between">
<div>
<div className="font-medium text-text-primary">{agency.name}</div>
<div className="flex items-center gap-4 mt-1 text-sm text-text-secondary">
<span className="flex items-center gap-1">
<Users size={14} />
{agency.creatorCount}
</span>
<span className={agency.passRate >= 90 ? 'text-accent-green' : agency.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}>
{agency.passRate}%
</span>
</div>
</div>
{isSelected && (
<CheckCircle size={20} className="text-accent-indigo" />
)}
</div>
</button>
)
})}
</div>
</div>
{/* 操作按钮 */}
<div className="flex items-center justify-end gap-4 pt-4 border-t border-border-subtle">
<Button variant="secondary" onClick={() => router.back()}>
</Button>
<Button
onClick={handleSubmit}
disabled={!isValid || isSubmitting}
>
{isSubmitting ? '创建中...' : '创建项目'}
</Button>
</div>
</CardContent>
</Card>
</div>
)
}