主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
245 lines
8.2 KiB
TypeScript
245 lines
8.2 KiB
TypeScript
'use client'
|
|
|
|
import { useRouter } from 'next/navigation'
|
|
import { useEffect, useState } from 'react'
|
|
import { Bell, CheckCircle, XCircle, Clock, ChevronDown, ChevronRight } from 'lucide-react'
|
|
import { DesktopLayout } from '@/components/layout/DesktopLayout'
|
|
import { MobileLayout } from '@/components/layout/MobileLayout'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
type MessageStatus = 'info' | 'success' | 'error'
|
|
|
|
const mockMessages = [
|
|
{
|
|
id: 'msg-001',
|
|
taskId: 'task-002',
|
|
title: 'AI 审核通过',
|
|
description: '已进入代理商审核,请等待最终结果。',
|
|
time: '刚刚',
|
|
status: 'success' as MessageStatus,
|
|
read: false,
|
|
},
|
|
{
|
|
id: 'msg-002',
|
|
taskId: 'task-003',
|
|
title: 'AI 审核未通过',
|
|
description: '检测到竞品 Logo 与绝对化用语,请修改后重新提交。',
|
|
time: '10 分钟前',
|
|
status: 'error' as MessageStatus,
|
|
read: false,
|
|
},
|
|
{
|
|
id: 'msg-003',
|
|
taskId: 'task-001',
|
|
title: '等待提交脚本',
|
|
description: '请先上传脚本,系统才能开始合规预审。',
|
|
time: '1 小时前',
|
|
status: 'info' as MessageStatus,
|
|
read: true,
|
|
},
|
|
]
|
|
|
|
const statusConfig: Record<MessageStatus, { icon: React.ElementType; color: string; bg: string }> = {
|
|
success: { icon: CheckCircle, color: 'text-accent-green', bg: 'bg-accent-green/15' },
|
|
error: { icon: XCircle, color: 'text-accent-coral', bg: 'bg-accent-coral/15' },
|
|
info: { icon: Clock, color: 'text-accent-indigo', bg: 'bg-accent-indigo/15' },
|
|
}
|
|
|
|
function MessageCard({
|
|
title,
|
|
description,
|
|
time,
|
|
status,
|
|
isRead,
|
|
onClick,
|
|
}: {
|
|
title: string
|
|
description: string
|
|
time: string
|
|
status: MessageStatus
|
|
isRead: boolean
|
|
onClick: () => void
|
|
}) {
|
|
const Icon = statusConfig[status].icon
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onClick}
|
|
className={cn(
|
|
'w-full text-left bg-bg-card rounded-xl p-4 flex items-start gap-4 card-shadow hover:bg-bg-elevated/50 transition-colors',
|
|
!isRead && 'ring-1 ring-accent-indigo/20'
|
|
)}
|
|
>
|
|
<div className={cn('w-10 h-10 rounded-full flex items-center justify-center', statusConfig[status].bg)}>
|
|
<Icon className={cn('w-5 h-5', statusConfig[status].color)} />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-1">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className={cn('text-sm', isRead ? 'text-text-primary' : 'text-text-primary font-semibold')}>
|
|
{title}
|
|
</span>
|
|
{!isRead && <span className="w-2 h-2 rounded-full bg-accent-indigo" />}
|
|
</div>
|
|
<span className="text-xs text-text-tertiary">{time}</span>
|
|
</div>
|
|
<p className="text-sm text-text-secondary">{description}</p>
|
|
</div>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export default function CreatorMessagesPage() {
|
|
const router = useRouter()
|
|
const [isMobile, setIsMobile] = useState(true)
|
|
const [messages, setMessages] = useState(mockMessages)
|
|
const [showRead, setShowRead] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const checkMobile = () => setIsMobile(window.innerWidth < 1024)
|
|
checkMobile()
|
|
window.addEventListener('resize', checkMobile)
|
|
return () => window.removeEventListener('resize', checkMobile)
|
|
}, [])
|
|
|
|
const handleClick = (messageId: string, taskId: string) => {
|
|
setMessages((prev) =>
|
|
prev.map((msg) => (msg.id === messageId ? { ...msg, read: true } : msg))
|
|
)
|
|
router.push(`/creator/task/${taskId}`)
|
|
}
|
|
const unreadCount = messages.filter((msg) => !msg.read).length
|
|
const unreadMessages = messages.filter((msg) => !msg.read)
|
|
const readMessages = messages.filter((msg) => msg.read)
|
|
|
|
const DesktopContent = (
|
|
<DesktopLayout role="creator">
|
|
<div className="flex flex-col gap-6 h-full">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 rounded-2xl bg-accent-indigo/15 flex items-center justify-center">
|
|
<Bell className="w-6 h-6 text-accent-indigo" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-[28px] font-bold text-text-primary">消息中心</h1>
|
|
<p className="text-[15px] text-text-secondary">追踪 AI 审核与人工审核进度</p>
|
|
</div>
|
|
</div>
|
|
<span className="text-sm text-text-tertiary">未读 {unreadCount}</span>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-4">
|
|
{unreadMessages.length === 0 && (
|
|
<div className="bg-bg-card rounded-xl p-4 text-sm text-text-tertiary">
|
|
暂无未读消息
|
|
</div>
|
|
)}
|
|
{unreadMessages.map((msg) => (
|
|
<MessageCard
|
|
key={msg.id}
|
|
title={msg.title}
|
|
description={msg.description}
|
|
time={msg.time}
|
|
status={msg.status}
|
|
isRead={msg.read}
|
|
onClick={() => handleClick(msg.id, msg.taskId)}
|
|
/>
|
|
))}
|
|
|
|
<div className="pt-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowRead((prev) => !prev)}
|
|
className="flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary"
|
|
>
|
|
{showRead ? <ChevronDown className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />}
|
|
已读 ({readMessages.length})
|
|
</button>
|
|
</div>
|
|
|
|
{showRead && readMessages.length === 0 && (
|
|
<div className="bg-bg-card rounded-xl p-4 text-sm text-text-tertiary">
|
|
暂无已读消息
|
|
</div>
|
|
)}
|
|
{showRead && readMessages.map((msg) => (
|
|
<MessageCard
|
|
key={msg.id}
|
|
title={msg.title}
|
|
description={msg.description}
|
|
time={msg.time}
|
|
status={msg.status}
|
|
isRead={msg.read}
|
|
onClick={() => handleClick(msg.id, msg.taskId)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</DesktopLayout>
|
|
)
|
|
|
|
const MobileContent = (
|
|
<MobileLayout role="creator">
|
|
<div className="flex flex-col gap-5 px-5 py-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-2xl bg-accent-indigo/15 flex items-center justify-center">
|
|
<Bell className="w-5 h-5 text-accent-indigo" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-bold text-text-primary">消息中心</h1>
|
|
<p className="text-sm text-text-secondary">审核进度提醒</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3">
|
|
{unreadMessages.length === 0 && (
|
|
<div className="bg-bg-card rounded-xl p-4 text-sm text-text-tertiary">
|
|
暂无未读消息
|
|
</div>
|
|
)}
|
|
{unreadMessages.map((msg) => (
|
|
<MessageCard
|
|
key={msg.id}
|
|
title={msg.title}
|
|
description={msg.description}
|
|
time={msg.time}
|
|
status={msg.status}
|
|
isRead={msg.read}
|
|
onClick={() => handleClick(msg.id, msg.taskId)}
|
|
/>
|
|
))}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowRead((prev) => !prev)}
|
|
className="flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary pt-1"
|
|
>
|
|
{showRead ? <ChevronDown className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />}
|
|
已读 ({readMessages.length})
|
|
</button>
|
|
|
|
{showRead && readMessages.length === 0 && (
|
|
<div className="bg-bg-card rounded-xl p-4 text-sm text-text-tertiary">
|
|
暂无已读消息
|
|
</div>
|
|
)}
|
|
{showRead && readMessages.map((msg) => (
|
|
<MessageCard
|
|
key={msg.id}
|
|
title={msg.title}
|
|
description={msg.description}
|
|
time={msg.time}
|
|
status={msg.status}
|
|
isRead={msg.read}
|
|
onClick={() => handleClick(msg.id, msg.taskId)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</MobileLayout>
|
|
)
|
|
|
|
return isMobile ? MobileContent : DesktopContent
|
|
}
|