Your Name 0bfedb95c8 feat: 为所有终端添加平台显示功能
- 新增 frontend/lib/platforms.ts 共享平台配置模块
- 支持6个平台: 抖音、小红书、B站、快手、微博、微信视频号
- 品牌方终端: 项目看板、项目详情、终审台列表添加平台显示
- 代理商终端: 工作台概览、审核台、Brief配置、达人管理、
  数据报表、消息中心、申诉处理添加平台显示
- 达人端: 任务列表添加平台显示
- 统一使用彩色头部条样式展示平台信息

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 18:53:51 +08:00

230 lines
8.9 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'
import { getPlatformInfo } from '@/lib/platforms'
// 模拟 Brief 列表
const mockBriefs = [
{
id: 'brief-001',
projectName: 'XX品牌618推广',
brandName: 'XX护肤品牌',
platform: 'douyin',
status: 'configured',
uploadedAt: '2026-02-01',
configuredAt: '2026-02-02',
creatorCount: 15,
sellingPoints: 5,
blacklistWords: 12,
},
{
id: 'brief-002',
projectName: '新品口红系列',
brandName: 'XX美妆品牌',
platform: 'xiaohongshu',
status: 'pending',
uploadedAt: '2026-02-05',
configuredAt: null,
creatorCount: 0,
sellingPoints: 0,
blacklistWords: 0,
},
{
id: 'brief-003',
projectName: '护肤品秋季活动',
brandName: 'XX护肤品牌',
platform: 'bilibili',
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 min-h-0">
{/* 页面标题 */}
<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) => {
const platform = getPlatformInfo(brief.platform)
return (
<Link key={brief.id} href={`/agency/briefs/${brief.id}`}>
<Card className="hover:border-accent-indigo/50 transition-colors cursor-pointer overflow-hidden">
{/* 平台顶部条 */}
{platform && (
<div className={`px-6 py-2 ${platform.bgColor} border-b ${platform.borderColor} flex items-center gap-2`}>
<span className="text-base">{platform.icon}</span>
<span className={`text-sm font-medium ${platform.textColor}`}>{platform.name}</span>
</div>
)}
<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>
)
}