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

800 lines
33 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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Modal } from '@/components/ui/Modal'
import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag'
import {
Search,
Plus,
Users,
TrendingUp,
TrendingDown,
Copy,
CheckCircle,
Clock,
MoreVertical,
FileText,
Video,
ChevronDown,
ChevronRight,
PlusCircle,
UserPlus,
AlertCircle,
MessageSquareText,
Trash2,
FolderPlus,
X
} from 'lucide-react'
import { getPlatformInfo } from '@/lib/platforms'
// 任务进度阶段
type TaskStage = 'script_pending' | 'script_ai_review' | 'script_agency_review' | 'script_brand_review' |
'video_pending' | 'video_ai_review' | 'video_agency_review' | 'video_brand_review' | 'completed'
// 任务阶段配置
const stageConfig: Record<TaskStage, { label: string; color: string; bgColor: string }> = {
script_pending: { label: '待提交脚本', color: 'text-text-tertiary', bgColor: 'bg-bg-elevated' },
script_ai_review: { label: '脚本AI审核中', color: 'text-accent-indigo', bgColor: 'bg-accent-indigo/15' },
script_agency_review: { label: '脚本代理商审核', color: 'text-purple-400', bgColor: 'bg-purple-500/15' },
script_brand_review: { label: '脚本品牌方终审', color: 'text-accent-blue', bgColor: 'bg-accent-blue/15' },
video_pending: { label: '待提交视频', color: 'text-accent-amber', bgColor: 'bg-accent-amber/15' },
video_ai_review: { label: '视频AI审核中', color: 'text-accent-indigo', bgColor: 'bg-accent-indigo/15' },
video_agency_review: { label: '视频代理商审核', color: 'text-purple-400', bgColor: 'bg-purple-500/15' },
video_brand_review: { label: '视频品牌方终审', color: 'text-accent-blue', bgColor: 'bg-accent-blue/15' },
completed: { label: '已完成', color: 'text-accent-green', bgColor: 'bg-accent-green/15' },
}
// 任务类型
interface CreatorTask {
id: string
name: string
projectName: string
platform: string
stage: TaskStage
appealRemaining: number
appealUsed: number
}
// 达人类型
interface Creator {
id: string
creatorId: string // 达人ID用于邀请和显示
name: string
avatar: string
status: 'active' | 'pending' | 'paused'
projectCount: number
scriptCount: { total: number; passed: number }
videoCount: { total: number; passed: number }
passRate: number
trend: 'up' | 'down' | 'stable'
joinedAt: string
tasks: CreatorTask[]
remark?: string // 备注
}
// 模拟项目列表(用于分配达人)
const mockProjects = [
{ id: 'proj-001', name: 'XX品牌618推广' },
{ id: 'proj-002', name: '口红系列推广' },
{ id: 'proj-003', name: 'XX运动品牌' },
{ id: 'proj-004', name: '护肤品秋季活动' },
]
// 模拟达人列表
const mockCreators: Creator[] = [
{
id: 'c-001',
creatorId: 'CR123456',
name: '小美护肤',
avatar: '小',
status: 'active',
projectCount: 5,
scriptCount: { total: 12, passed: 10 },
videoCount: { total: 10, passed: 8 },
passRate: 85,
trend: 'up',
joinedAt: '2025-08-15',
tasks: [
{ id: 'task-001', name: '夏日护肤推广', projectName: 'XX品牌618', platform: 'douyin', stage: 'video_agency_review', appealRemaining: 1, appealUsed: 0 },
{ id: 'task-002', name: '防晒霜测评', projectName: 'XX品牌618', platform: 'douyin', stage: 'script_brand_review', appealRemaining: 0, appealUsed: 1 },
],
},
{
id: 'c-002',
creatorId: 'CR789012',
name: '美妆Lisa',
avatar: 'L',
status: 'active',
projectCount: 3,
scriptCount: { total: 8, passed: 7 },
videoCount: { total: 6, passed: 5 },
passRate: 80,
trend: 'stable',
joinedAt: '2025-10-20',
tasks: [
{ id: 'task-003', name: '新品口红试色', projectName: '口红系列推广', platform: 'xiaohongshu', stage: 'video_pending', appealRemaining: 2, appealUsed: 0 },
],
},
{
id: 'c-003',
creatorId: 'CR345678',
name: '健身教练王',
avatar: '王',
status: 'active',
projectCount: 2,
scriptCount: { total: 5, passed: 5 },
videoCount: { total: 4, passed: 4 },
passRate: 100,
trend: 'up',
joinedAt: '2025-12-01',
tasks: [
{ id: 'task-004', name: '健身器材使用教程', projectName: 'XX运动品牌', platform: 'bilibili', stage: 'script_ai_review', appealRemaining: 1, appealUsed: 0 },
],
},
{
id: 'c-004',
creatorId: 'CR901234',
name: '时尚达人',
avatar: '时',
status: 'pending',
projectCount: 0,
scriptCount: { total: 0, passed: 0 },
videoCount: { total: 0, passed: 0 },
passRate: 0,
trend: 'stable',
joinedAt: '2026-02-05',
tasks: [],
},
]
function StatusTag({ status }: { status: string }) {
if (status === 'active') return <SuccessTag></SuccessTag>
if (status === 'pending') return <PendingTag></PendingTag>
return <WarningTag></WarningTag>
}
function StageTag({ stage }: { stage: TaskStage }) {
const config = stageConfig[stage]
return (
<span className={`px-2 py-1 rounded-md text-xs font-medium ${config.bgColor} ${config.color}`}>
{config.label}
</span>
)
}
export default function AgencyCreatorsPage() {
const [searchQuery, setSearchQuery] = useState('')
const [showInviteModal, setShowInviteModal] = useState(false)
const [inviteCreatorId, setInviteCreatorId] = useState('')
const [inviteResult, setInviteResult] = useState<{ success: boolean; message: string } | null>(null)
const [expandedCreators, setExpandedCreators] = useState<string[]>([])
const [creators, setCreators] = useState(mockCreators)
const [copiedId, setCopiedId] = useState<string | null>(null)
// 操作菜单状态
const [openMenuId, setOpenMenuId] = useState<string | null>(null)
// 备注弹窗状态
const [remarkModal, setRemarkModal] = useState<{ open: boolean; creator: Creator | null }>({ open: false, creator: null })
const [remarkText, setRemarkText] = useState('')
// 删除确认弹窗状态
const [deleteModal, setDeleteModal] = useState<{ open: boolean; creator: Creator | null }>({ open: false, creator: null })
// 分配项目弹窗状态
const [assignModal, setAssignModal] = useState<{ open: boolean; creator: Creator | null }>({ open: false, creator: null })
const [selectedProject, setSelectedProject] = useState('')
const filteredCreators = creators.filter(creator =>
creator.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
creator.creatorId.toLowerCase().includes(searchQuery.toLowerCase())
)
// 切换展开状态
const toggleExpand = (creatorId: string) => {
setExpandedCreators(prev =>
prev.includes(creatorId)
? prev.filter(id => id !== creatorId)
: [...prev, creatorId]
)
}
// 复制达人ID
const handleCopyCreatorId = async (creatorId: string) => {
await navigator.clipboard.writeText(creatorId)
setCopiedId(creatorId)
setTimeout(() => setCopiedId(null), 2000)
}
// 增加申诉次数
const handleAddAppealQuota = (creatorId: string, taskId: string) => {
setCreators(prev => prev.map(creator => {
if (creator.id === creatorId) {
return {
...creator,
tasks: creator.tasks.map(task => {
if (task.id === taskId) {
return { ...task, appealRemaining: task.appealRemaining + 1 }
}
return task
}),
}
}
return creator
}))
}
// 邀请达人
const handleInvite = () => {
if (!inviteCreatorId.trim()) {
setInviteResult({ success: false, message: '请输入达人ID' })
return
}
// 模拟检查达人ID是否存在
const idPattern = /^CR\d{6}$/
if (!idPattern.test(inviteCreatorId.toUpperCase())) {
setInviteResult({ success: false, message: '达人ID格式错误应为CR+6位数字' })
return
}
// 检查是否已邀请
if (creators.some(c => c.creatorId === inviteCreatorId.toUpperCase())) {
setInviteResult({ success: false, message: '该达人已在您的列表中' })
return
}
// 模拟发送邀请成功
setInviteResult({ success: true, message: `已向达人 ${inviteCreatorId.toUpperCase()} 发送邀请` })
}
const handleCloseInviteModal = () => {
setShowInviteModal(false)
setInviteCreatorId('')
setInviteResult(null)
}
// 打开备注弹窗
const handleOpenRemark = (creator: Creator) => {
setRemarkText(creator.remark || '')
setRemarkModal({ open: true, creator })
setOpenMenuId(null)
}
// 保存备注
const handleSaveRemark = () => {
if (remarkModal.creator) {
setCreators(prev => prev.map(c =>
c.id === remarkModal.creator!.id ? { ...c, remark: remarkText } : c
))
}
setRemarkModal({ open: false, creator: null })
setRemarkText('')
}
// 打开删除确认
const handleOpenDelete = (creator: Creator) => {
setDeleteModal({ open: true, creator })
setOpenMenuId(null)
}
// 确认删除
const handleConfirmDelete = () => {
if (deleteModal.creator) {
setCreators(prev => prev.filter(c => c.id !== deleteModal.creator!.id))
}
setDeleteModal({ open: false, creator: null })
}
// 打开分配项目弹窗
const handleOpenAssign = (creator: Creator) => {
setSelectedProject('')
setAssignModal({ open: true, creator })
setOpenMenuId(null)
}
// 确认分配项目
const handleConfirmAssign = () => {
if (assignModal.creator && selectedProject) {
const project = mockProjects.find(p => p.id === selectedProject)
alert(`已将达人「${assignModal.creator.name}」分配到项目「${project?.name}`)
}
setAssignModal({ open: false, creator: null })
setSelectedProject('')
}
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"></h1>
<p className="text-sm text-text-secondary mt-1"></p>
</div>
<Button onClick={() => setShowInviteModal(true)}>
<Plus size={16} />
</Button>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-text-secondary"></p>
<p className="text-2xl font-bold text-text-primary">{mockCreators.length}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-accent-indigo/20 flex items-center justify-center">
<Users size={20} className="text-accent-indigo" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-text-secondary"></p>
<p className="text-2xl font-bold text-accent-green">{mockCreators.filter(c => c.status === 'active').length}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-accent-green/20 flex items-center justify-center">
<CheckCircle size={20} className="text-accent-green" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-text-secondary"></p>
<p className="text-2xl font-bold text-text-primary">{mockCreators.reduce((sum, c) => sum + c.scriptCount.total, 0)}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center">
<FileText size={20} className="text-purple-400" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="py-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-text-secondary"></p>
<p className="text-2xl font-bold text-text-primary">{mockCreators.reduce((sum, c) => sum + c.videoCount.total, 0)}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center">
<Video size={20} className="text-orange-400" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* 搜索 */}
<div className="relative max-w-md">
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary" />
<input
type="text"
placeholder="搜索达人名称或达人ID..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 border border-border-subtle rounded-xl bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
{/* 达人列表 */}
<Card>
<CardContent className="p-0 overflow-x-auto">
<table className="w-full min-w-[900px]">
<thead>
<tr className="border-b border-border-subtle text-left text-sm text-text-secondary bg-bg-elevated">
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium">ID</th>
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium"></th>
<th className="px-6 py-4 font-medium"></th>
</tr>
</thead>
<tbody>
{filteredCreators.map((creator) => {
const isExpanded = expandedCreators.includes(creator.id)
const hasActiveTasks = creator.tasks.length > 0
return (
<>
<tr key={creator.id} className="border-b border-border-subtle hover:bg-bg-elevated/50">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
{/* 展开按钮 */}
{hasActiveTasks ? (
<button
type="button"
onClick={() => toggleExpand(creator.id)}
className="w-6 h-6 rounded flex items-center justify-center hover:bg-bg-elevated"
>
{isExpanded ? (
<ChevronDown size={16} className="text-text-secondary" />
) : (
<ChevronRight size={16} className="text-text-secondary" />
)}
</button>
) : (
<div className="w-6" />
)}
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-accent-indigo to-purple-500 flex items-center justify-center">
<span className="text-white font-medium">{creator.avatar}</span>
</div>
<div>
<div className="flex items-center gap-2">
<span className="font-medium text-text-primary">{creator.name}</span>
{creator.remark && (
<span className="px-2 py-0.5 text-xs rounded bg-accent-amber/15 text-accent-amber" title={creator.remark}>
</span>
)}
</div>
{creator.remark && (
<p className="text-xs text-text-tertiary mt-0.5 line-clamp-1">{creator.remark}</p>
)}
{hasActiveTasks && (
<button
type="button"
onClick={() => toggleExpand(creator.id)}
className="text-xs text-accent-indigo hover:underline flex items-center gap-1 mt-0.5"
>
{isExpanded ? (
<>
<ChevronDown size={12} />
</>
) : (
<>
<ChevronRight size={12} />
{creator.tasks.length}
</>
)}
</button>
)}
</div>
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<code className="px-2 py-1 rounded bg-bg-elevated text-sm font-mono text-accent-indigo">
{creator.creatorId}
</code>
<button
type="button"
onClick={() => handleCopyCreatorId(creator.creatorId)}
className="p-1 rounded hover:bg-bg-elevated transition-colors"
title="复制达人ID"
>
{copiedId === creator.creatorId ? (
<CheckCircle size={14} className="text-accent-green" />
) : (
<Copy size={14} className="text-text-tertiary" />
)}
</button>
</div>
</td>
<td className="px-6 py-4">
<StatusTag status={creator.status} />
</td>
<td className="px-6 py-4">
<span className="text-text-primary">{creator.scriptCount.passed}</span>
<span className="text-text-tertiary">/{creator.scriptCount.total}</span>
</td>
<td className="px-6 py-4">
<span className="text-text-primary">{creator.videoCount.passed}</span>
<span className="text-text-tertiary">/{creator.videoCount.total}</span>
</td>
<td className="px-6 py-4">
{creator.status === 'active' && creator.passRate > 0 ? (
<div className="flex items-center gap-2">
<span className={`font-medium ${creator.passRate >= 90 ? 'text-accent-green' : creator.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}>
{creator.passRate}%
</span>
{creator.trend === 'up' && <TrendingUp size={14} className="text-accent-green" />}
{creator.trend === 'down' && <TrendingDown size={14} className="text-accent-coral" />}
</div>
) : (
<span className="text-text-tertiary">-</span>
)}
</td>
<td className="px-6 py-4 text-sm text-text-tertiary">{creator.joinedAt}</td>
<td className="px-6 py-4">
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setOpenMenuId(openMenuId === creator.id ? null : creator.id)}
>
<MoreVertical size={16} />
</Button>
{/* 下拉菜单 */}
{openMenuId === creator.id && (
<div className="absolute right-0 top-full mt-1 w-40 bg-bg-card rounded-xl shadow-lg border border-border-subtle z-10 overflow-hidden">
<button
type="button"
onClick={() => handleOpenRemark(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-text-primary hover:bg-bg-elevated flex items-center gap-2"
>
<MessageSquareText size={14} className="text-text-secondary" />
{creator.remark ? '编辑备注' : '添加备注'}
</button>
<button
type="button"
onClick={() => handleOpenAssign(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-text-primary hover:bg-bg-elevated flex items-center gap-2"
>
<FolderPlus size={14} className="text-text-secondary" />
</button>
<button
type="button"
onClick={() => handleOpenDelete(creator)}
className="w-full px-4 py-2.5 text-left text-sm text-accent-coral hover:bg-accent-coral/10 flex items-center gap-2"
>
<Trash2 size={14} />
</button>
</div>
)}
</div>
</td>
</tr>
{/* 展开的任务列表 */}
{isExpanded && hasActiveTasks && (
<tr key={`${creator.id}-tasks`} className="bg-bg-elevated/30">
<td colSpan={8} className="px-6 py-4">
<div className="ml-9 pl-6 border-l-2 border-accent-indigo/30">
<div className="text-sm font-medium text-text-secondary mb-3"></div>
<div className="space-y-2">
{creator.tasks.map(task => {
const taskPlatform = getPlatformInfo(task.platform)
return (
<div key={task.id} className="bg-bg-card rounded-xl overflow-hidden">
{/* 平台顶部条 */}
{taskPlatform && (
<div className={`px-4 py-1.5 ${taskPlatform.bgColor} border-b ${taskPlatform.borderColor} flex items-center gap-1.5`}>
<span className="text-sm">{taskPlatform.icon}</span>
<span className={`text-xs font-medium ${taskPlatform.textColor}`}>{taskPlatform.name}</span>
</div>
)}
<div className="flex items-center justify-between p-4">
<div className="flex items-center gap-4">
<div>
<div className="font-medium text-text-primary">{task.name}</div>
<div className="text-xs text-text-tertiary mt-0.5">: {task.projectName}</div>
</div>
<StageTag stage={task.stage} />
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-3 text-sm">
<span className="text-text-tertiary">:</span>
<span className="text-accent-indigo font-medium">{task.appealRemaining}</span>
<span className="text-text-tertiary">/</span>
<span className="text-text-tertiary"> {task.appealUsed}</span>
</div>
<Button
variant="secondary"
size="sm"
onClick={() => handleAddAppealQuota(creator.id, task.id)}
>
<PlusCircle size={14} />
+1
</Button>
</div>
</div>
</div>
)
})}
</div>
</div>
</td>
</tr>
)}
</>
)
})}
</tbody>
</table>
{filteredCreators.length === 0 && (
<div className="text-center py-12 text-text-tertiary">
<Users size={48} className="mx-auto mb-4 opacity-50" />
<p></p>
</div>
)}
</CardContent>
</Card>
{/* 邀请达人弹窗 */}
<Modal isOpen={showInviteModal} onClose={handleCloseInviteModal} title="邀请达人">
<div className="space-y-4">
<p className="text-text-secondary text-sm">
ID邀请合作ID可在达人的个人中心查看
</p>
<div>
<label className="block text-sm font-medium text-text-primary mb-2">ID</label>
<div className="flex gap-2">
<input
type="text"
value={inviteCreatorId}
onChange={(e) => {
setInviteCreatorId(e.target.value.toUpperCase())
setInviteResult(null)
}}
placeholder="例如: CR123456"
className="flex-1 px-4 py-2.5 border border-border-subtle rounded-xl bg-bg-elevated text-text-primary font-mono focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
<Button variant="secondary" onClick={handleInvite}>
</Button>
</div>
<p className="text-xs text-text-tertiary mt-2">ID格式CR + 6</p>
</div>
{inviteResult && (
<div className={`p-4 rounded-xl flex items-start gap-3 ${
inviteResult.success ? 'bg-accent-green/10 border border-accent-green/20' : 'bg-accent-coral/10 border border-accent-coral/20'
}`}>
{inviteResult.success ? (
<CheckCircle size={18} className="text-accent-green flex-shrink-0 mt-0.5" />
) : (
<AlertCircle size={18} className="text-accent-coral flex-shrink-0 mt-0.5" />
)}
<span className={`text-sm ${inviteResult.success ? 'text-accent-green' : 'text-accent-coral'}`}>
{inviteResult.message}
</span>
</div>
)}
<div className="flex gap-3 justify-end pt-4">
<Button variant="ghost" onClick={handleCloseInviteModal}>
</Button>
<Button
onClick={() => {
if (inviteResult?.success) {
handleCloseInviteModal()
}
}}
disabled={!inviteResult?.success}
>
<UserPlus size={16} />
</Button>
</div>
</div>
</Modal>
{/* 备注弹窗 */}
<Modal
isOpen={remarkModal.open}
onClose={() => { setRemarkModal({ open: false, creator: null }); setRemarkText(''); }}
title={`${remarkModal.creator?.remark ? '编辑' : '添加'}备注 - ${remarkModal.creator?.name}`}
>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-text-primary mb-2"></label>
<textarea
value={remarkText}
onChange={(e) => setRemarkText(e.target.value)}
placeholder="输入备注信息,如达人特点、合作注意事项等..."
className="w-full h-32 px-4 py-3 border border-border-subtle rounded-xl bg-bg-elevated text-text-primary placeholder-text-tertiary resize-none focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
<div className="flex gap-3 justify-end">
<Button variant="ghost" onClick={() => { setRemarkModal({ open: false, creator: null }); setRemarkText(''); }}>
</Button>
<Button onClick={handleSaveRemark}>
<CheckCircle size={16} />
</Button>
</div>
</div>
</Modal>
{/* 删除确认弹窗 */}
<Modal
isOpen={deleteModal.open}
onClose={() => setDeleteModal({ open: false, creator: null })}
title="确认移除达人"
>
<div className="space-y-4">
<div className="p-4 rounded-xl bg-accent-coral/10 border border-accent-coral/20">
<div className="flex items-start gap-3">
<AlertCircle size={20} className="text-accent-coral flex-shrink-0 mt-0.5" />
<div>
<p className="text-text-primary font-medium">{deleteModal.creator?.name}</p>
<p className="text-sm text-text-secondary mt-1">
</p>
</div>
</div>
</div>
<div className="flex gap-3 justify-end">
<Button variant="ghost" onClick={() => setDeleteModal({ open: false, creator: null })}>
</Button>
<Button
variant="secondary"
className="border-accent-coral text-accent-coral hover:bg-accent-coral/10"
onClick={handleConfirmDelete}
>
<Trash2 size={16} />
</Button>
</div>
</div>
</Modal>
{/* 分配项目弹窗 */}
<Modal
isOpen={assignModal.open}
onClose={() => { setAssignModal({ open: false, creator: null }); setSelectedProject(''); }}
title={`分配达人到项目 - ${assignModal.creator?.name}`}
>
<div className="space-y-4">
<p className="text-text-secondary text-sm">
</p>
<div>
<label className="block text-sm font-medium text-text-primary mb-2"></label>
<div className="space-y-2">
{mockProjects.map((project) => (
<label
key={project.id}
className={`flex items-center gap-3 p-4 rounded-xl border cursor-pointer transition-colors ${
selectedProject === project.id
? 'border-accent-indigo bg-accent-indigo/10'
: 'border-border-subtle hover:border-accent-indigo/50'
}`}
>
<input
type="radio"
name="project"
value={project.id}
checked={selectedProject === project.id}
onChange={(e) => setSelectedProject(e.target.value)}
className="w-4 h-4 text-accent-indigo"
/>
<span className="text-text-primary">{project.name}</span>
</label>
))}
</div>
</div>
<div className="flex gap-3 justify-end pt-2">
<Button variant="ghost" onClick={() => { setAssignModal({ open: false, creator: null }); setSelectedProject(''); }}>
</Button>
<Button onClick={handleConfirmAssign} disabled={!selectedProject}>
<FolderPlus size={16} />
</Button>
</div>
</div>
</Modal>
{/* 点击其他地方关闭菜单 */}
{openMenuId && (
<div
className="fixed inset-0 z-0"
onClick={() => setOpenMenuId(null)}
/>
)}
</div>
)
}