feat: 完善审核台文件预览与消息通知系统
主要更新: - 新增 FilePreview 通用组件,支持视频/图片/PDF 内嵌预览 - 审核详情页添加文件信息卡片、预览/下载功能 - 审核列表和详情页添加申诉标识和申诉理由显示 - 完善三端消息通知系统(达人/代理商/品牌) - 新增达人 Brief 查看页面 - 新增品牌方消息中心页面 - 创建后端开发备忘文档 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
bbc8a4f641
commit
a5a005db0c
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
||||
@@ -15,14 +16,37 @@ import {
|
||||
Clock,
|
||||
Check,
|
||||
MoreVertical,
|
||||
PlusCircle
|
||||
PlusCircle,
|
||||
UserCheck,
|
||||
UserX,
|
||||
Bot,
|
||||
Settings,
|
||||
CalendarClock,
|
||||
Building2,
|
||||
Eye
|
||||
} from 'lucide-react'
|
||||
import { getPlatformInfo } from '@/lib/platforms'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// 消息类型
|
||||
type MessageType =
|
||||
| 'appeal_quota_request' // 达人申请增加申诉次数
|
||||
| 'task_submitted' // 达人提交了脚本/视频
|
||||
| 'review_complete' // 品牌终审通过
|
||||
| 'review_rejected' // 品牌终审驳回
|
||||
| 'new_project' // 被品牌邀请参与项目
|
||||
| 'warning' // 风险预警
|
||||
| 'creator_accept' // 达人接受签约邀请
|
||||
| 'creator_reject' // 达人拒绝签约邀请
|
||||
| 'ai_review_complete' // AI审核完成,待代理商审核
|
||||
| 'brand_config_updated' // 品牌方更新了配置
|
||||
| 'task_deadline' // 任务截止提醒
|
||||
| 'brand_brief_updated' // 品牌方更新了Brief
|
||||
| 'system_notice' // 系统通知
|
||||
|
||||
interface Message {
|
||||
id: string
|
||||
type: string
|
||||
type: MessageType
|
||||
title: string
|
||||
content: string
|
||||
time: string
|
||||
@@ -31,6 +55,11 @@ interface Message {
|
||||
iconColor: string
|
||||
bgColor: string
|
||||
platform?: string
|
||||
taskId?: string
|
||||
projectId?: string
|
||||
creatorName?: string
|
||||
hasAction?: boolean
|
||||
actionType?: 'review' | 'view' | 'config'
|
||||
// 申诉次数请求专用字段
|
||||
appealRequest?: {
|
||||
creatorName: string
|
||||
@@ -62,18 +91,48 @@ const mockMessages: Message[] = [
|
||||
},
|
||||
{
|
||||
id: 'msg-002',
|
||||
type: 'ai_review_complete',
|
||||
title: 'AI审核完成',
|
||||
content: '达人「小美护肤」的脚本【夏日护肤推广】已通过AI审核,请及时进行人工审核',
|
||||
time: '10分钟前',
|
||||
read: false,
|
||||
icon: Bot,
|
||||
iconColor: 'text-accent-indigo',
|
||||
bgColor: 'bg-accent-indigo/20',
|
||||
platform: 'xiaohongshu',
|
||||
taskId: 'task-006',
|
||||
hasAction: true,
|
||||
actionType: 'review',
|
||||
},
|
||||
{
|
||||
id: 'msg-003',
|
||||
type: 'creator_accept',
|
||||
title: '达人已签约',
|
||||
content: '达人「美妆达人小王」已接受您的签约邀请,可以开始分配任务',
|
||||
time: '20分钟前',
|
||||
read: false,
|
||||
icon: UserCheck,
|
||||
iconColor: 'text-accent-green',
|
||||
bgColor: 'bg-accent-green/20',
|
||||
creatorName: '美妆达人小王',
|
||||
},
|
||||
{
|
||||
id: 'msg-004',
|
||||
type: 'task_submitted',
|
||||
title: '新脚本提交',
|
||||
content: '达人「小美护肤」提交了「夏日护肤推广脚本」,请及时审核。',
|
||||
time: '10分钟前',
|
||||
content: '达人「小美护肤」提交了「夏日护肤推广脚本」,请及时审核',
|
||||
time: '30分钟前',
|
||||
read: false,
|
||||
icon: FileText,
|
||||
iconColor: 'text-accent-indigo',
|
||||
bgColor: 'bg-accent-indigo/20',
|
||||
platform: 'xiaohongshu',
|
||||
taskId: 'task-006',
|
||||
hasAction: true,
|
||||
actionType: 'review',
|
||||
},
|
||||
{
|
||||
id: 'msg-003',
|
||||
id: 'msg-005',
|
||||
type: 'appeal_quota_request',
|
||||
title: '申诉次数申请',
|
||||
content: '达人「美妆达人小王」申请增加「双11护肤品种草」的申诉次数',
|
||||
@@ -91,75 +150,165 @@ const mockMessages: Message[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'msg-004',
|
||||
id: 'msg-006',
|
||||
type: 'task_deadline',
|
||||
title: '任务即将截止',
|
||||
content: '【XX品牌618推广】任务将于3天后截止,还有5位达人未提交脚本',
|
||||
time: '1小时前',
|
||||
read: false,
|
||||
icon: CalendarClock,
|
||||
iconColor: 'text-orange-400',
|
||||
bgColor: 'bg-orange-500/20',
|
||||
platform: 'douyin',
|
||||
hasAction: true,
|
||||
actionType: 'view',
|
||||
},
|
||||
{
|
||||
id: 'msg-007',
|
||||
type: 'review_complete',
|
||||
title: '品牌终审通过',
|
||||
content: '「新品口红试色」视频已通过品牌方终审。',
|
||||
content: '【XX品牌618推广】达人「美妆小红」的视频「新品口红试色」已通过品牌方终审,可通知达人发布',
|
||||
time: '1小时前',
|
||||
read: false,
|
||||
icon: CheckCircle,
|
||||
iconColor: 'text-accent-green',
|
||||
bgColor: 'bg-accent-green/20',
|
||||
platform: 'xiaohongshu',
|
||||
taskId: 'task-004',
|
||||
},
|
||||
{
|
||||
id: 'msg-005',
|
||||
id: 'msg-008',
|
||||
type: 'review_rejected',
|
||||
title: '品牌终审驳回',
|
||||
content: '「健身器材开箱」视频被品牌方驳回,原因:违禁词使用。',
|
||||
content: '【BB运动饮料】达人「健身教练王」的视频「健身器材开箱」被品牌方驳回,原因:违禁词使用,请通知达人修改',
|
||||
time: '2小时前',
|
||||
read: false,
|
||||
icon: XCircle,
|
||||
iconColor: 'text-accent-coral',
|
||||
bgColor: 'bg-accent-coral/20',
|
||||
platform: 'bilibili',
|
||||
taskId: 'task-010',
|
||||
},
|
||||
{
|
||||
id: 'msg-006',
|
||||
id: 'msg-009',
|
||||
type: 'brand_config_updated',
|
||||
title: '品牌规则更新',
|
||||
content: '品牌方「XX护肤品牌」更新了违禁词配置,新增8个违禁词',
|
||||
time: '3小时前',
|
||||
read: true,
|
||||
icon: Settings,
|
||||
iconColor: 'text-accent-amber',
|
||||
bgColor: 'bg-accent-amber/20',
|
||||
hasAction: true,
|
||||
actionType: 'config',
|
||||
},
|
||||
{
|
||||
id: 'msg-010',
|
||||
type: 'brand_brief_updated',
|
||||
title: 'Brief更新通知',
|
||||
content: '品牌方更新了【XX品牌618推广】的Brief要求,请查看最新内容',
|
||||
time: '4小时前',
|
||||
read: true,
|
||||
icon: FileText,
|
||||
iconColor: 'text-accent-indigo',
|
||||
bgColor: 'bg-accent-indigo/20',
|
||||
projectId: 'proj-001',
|
||||
hasAction: true,
|
||||
actionType: 'view',
|
||||
},
|
||||
{
|
||||
id: 'msg-011',
|
||||
type: 'creator_reject',
|
||||
title: '达人已拒绝',
|
||||
content: '达人「时尚博主Anna」拒绝了您的签约邀请',
|
||||
time: '昨天 14:30',
|
||||
read: true,
|
||||
icon: UserX,
|
||||
iconColor: 'text-accent-coral',
|
||||
bgColor: 'bg-accent-coral/20',
|
||||
creatorName: '时尚博主Anna',
|
||||
},
|
||||
{
|
||||
id: 'msg-012',
|
||||
type: 'new_project',
|
||||
title: '新项目邀请',
|
||||
content: '您被邀请参与「XX品牌新品推广」项目,请配置 Brief。',
|
||||
content: '您被邀请参与「XX品牌新品推广」项目,请配置 Brief',
|
||||
time: '昨天',
|
||||
read: true,
|
||||
icon: Users,
|
||||
icon: Building2,
|
||||
iconColor: 'text-purple-400',
|
||||
bgColor: 'bg-purple-500/20',
|
||||
platform: 'douyin',
|
||||
projectId: 'proj-001',
|
||||
hasAction: true,
|
||||
actionType: 'config',
|
||||
},
|
||||
{
|
||||
id: 'msg-007',
|
||||
id: 'msg-013',
|
||||
type: 'warning',
|
||||
title: '风险预警',
|
||||
content: '达人「美妆Lisa」连续2次提交被驳回,建议关注。',
|
||||
content: '达人「美妆Lisa」连续2次提交被驳回,建议关注并提供指导',
|
||||
time: '昨天',
|
||||
read: true,
|
||||
icon: AlertTriangle,
|
||||
iconColor: 'text-orange-400',
|
||||
bgColor: 'bg-orange-500/20',
|
||||
platform: 'xiaohongshu',
|
||||
creatorName: '美妆Lisa',
|
||||
},
|
||||
{
|
||||
id: 'msg-008',
|
||||
id: 'msg-015',
|
||||
type: 'task_submitted',
|
||||
title: '新视频提交',
|
||||
content: '达人「健身教练王」提交了「健身器材使用教程」视频,请及时审核。',
|
||||
content: '达人「健身教练王」提交了「健身器材使用教程」视频,请及时审核',
|
||||
time: '2天前',
|
||||
read: true,
|
||||
icon: Video,
|
||||
iconColor: 'text-purple-400',
|
||||
bgColor: 'bg-purple-500/20',
|
||||
platform: 'bilibili',
|
||||
taskId: 'task-009',
|
||||
hasAction: true,
|
||||
actionType: 'review',
|
||||
},
|
||||
{
|
||||
id: 'msg-016',
|
||||
type: 'system_notice',
|
||||
title: '系统通知',
|
||||
content: '平台违禁词库已更新,「抖音」平台新增美妆类目违禁词56个',
|
||||
time: '3天前',
|
||||
read: true,
|
||||
icon: Bell,
|
||||
iconColor: 'text-text-secondary',
|
||||
bgColor: 'bg-bg-elevated',
|
||||
},
|
||||
]
|
||||
|
||||
export default function AgencyMessagesPage() {
|
||||
const router = useRouter()
|
||||
const [messages, setMessages] = useState(mockMessages)
|
||||
const [filter, setFilter] = useState<'all' | 'unread'>('all')
|
||||
const [filter, setFilter] = useState<'all' | 'unread' | 'pending'>('all')
|
||||
|
||||
const unreadCount = messages.filter(m => !m.read).length
|
||||
const pendingAppealRequests = messages.filter(m => m.appealRequest?.status === 'pending').length
|
||||
const pendingReviewCount = messages.filter(m =>
|
||||
!m.read && (m.type === 'task_submitted' || m.type === 'ai_review_complete')
|
||||
).length
|
||||
|
||||
const filteredMessages = filter === 'all' ? messages : messages.filter(m => !m.read)
|
||||
const getFilteredMessages = () => {
|
||||
switch (filter) {
|
||||
case 'unread':
|
||||
return messages.filter(m => !m.read)
|
||||
case 'pending':
|
||||
return messages.filter(m =>
|
||||
m.type === 'task_submitted' || m.type === 'ai_review_complete' || m.type === 'appeal_quota_request'
|
||||
)
|
||||
default:
|
||||
return messages
|
||||
}
|
||||
}
|
||||
|
||||
const filteredMessages = getFilteredMessages()
|
||||
|
||||
const markAsRead = (id: string) => {
|
||||
setMessages(prev => prev.map(m => m.id === id ? { ...m, read: true } : m))
|
||||
@@ -186,6 +335,77 @@ export default function AgencyMessagesPage() {
|
||||
}))
|
||||
}
|
||||
|
||||
// 处理消息点击
|
||||
const handleMessageClick = (message: Message) => {
|
||||
if (message.type === 'appeal_quota_request') return // 申诉请求不跳转
|
||||
if (message.type === 'system_notice') return // 系统通知不跳转
|
||||
markAsRead(message.id)
|
||||
|
||||
// 根据消息类型决定跳转
|
||||
switch (message.type) {
|
||||
case 'creator_accept':
|
||||
case 'creator_reject':
|
||||
// 达人签约相关 -> 达人管理
|
||||
router.push('/agency/creators')
|
||||
break
|
||||
case 'warning':
|
||||
// 风险预警 -> 达人管理
|
||||
router.push('/agency/creators')
|
||||
break
|
||||
case 'brand_config_updated':
|
||||
// 品牌规则更新 -> Brief配置
|
||||
router.push('/agency/briefs')
|
||||
break
|
||||
case 'task_deadline':
|
||||
// 任务截止提醒 -> 任务列表
|
||||
if (message.projectId) {
|
||||
router.push(`/agency/briefs/${message.projectId}`)
|
||||
} else {
|
||||
router.push('/agency/review')
|
||||
}
|
||||
break
|
||||
default:
|
||||
// 默认逻辑
|
||||
if (message.taskId) {
|
||||
router.push(`/agency/review/${message.taskId}`)
|
||||
} else if (message.projectId) {
|
||||
router.push(`/agency/briefs/${message.projectId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理操作按钮点击
|
||||
const handleAction = (message: Message, e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
markAsRead(message.id)
|
||||
|
||||
switch (message.actionType) {
|
||||
case 'review':
|
||||
if (message.taskId) {
|
||||
router.push(`/agency/review/${message.taskId}`)
|
||||
} else {
|
||||
router.push('/agency/review')
|
||||
}
|
||||
break
|
||||
case 'view':
|
||||
if (message.projectId) {
|
||||
router.push(`/agency/briefs/${message.projectId}`)
|
||||
} else if (message.type === 'task_deadline') {
|
||||
router.push('/agency/review')
|
||||
} else {
|
||||
router.push('/agency/creators')
|
||||
}
|
||||
break
|
||||
case 'config':
|
||||
if (message.projectId) {
|
||||
router.push(`/agency/briefs/${message.projectId}`)
|
||||
} else {
|
||||
router.push('/agency/briefs')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
@@ -209,20 +429,32 @@ export default function AgencyMessagesPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilter('all')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
className={cn(
|
||||
'px-4 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
filter === 'all' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
|
||||
}`}
|
||||
)}
|
||||
>
|
||||
全部消息
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilter('unread')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
className={cn(
|
||||
'px-4 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
filter === 'unread' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
|
||||
}`}
|
||||
)}
|
||||
>
|
||||
未读消息 ({unreadCount})
|
||||
未读 ({unreadCount})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilter('pending')}
|
||||
className={cn(
|
||||
'px-4 py-2 rounded-md text-sm font-medium transition-colors',
|
||||
filter === 'pending' ? 'bg-bg-card text-text-primary shadow-sm' : 'text-text-secondary hover:text-text-primary'
|
||||
)}
|
||||
>
|
||||
待处理
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -237,26 +469,28 @@ export default function AgencyMessagesPage() {
|
||||
return (
|
||||
<Card
|
||||
key={message.id}
|
||||
className={`transition-all overflow-hidden ${
|
||||
!isAppealRequest ? 'cursor-pointer hover:border-accent-indigo/50' : ''
|
||||
} ${!message.read ? 'border-l-4 border-l-accent-indigo' : ''}`}
|
||||
onClick={() => !isAppealRequest && markAsRead(message.id)}
|
||||
className={cn(
|
||||
'transition-all overflow-hidden',
|
||||
!isAppealRequest && 'cursor-pointer hover:border-accent-indigo/50',
|
||||
!message.read && 'border-l-4 border-l-accent-indigo'
|
||||
)}
|
||||
onClick={() => handleMessageClick(message)}
|
||||
>
|
||||
{/* 平台顶部条 */}
|
||||
{platform && (
|
||||
<div className={`px-4 py-1.5 ${platform.bgColor} border-b ${platform.borderColor} flex items-center gap-1.5`}>
|
||||
<div className={cn('px-4 py-1.5 border-b flex items-center gap-1.5', platform.bgColor, platform.borderColor)}>
|
||||
<span className="text-sm">{platform.icon}</span>
|
||||
<span className={`text-xs font-medium ${platform.textColor}`}>{platform.name}</span>
|
||||
<span className={cn('text-xs font-medium', platform.textColor)}>{platform.name}</span>
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`w-10 h-10 rounded-lg ${message.bgColor} flex items-center justify-center flex-shrink-0`}>
|
||||
<div className={cn('w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0', message.bgColor)}>
|
||||
<Icon size={20} className={message.iconColor} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className={`font-medium ${!message.read ? 'text-text-primary' : 'text-text-secondary'}`}>
|
||||
<h3 className={cn('font-medium', !message.read ? 'text-text-primary' : 'text-text-secondary')}>
|
||||
{message.title}
|
||||
</h3>
|
||||
{!message.read && (
|
||||
@@ -275,10 +509,24 @@ export default function AgencyMessagesPage() {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary mt-1">{message.content}</p>
|
||||
<p className="text-xs text-text-tertiary mt-2 flex items-center gap-1">
|
||||
<Clock size={12} />
|
||||
{message.time}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<p className="text-xs text-text-tertiary flex items-center gap-1">
|
||||
<Clock size={12} />
|
||||
{message.time}
|
||||
</p>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
{message.hasAction && !isAppealRequest && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={(e) => handleAction(message, e)}
|
||||
>
|
||||
<Eye size={14} />
|
||||
{message.actionType === 'review' ? '去审核' : message.actionType === 'config' ? '去配置' : '查看'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 申诉次数请求操作按钮 */}
|
||||
{isAppealRequest && appealStatus === 'pending' && (
|
||||
@@ -308,11 +556,6 @@ export default function AgencyMessagesPage() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isAppealRequest && (
|
||||
<Button variant="ghost" size="sm" onClick={(e) => e.stopPropagation()}>
|
||||
<MoreVertical size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user