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,
|
ChevronRight,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
AlertCircle
|
AlertCircle,
|
||||||
|
MessageSquareText,
|
||||||
|
Trash2,
|
||||||
|
FolderPlus,
|
||||||
|
X
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
// 任务进度阶段
|
// 任务进度阶段
|
||||||
@ -65,8 +69,17 @@ interface Creator {
|
|||||||
trend: 'up' | 'down' | 'stable'
|
trend: 'up' | 'down' | 'stable'
|
||||||
joinedAt: string
|
joinedAt: string
|
||||||
tasks: CreatorTask[]
|
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[] = [
|
const mockCreators: Creator[] = [
|
||||||
{
|
{
|
||||||
@ -158,6 +171,20 @@ export default function AgencyCreatorsPage() {
|
|||||||
const [creators, setCreators] = useState(mockCreators)
|
const [creators, setCreators] = useState(mockCreators)
|
||||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
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 =>
|
const filteredCreators = creators.filter(creator =>
|
||||||
creator.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
creator.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
creator.creatorId.toLowerCase().includes(searchQuery.toLowerCase())
|
creator.creatorId.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
@ -227,6 +254,55 @@ export default function AgencyCreatorsPage() {
|
|||||||
setInviteResult(null)
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
@ -355,7 +431,17 @@ export default function AgencyCreatorsPage() {
|
|||||||
<span className="text-white font-medium">{creator.avatar}</span>
|
<span className="text-white font-medium">{creator.avatar}</span>
|
||||||
</div>
|
</div>
|
||||||
<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 && (
|
{hasActiveTasks && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -423,9 +509,44 @@ export default function AgencyCreatorsPage() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-text-tertiary">{creator.joinedAt}</td>
|
<td className="px-6 py-4 text-sm text-text-tertiary">{creator.joinedAt}</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<Button variant="ghost" size="sm">
|
<div className="relative">
|
||||||
<MoreVertical size={16} />
|
<Button
|
||||||
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/* 展开的任务列表 */}
|
{/* 展开的任务列表 */}
|
||||||
@ -542,6 +663,123 @@ export default function AgencyCreatorsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user