主要更新: - 新增 FilePreview 通用组件,支持视频/图片/PDF 内嵌预览 - 审核详情页添加文件信息卡片、预览/下载功能 - 审核列表和详情页添加申诉标识和申诉理由显示 - 完善三端消息通知系统(达人/代理商/品牌) - 新增达人 Brief 查看页面 - 新增品牌方消息中心页面 - 创建后端开发备忘文档 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
665 lines
22 KiB
TypeScript
665 lines
22 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
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' // 系统通知
|
||
|
||
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<MessageType, {
|
||
icon: React.ElementType
|
||
iconColor: string
|
||
bgColor: string
|
||
}> = {
|
||
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' },
|
||
}
|
||
|
||
// 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]
|
||
const Icon = config.icon
|
||
|
||
return (
|
||
<div
|
||
className={cn(
|
||
'rounded-xl p-4 flex gap-4 transition-colors',
|
||
message.read
|
||
? 'bg-transparent border border-bg-elevated'
|
||
: 'bg-bg-elevated',
|
||
// 新任务和邀请类型不整体点击跳转
|
||
message.type !== 'new_task' && message.type !== 'invite' && message.taskId ? 'cursor-pointer' : ''
|
||
)}
|
||
onClick={() => {
|
||
if (message.type !== 'new_task' && message.type !== 'invite') {
|
||
onRead()
|
||
if (message.taskId) onNavigate()
|
||
}
|
||
}}
|
||
>
|
||
{/* 图标 */}
|
||
<div className={cn('w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0', config.bgColor)}>
|
||
<Icon size={24} className={config.iconColor} />
|
||
</div>
|
||
|
||
{/* 内容 */}
|
||
<div className="flex-1 flex flex-col gap-2">
|
||
{/* 头部 */}
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[15px] font-semibold text-text-primary">{message.title}</span>
|
||
<span className="text-xs text-text-secondary">{message.time}</span>
|
||
</div>
|
||
|
||
{/* 描述 */}
|
||
<p className="text-sm text-text-secondary leading-relaxed">{message.content}</p>
|
||
|
||
{/* 新任务类型的操作按钮 */}
|
||
{message.type === 'new_task' && message.taskId && (
|
||
<div className="flex items-center gap-3 pt-2">
|
||
<button
|
||
type="button"
|
||
className="px-4 py-2 rounded-md bg-accent-indigo text-white text-sm font-medium hover:bg-accent-indigo/90 transition-colors"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
onRead()
|
||
onViewBrief?.()
|
||
}}
|
||
>
|
||
查看任务要求
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="px-4 py-2 rounded-md border border-border-subtle text-text-secondary text-sm font-medium hover:bg-bg-elevated transition-colors"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
onRead()
|
||
onNavigate()
|
||
}}
|
||
>
|
||
开始任务
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* 邀请类型的操作按钮 */}
|
||
{message.hasActions && (
|
||
<div className="flex items-center gap-3 pt-2">
|
||
<button
|
||
type="button"
|
||
className="px-4 py-2 rounded-md bg-accent-indigo text-white text-sm font-medium hover:bg-accent-indigo/90 transition-colors"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
onAcceptInvite?.()
|
||
}}
|
||
>
|
||
接受邀请
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="px-4 py-2 rounded-md border border-border-subtle text-text-secondary text-sm font-medium hover:bg-bg-elevated transition-colors"
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
onIgnoreInvite?.()
|
||
}}
|
||
>
|
||
暂时忽略
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 未读标记 */}
|
||
{!message.read && (
|
||
<div className="w-2.5 h-2.5 rounded-full bg-accent-indigo flex-shrink-0 mt-1" />
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 邀请确认弹窗
|
||
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 (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||
<div className="absolute inset-0 bg-black/50" onClick={onClose} />
|
||
<div className="relative bg-bg-card rounded-2xl p-6 w-full max-w-md mx-4 card-shadow">
|
||
<h3 className="text-xl font-bold text-text-primary mb-2">
|
||
{isAccept ? '确认接受邀请' : '确认忽略邀请'}
|
||
</h3>
|
||
<p className="text-sm text-text-secondary mb-6">
|
||
{isAccept
|
||
? `您确定要接受「${agencyName}」的签约邀请吗?接受后您将成为该代理商的签约达人,可以接收推广任务。`
|
||
: `您确定要忽略「${agencyName}」的邀请吗?您可以稍后在消息中心重新查看此邀请。`}
|
||
</p>
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="flex-1 py-3 rounded-xl bg-bg-elevated text-text-primary text-sm font-medium"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={onConfirm}
|
||
className={cn(
|
||
'flex-1 py-3 rounded-xl text-sm font-semibold',
|
||
isAccept ? 'bg-accent-indigo text-white' : 'bg-accent-coral text-white'
|
||
)}
|
||
>
|
||
{isAccept ? '确认接受' : '确认忽略'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 成功提示弹窗
|
||
function SuccessModal({
|
||
isOpen,
|
||
message,
|
||
onClose,
|
||
}: {
|
||
isOpen: boolean
|
||
message: string
|
||
onClose: () => void
|
||
}) {
|
||
if (!isOpen) return null
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||
<div className="absolute inset-0 bg-black/50" onClick={onClose} />
|
||
<div className="relative bg-bg-card rounded-2xl p-8 w-full max-w-sm mx-4 card-shadow flex flex-col items-center gap-4">
|
||
<div className="w-16 h-16 rounded-full bg-accent-green/15 flex items-center justify-center">
|
||
<CheckCircle className="w-8 h-8 text-accent-green" />
|
||
</div>
|
||
<p className="text-base font-semibold text-text-primary text-center">{message}</p>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="px-8 py-2.5 rounded-xl bg-accent-indigo text-white text-sm font-medium"
|
||
>
|
||
我知道了
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function CreatorMessagesPage() {
|
||
const router = useRouter()
|
||
const [messages, setMessages] = useState(mockMessages)
|
||
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 markAsRead = (id: string) => {
|
||
setMessages(prev => prev.map(msg =>
|
||
msg.id === id ? { ...msg, read: true } : msg
|
||
))
|
||
}
|
||
|
||
const markAllAsRead = () => {
|
||
setMessages(prev => prev.map(msg => ({ ...msg, read: true })))
|
||
}
|
||
|
||
// 根据消息类型跳转到对应页面
|
||
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 (
|
||
<ResponsiveLayout role="creator">
|
||
<div className="flex flex-col gap-6 h-full">
|
||
{/* 顶部栏 */}
|
||
<div className="flex items-center justify-between flex-wrap gap-4">
|
||
<div className="flex flex-col gap-1">
|
||
<h1 className="text-xl lg:text-[24px] font-bold text-text-primary">消息中心</h1>
|
||
<p className="text-sm text-text-secondary">查看任务通知、审核结果和申诉反馈</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
className="px-4 py-2.5 rounded-lg bg-bg-elevated text-text-primary text-sm"
|
||
onClick={markAllAsRead}
|
||
>
|
||
全部标为已读
|
||
</button>
|
||
</div>
|
||
|
||
{/* 消息列表 - 可滚动 */}
|
||
<div className="bg-bg-card rounded-2xl p-4 lg:p-6 flex-1 overflow-hidden">
|
||
<div className="flex flex-col gap-4 h-full overflow-y-auto pr-2">
|
||
{messages.map((message) => (
|
||
<MessageCard
|
||
key={message.id}
|
||
message={message}
|
||
onRead={() => 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)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 确认弹窗 */}
|
||
<InviteConfirmModal
|
||
isOpen={confirmModal.isOpen}
|
||
type={confirmModal.type}
|
||
agencyName={getAgencyName()}
|
||
onClose={() => setConfirmModal({ ...confirmModal, isOpen: false })}
|
||
onConfirm={handleConfirmAction}
|
||
/>
|
||
|
||
{/* 成功提示弹窗 */}
|
||
<SuccessModal
|
||
isOpen={successModal.isOpen}
|
||
message={successModal.message}
|
||
onClose={() => setSuccessModal({ ...successModal, isOpen: false })}
|
||
/>
|
||
</ResponsiveLayout>
|
||
)
|
||
}
|