feat(agency): 达人管理添加操作菜单功能
操作下拉菜单包含: - 添加/编辑备注:可为达人添加备注信息,列表中显示备注标签和内容 - 分配到项目:选择项目将达人分配进去 - 移除达人:确认后移除该达人(保留历史数据) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9a0e2cac03
commit
9f15709eed
@ -21,7 +21,11 @@ import {
|
||||
ChevronRight,
|
||||
PlusCircle,
|
||||
UserPlus,
|
||||
AlertCircle
|
||||
AlertCircle,
|
||||
MessageSquareText,
|
||||
Trash2,
|
||||
FolderPlus,
|
||||
X
|
||||
} from 'lucide-react'
|
||||
|
||||
// 任务进度阶段
|
||||
@ -65,8 +69,17 @@ interface Creator {
|
||||
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[] = [
|
||||
{
|
||||
@ -158,6 +171,20 @@ export default function AgencyCreatorsPage() {
|
||||
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())
|
||||
@ -227,6 +254,55 @@ export default function AgencyCreatorsPage() {
|
||||
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">
|
||||
{/* 页面标题 */}
|
||||
@ -355,7 +431,17 @@ export default function AgencyCreatorsPage() {
|
||||
<span className="text-white font-medium">{creator.avatar}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-text-primary">{creator.name}</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"
|
||||
@ -423,9 +509,44 @@ export default function AgencyCreatorsPage() {
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-text-tertiary">{creator.joinedAt}</td>
|
||||
<td className="px-6 py-4">
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreVertical size={16} />
|
||||
</Button>
|
||||
<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>
|
||||
{/* 展开的任务列表 */}
|
||||
@ -542,6 +663,123 @@ export default function AgencyCreatorsPage() {
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user