feat: 完善品牌方和代理商前端功能

品牌方功能:
- 项目看板: 添加截止日期编辑功能
- 项目详情: 添加代理商管理、截止日期编辑、最近任务显示代理商
- 项目创建: 代理商选择支持搜索(名称/ID/公司名)
- 代理商管理: 通过ID邀请、添加备注/分配项目/移除操作
- Brief配置: 新增项目级Brief和规则配置页面
- 系统设置: 完善账户安全(密码/2FA/邮箱/手机/设备管理)、数据导出、退出登录

代理商功能:
- 个人中心: 新增代理商ID展示、公司信息(企业验证)、个人信息编辑
- 账户设置: 密码修改、手机/邮箱绑定、两步验证
- 通知设置: 分类型和渠道的通知开关
- 审核历史: 搜索筛选和统计展示
- 帮助反馈: FAQ分类搜索和客服联系

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-06 17:40:11 +08:00
co-authored by Claude Opus 4.5
parent ae74c515c7
commit 964797d2e9
14 changed files with 4349 additions and 154 deletions
+86 -38
View File
@@ -12,15 +12,19 @@ import {
FileText,
CheckCircle,
X,
Users
Users,
Search,
Building2
} 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 },
{ id: 'AG789012', name: '星耀传媒', companyName: '上海星耀文化传媒有限公司', creatorCount: 50, passRate: 92 },
{ id: 'AG456789', name: '创意无限', companyName: '深圳创意无限广告有限公司', creatorCount: 35, passRate: 88 },
{ id: 'AG123456', name: '美妆达人MCN', companyName: '杭州美妆达人网络科技有限公司', creatorCount: 28, passRate: 82 },
{ id: 'AG111111', name: '蓝海科技', companyName: '北京蓝海数字科技有限公司', creatorCount: 42, passRate: 85 },
{ id: 'AG222222', name: '云创网络', companyName: '杭州云创网络技术有限公司', creatorCount: 30, passRate: 90 },
{ id: 'AG333333', name: '天府传媒', companyName: '成都天府传媒集团有限公司', creatorCount: 25, passRate: 87 },
]
export default function CreateProjectPage() {
@@ -30,6 +34,15 @@ export default function CreateProjectPage() {
const [briefFile, setBriefFile] = useState<File | null>(null)
const [selectedAgencies, setSelectedAgencies] = useState<string[]>([])
const [isSubmitting, setIsSubmitting] = useState(false)
const [agencySearch, setAgencySearch] = useState('')
// 搜索过滤代理商
const filteredAgencies = mockAgencies.filter(agency =>
agencySearch === '' ||
agency.name.toLowerCase().includes(agencySearch.toLowerCase()) ||
agency.id.toLowerCase().includes(agencySearch.toLowerCase()) ||
agency.companyName.toLowerCase().includes(agencySearch.toLowerCase())
)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
@@ -145,41 +158,76 @@ export default function CreateProjectPage() {
{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 className="relative mb-4">
<Search size={18} className="absolute left-4 top-1/2 -translate-y-1/2 text-text-tertiary" />
<input
type="text"
value={agencySearch}
onChange={(e) => setAgencySearch(e.target.value)}
placeholder="搜索代理商名称、ID或公司名..."
className="w-full pl-11 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 className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-80 overflow-y-auto">
{filteredAgencies.length > 0 ? (
filteredAgencies.map((agency) => {
const isSelected = selectedAgencies.includes(agency.id)
return (
<button
key={agency.id}
type="button"
onClick={() => toggleAgency(agency.id)}
className={`p-4 rounded-xl 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 gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
isSelected ? 'bg-accent-indigo' : 'bg-accent-indigo/15'
}`}>
{isSelected ? (
<CheckCircle size={20} className="text-white" />
) : (
<Building2 size={20} className="text-accent-indigo" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-medium text-text-primary">{agency.name}</span>
<span className="text-xs text-text-tertiary font-mono">{agency.id}</span>
</div>
<p className="text-sm text-text-secondary truncate mt-0.5">{agency.companyName}</p>
<div className="flex items-center gap-4 mt-1.5 text-xs text-text-tertiary">
<span className="flex items-center gap-1">
<Users size={12} />
{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>
</div>
{isSelected && (
<CheckCircle size={20} className="text-accent-indigo" />
)}
</div>
</button>
)
})}
</button>
)
})
) : (
<div className="col-span-2 text-center py-8 text-text-tertiary">
<Search size={32} className="mx-auto mb-2 opacity-50" />
<p></p>
</div>
)}
</div>
<p className="text-xs text-text-tertiary mt-3">
"代理商管理"
</p>
</div>
{/* 操作按钮 */}