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

216 lines
8.2 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 Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag'
import {
FileText,
Search,
Filter,
Clock,
CheckCircle,
AlertTriangle,
ChevronRight,
Settings
} from 'lucide-react'
// 模拟 Brief 列表
const mockBriefs = [
{
id: 'brief-001',
projectName: 'XX品牌618推广',
brandName: 'XX护肤品牌',
status: 'configured',
uploadedAt: '2026-02-01',
configuredAt: '2026-02-02',
creatorCount: 15,
sellingPoints: 5,
blacklistWords: 12,
},
{
id: 'brief-002',
projectName: '新品口红系列',
brandName: 'XX美妆品牌',
status: 'pending',
uploadedAt: '2026-02-05',
configuredAt: null,
creatorCount: 0,
sellingPoints: 0,
blacklistWords: 0,
},
{
id: 'brief-003',
projectName: '护肤品秋季活动',
brandName: 'XX护肤品牌',
status: 'configured',
uploadedAt: '2025-09-15',
configuredAt: '2025-09-16',
creatorCount: 10,
sellingPoints: 4,
blacklistWords: 8,
},
]
function StatusTag({ status }: { status: string }) {
if (status === 'configured') return <SuccessTag></SuccessTag>
if (status === 'pending') return <WarningTag></WarningTag>
return <PendingTag></PendingTag>
}
export default function AgencyBriefsPage() {
const [searchQuery, setSearchQuery] = useState('')
const [statusFilter, setStatusFilter] = useState<string>('all')
const filteredBriefs = mockBriefs.filter(brief => {
const matchesSearch = brief.projectName.toLowerCase().includes(searchQuery.toLowerCase()) ||
brief.brandName.toLowerCase().includes(searchQuery.toLowerCase())
const matchesStatus = statusFilter === 'all' || brief.status === statusFilter
return matchesSearch && matchesStatus
})
const pendingCount = mockBriefs.filter(b => b.status === 'pending').length
const configuredCount = mockBriefs.filter(b => b.status === 'configured').length
return (
<div className="space-y-6">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-text-primary">Brief </h1>
<p className="text-sm text-text-secondary mt-1"> Brief</p>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="px-3 py-1.5 bg-yellow-500/20 text-yellow-400 rounded-lg font-medium">
{pendingCount}
</span>
<span className="px-3 py-1.5 bg-accent-green/20 text-accent-green rounded-lg font-medium">
{configuredCount}
</span>
</div>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary" />
<input
type="text"
placeholder="搜索项目名称或品牌..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg">
<button
type="button"
onClick={() => setStatusFilter('all')}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
statusFilter === 'all' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
</button>
<button
type="button"
onClick={() => setStatusFilter('pending')}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
statusFilter === 'pending' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
</button>
<button
type="button"
onClick={() => setStatusFilter('configured')}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
statusFilter === 'configured' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
}`}
>
</button>
</div>
</div>
{/* Brief 列表 */}
<div className="grid grid-cols-1 gap-4">
{filteredBriefs.map((brief) => (
<Link key={brief.id} href={`/agency/briefs/${brief.id}`}>
<Card className="hover:border-accent-indigo/50 transition-colors cursor-pointer">
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
brief.status === 'configured' ? 'bg-accent-green/20' : 'bg-yellow-500/20'
}`}>
{brief.status === 'configured' ? (
<CheckCircle size={24} className="text-accent-green" />
) : (
<AlertTriangle size={24} className="text-yellow-400" />
)}
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="font-medium text-text-primary">{brief.projectName}</h3>
<StatusTag status={brief.status} />
</div>
<div className="flex items-center gap-4 mt-1 text-sm text-text-secondary">
<span>{brief.brandName}</span>
<span className="flex items-center gap-1">
<Clock size={12} />
{brief.uploadedAt}
</span>
</div>
</div>
</div>
<div className="flex items-center gap-8">
{brief.status === 'configured' && (
<div className="flex items-center gap-6 text-sm">
<div className="text-center">
<div className="text-lg font-bold text-text-primary">{brief.sellingPoints}</div>
<div className="text-text-tertiary"></div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">{brief.blacklistWords}</div>
<div className="text-text-tertiary"></div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">{brief.creatorCount}</div>
<div className="text-text-tertiary"></div>
</div>
</div>
)}
<Button variant={brief.status === 'pending' ? 'primary' : 'secondary'} size="sm">
{brief.status === 'pending' ? (
<>
<Settings size={14} />
</>
) : (
<>
<ChevronRight size={14} />
</>
)}
</Button>
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
{filteredBriefs.length === 0 && (
<div className="text-center py-16">
<FileText size={48} className="mx-auto text-text-tertiary opacity-50 mb-4" />
<p className="text-text-secondary"> Brief</p>
</div>
)}
</div>
)
}