'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { USE_MOCK } from '@/contexts/AuthContext' import { api } from '@/lib/api' import { UserPlus, ClipboardList, CheckCircle, PenLine, ScanSearch, Building2, XCircle, BadgeCheck, Video, MessageCircle, CalendarClock, FileText, Bell, Eye, Clock } from 'lucide-react' import { Button } from '@/components/ui/Button' import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' import { cn } from '@/lib/utils' // 消息类型 type MessageType = | 'invite' // 代理商邀请 | 'new_task' // 新任务分配 | 'pass' // 审核通过 | 'need_fix' // 需要修改 | 'ai_complete' // AI审核完成 | 'agency_pass' // 代理商审核通过 | 'agency_reject' // 代理商审核驳回 | 'brand_pass' // 品牌方审核通过 | 'brand_reject' // 品牌方审核驳回 | 'video_ai' // 视频AI审核完成 | 'appeal' // 申诉结果(通用) | 'appeal_success' // 申诉成功(违规被撤销) | 'appeal_failed' // 申诉失败(维持原判) | 'appeal_quota_approved' // 申请增加申诉次数成功 | 'appeal_quota_rejected' // 申请增加申诉次数失败 | 'video_agency_reject' // 视频代理商驳回 | 'video_brand_reject' // 视频品牌方驳回 | 'task_deadline' // 任务截止提醒 | 'brief_updated' // Brief更新通知 | 'system_notice' // 系统通知 | 'reject' // 审核驳回 | 'force_pass' // 强制通过 | 'approve' // 审核批准 type Message = { id: string type: MessageType title: string content: string time: string read: boolean taskId?: string hasActions?: boolean // 是否有操作按钮(邀请类型) agencyName?: string // 代理商名称(新任务类型) taskName?: string // 任务名称(新任务类型) } // 消息配置 const messageConfig: Record = { invite: { icon: UserPlus, iconColor: 'text-purple-400', bgColor: 'bg-purple-500/20' }, new_task: { icon: ClipboardList, iconColor: 'text-accent-indigo', bgColor: 'bg-accent-indigo/20' }, pass: { icon: CheckCircle, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, need_fix: { icon: PenLine, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, ai_complete: { icon: ScanSearch, iconColor: 'text-accent-indigo', bgColor: 'bg-accent-indigo/20' }, agency_pass: { icon: Building2, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, agency_reject: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, brand_pass: { icon: BadgeCheck, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, brand_reject: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, video_ai: { icon: Video, iconColor: 'text-accent-indigo', bgColor: 'bg-accent-indigo/20' }, appeal: { icon: MessageCircle, iconColor: 'text-accent-blue', bgColor: 'bg-accent-blue/20' }, appeal_success: { icon: CheckCircle, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, appeal_failed: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, appeal_quota_approved: { icon: CheckCircle, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, appeal_quota_rejected: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, video_agency_reject: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, video_brand_reject: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, task_deadline: { icon: CalendarClock, iconColor: 'text-orange-400', bgColor: 'bg-orange-500/20' }, brief_updated: { icon: FileText, iconColor: 'text-accent-indigo', bgColor: 'bg-accent-indigo/20' }, system_notice: { icon: Bell, iconColor: 'text-text-secondary', bgColor: 'bg-bg-elevated' }, reject: { icon: XCircle, iconColor: 'text-accent-coral', bgColor: 'bg-accent-coral/20' }, force_pass: { icon: CheckCircle, iconColor: 'text-accent-amber', bgColor: 'bg-accent-amber/20' }, approve: { icon: CheckCircle, iconColor: 'text-accent-green', bgColor: 'bg-accent-green/20' }, } // 12条消息数据 const mockMessages: Message[] = [ { id: 'msg-001', type: 'invite', title: '代理商邀请', content: '「星辰传媒」邀请您成为签约达人,加入后可接收该代理商分配的推广任务', time: '5分钟前', read: false, hasActions: true, }, { id: 'msg-002', type: 'new_task', title: '新任务分配', content: '您被「星辰传媒」安排了新任务【XX品牌618推广】,请先查看任务要求后再提交脚本', time: '10分钟前', read: false, taskId: 'task-001', agencyName: '星辰传媒', taskName: 'XX品牌618推广', }, { id: 'msg-003', type: 'pass', title: '审核通过', content: '恭喜!您的视频【AA数码新品发布】已通过审核,可安排发布', time: '2小时前', read: true, taskId: 'task-004', }, { id: 'msg-004', type: 'need_fix', title: '需要修改', content: '您的视频【ZZ饮品夏日】有2处需修改:00:15竞品露出、00:42口播违规词,点击查看详情', time: '昨天 14:30', read: true, taskId: 'task-003', }, { id: 'msg-005', type: 'ai_complete', title: 'AI审核完成', content: '您的脚本【CC服装春季款】AI预审已完成,已进入代理商审核', time: '30分钟前', read: true, taskId: 'task-006', }, { id: 'msg-006', type: 'agency_pass', title: '代理商审核通过', content: '您的脚本【DD家电测评】已通过代理商审核,等待品牌方终审', time: '1小时前', read: true, taskId: 'task-007', }, { id: 'msg-007', type: 'agency_reject', title: '代理商审核驳回', content: '您的脚本【HH美妆代言】被代理商驳回,原因:品牌调性不符,请修改后重新提交', time: '2小时前', read: true, taskId: 'task-010', }, { id: 'msg-008', type: 'brand_pass', title: '品牌方审核通过', content: '恭喜!您的脚本【EE食品试吃】已通过品牌方终审,请在7天内上传视频', time: '昨天 14:30', read: true, taskId: 'task-008', }, { id: 'msg-009', type: 'brand_reject', title: '品牌方审核驳回', content: '您的脚本【II数码配件】被品牌方驳回,原因:产品卖点不够突出,请修改后重新提交', time: '昨天 16:45', read: true, taskId: 'task-011', }, { id: 'msg-010', type: 'video_ai', title: '视频AI审核完成', content: '您的视频【JJ旅行vlog】AI预审已完成,已进入代理商审核环节', time: '今天 09:15', read: true, taskId: 'task-013', }, { id: 'msg-011', type: 'appeal_success', title: '申诉成功', content: '您对【ZZ饮品夏日】的申诉已通过,违规已被撤销,申诉次数已返还', time: '2天前', read: false, taskId: 'task-003', }, { id: 'msg-011b', type: 'appeal_failed', title: '申诉未通过', content: '您对【HH美妆代言】的申诉未通过,维持原审核结果,请根据建议修改后重新提交', time: '2天前', read: true, taskId: 'task-011', }, { id: 'msg-011c', type: 'appeal_quota_approved', title: '申诉次数申请通过', content: '您申请增加【AA数码新品发布】的申诉次数已被「星辰传媒」批准,当前可用申诉次数 +1', time: '3天前', read: false, taskId: 'task-004', }, { id: 'msg-011d', type: 'appeal_quota_rejected', title: '申诉次数申请被拒', content: '您申请增加【BB运动饮料】的申诉次数被「星辰传媒」拒绝,请仔细阅读驳回原因后修改内容', time: '3天前', read: true, taskId: 'task-005', }, { id: 'msg-012', type: 'video_agency_reject', title: '视频代理商审核驳回', content: '您的视频【KK宠物用品】被代理商驳回,原因:背景音乐版权问题,请修改后重新提交', time: '今天 11:20', read: true, taskId: 'task-014', }, { id: 'msg-013', type: 'video_brand_reject', title: '视频品牌方审核驳回', content: '您的视频【MM厨房电器】被品牌方驳回,原因:产品使用场景不够真实,请修改后重新提交', time: '昨天 18:30', read: true, taskId: 'task-015', }, { id: 'msg-014', type: 'task_deadline', title: '任务即将截止', content: '您的任务【XX品牌618推广】将于3天后截止,请尽快提交脚本', time: '今天 08:00', read: false, taskId: 'task-001', }, { id: 'msg-015', type: 'brief_updated', title: 'Brief更新通知', content: '【XX品牌618推广】的Brief要求已更新,新增2个必选卖点,请查看最新要求', time: '昨天 15:00', read: true, taskId: 'task-001', }, { id: 'msg-017', type: 'system_notice', title: '系统通知', content: '平台违禁词库已更新,请在创作时注意避免使用新增的违禁词', time: '4天前', read: true, }, ] // 消息卡片组件 function MessageCard({ message, onRead, onNavigate, onViewBrief, onAcceptInvite, onIgnoreInvite, }: { message: Message onRead: () => void onNavigate: () => void onViewBrief?: () => void onAcceptInvite?: () => void onIgnoreInvite?: () => void }) { const config = messageConfig[message.type] || messageConfig.system_notice const Icon = config.icon return (
{ if (message.type !== 'new_task' && message.type !== 'invite') { onRead() if (message.taskId) onNavigate() } }} > {/* 图标 */}
{/* 内容 */}
{/* 头部 */}
{message.title} {message.time}
{/* 描述 */}

{message.content}

{/* 新任务类型的操作按钮 */} {message.type === 'new_task' && message.taskId && (
)} {/* 邀请类型的操作按钮 */} {message.hasActions && (
)}
{/* 未读标记 */} {!message.read && (
)}
) } // 邀请确认弹窗 function InviteConfirmModal({ isOpen, type, agencyName, onClose, onConfirm, }: { isOpen: boolean type: 'accept' | 'ignore' agencyName: string onClose: () => void onConfirm: () => void }) { if (!isOpen) return null const isAccept = type === 'accept' return (

{isAccept ? '确认接受邀请' : '确认忽略邀请'}

{isAccept ? `您确定要接受「${agencyName}」的签约邀请吗?接受后您将成为该代理商的签约达人,可以接收推广任务。` : `您确定要忽略「${agencyName}」的邀请吗?您可以稍后在消息中心重新查看此邀请。`}

) } // 成功提示弹窗 function SuccessModal({ isOpen, message, onClose, }: { isOpen: boolean message: string onClose: () => void }) { if (!isOpen) return null return (

{message}

) } export default function CreatorMessagesPage() { const router = useRouter() const [messages, setMessages] = useState(mockMessages) const [loading, setLoading] = useState(true) const [confirmModal, setConfirmModal] = useState<{ isOpen: boolean; type: 'accept' | 'ignore'; messageId: string }>({ isOpen: false, type: 'accept', messageId: '', }) const [successModal, setSuccessModal] = useState<{ isOpen: boolean; message: string }>({ isOpen: false, message: '', }) const loadData = useCallback(async () => { if (USE_MOCK) { setMessages(mockMessages) setLoading(false) return } try { const res = await api.getMessages({ page: 1, page_size: 50 }) const mapped: Message[] = res.items.map(item => ({ id: item.id, type: (item.type || 'system_notice') as MessageType, title: item.title, content: item.content, time: item.created_at ? new Date(item.created_at).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '', read: item.is_read, taskId: item.related_task_id || undefined, })) setMessages(mapped) } catch { // 加载失败保持 mock 数据 } finally { setLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) const markAsRead = async (id: string) => { setMessages(prev => prev.map(msg => msg.id === id ? { ...msg, read: true } : msg )) if (!USE_MOCK) { try { await api.markMessageAsRead(id) } catch {} } } const markAllAsRead = async () => { setMessages(prev => prev.map(msg => ({ ...msg, read: true }))) if (!USE_MOCK) { try { await api.markAllMessagesAsRead() } catch {} } } // 根据消息类型跳转到对应页面 const navigateByMessage = (message: Message) => { // 标记已读 markAsRead(message.id) // 根据消息类型决定跳转目标 switch (message.type) { case 'invite': // 邀请消息不跳转,在卡片内有操作按钮 break case 'new_task': // 新任务 -> 跳转到Brief查看页 if (message.taskId) router.push(`/creator/task/${message.taskId}/brief`) break case 'task_deadline': // 任务截止提醒 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'brief_updated': // Brief更新 -> 跳转到Brief查看页 if (message.taskId) router.push(`/creator/task/${message.taskId}/brief`) break case 'pass': // 审核通过 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'need_fix': // 需要修改 -> 跳转到任务详情(查看问题) if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'ai_complete': // AI审核完成 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'agency_pass': // 代理商审核通过 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'agency_reject': // 代理商审核驳回 -> 跳转到任务详情(查看驳回原因) if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'brand_pass': // 品牌方审核通过 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'brand_reject': // 品牌方审核驳回 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'video_ai': // 视频AI审核完成 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'appeal': // 申诉结果 -> 跳转到申诉中心 router.push('/creator/appeals') break case 'appeal_success': case 'appeal_failed': // 申诉成功/失败 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) else router.push('/creator/appeals') break case 'appeal_quota_approved': case 'appeal_quota_rejected': // 申诉次数申请结果 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) else router.push('/creator/appeal-quota') break case 'video_agency_reject': // 视频代理商驳回 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break case 'video_brand_reject': // 视频品牌方驳回 -> 跳转到任务详情 if (message.taskId) router.push(`/creator/task/${message.taskId}`) break default: if (message.taskId) router.push(`/creator/task/${message.taskId}`) } } const handleAcceptInvite = (messageId: string) => { setConfirmModal({ isOpen: true, type: 'accept', messageId }) } const handleIgnoreInvite = (messageId: string) => { setConfirmModal({ isOpen: true, type: 'ignore', messageId }) } const handleConfirmAction = () => { const { type, messageId } = confirmModal setConfirmModal({ ...confirmModal, isOpen: false }) // 更新消息状态 setMessages(prev => prev.map(msg => msg.id === messageId ? { ...msg, hasActions: false, read: true } : msg )) // 显示成功提示 setSuccessModal({ isOpen: true, message: type === 'accept' ? '已成功接受邀请!您现在可以接收该代理商分配的推广任务了。' : '已忽略该邀请。如需重新查看,请联系代理商。', }) } // 获取当前确认弹窗的代理商名称 const getAgencyName = () => { const message = messages.find(m => m.id === confirmModal.messageId) if (message?.content.includes('「')) { const match = message.content.match(/「([^」]+)」/) return match ? match[1] : '该代理商' } return '该代理商' } return (
{/* 顶部栏 */}

消息中心

查看任务通知、审核结果和申诉反馈

{/* 消息列表 - 可滚动 */}
{messages.map((message) => ( markAsRead(message.id)} onNavigate={() => navigateByMessage(message)} onViewBrief={() => { if (message.taskId) router.push(`/creator/task/${message.taskId}/brief`) }} onAcceptInvite={() => handleAcceptInvite(message.id)} onIgnoreInvite={() => handleIgnoreInvite(message.id)} /> ))}
{/* 确认弹窗 */} setConfirmModal({ ...confirmModal, isOpen: false })} onConfirm={handleConfirmAction} /> {/* 成功提示弹窗 */} setSuccessModal({ ...successModal, isOpen: false })} />
) }