From 2f9b7f05fdefd6eb086357b3ebd9b5670d4565d8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 6 Feb 2026 15:38:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(creator):=20=E5=AE=8C=E6=88=90=E8=BE=BE?= =?UTF-8?q?=E4=BA=BA=E7=AB=AF=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2=E5=BC=80?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增申诉中心页面(列表、详情、新建申诉) - 新增申诉次数管理页面(按任务显示配额,支持向代理商申请) - 新增个人中心页面(达人ID复制、菜单导航) - 新增个人信息编辑、账户设置、消息通知设置页面 - 新增帮助中心和历史记录页面 - 新增脚本提交和视频提交页面 - 优化消息中心页面(消息详情跳转) - 优化任务详情页面布局和交互 - 更新 ResponsiveLayout、Sidebar、ReviewSteps 通用组件 Co-Authored-By: Claude Opus 4.5 --- frontend/app/creator/appeal-quota/page.tsx | 269 ++++ frontend/app/creator/appeals/[id]/page.tsx | 336 +++++ frontend/app/creator/appeals/new/page.tsx | 395 ++++++ frontend/app/creator/appeals/page.tsx | 274 ++++ frontend/app/creator/help/page.tsx | 268 ++++ frontend/app/creator/history/page.tsx | 203 +++ frontend/app/creator/messages/page.tsx | 669 +++++++--- frontend/app/creator/page.tsx | 898 ++++++------- frontend/app/creator/profile/edit/page.tsx | 249 ++++ frontend/app/creator/profile/page.tsx | 275 ++++ .../app/creator/settings/account/page.tsx | 383 ++++++ .../creator/settings/notification/page.tsx | 279 ++++ frontend/app/creator/task/[id]/page.tsx | 1122 ++++++++++------- .../app/creator/task/[id]/script/page.tsx | 461 +++++++ frontend/app/creator/task/[id]/video/page.tsx | 534 ++++++++ .../components/layout/ResponsiveLayout.tsx | 88 +- frontend/components/navigation/Sidebar.tsx | 30 +- frontend/components/ui/ReviewSteps.tsx | 50 +- 18 files changed, 5638 insertions(+), 1145 deletions(-) create mode 100644 frontend/app/creator/appeal-quota/page.tsx create mode 100644 frontend/app/creator/appeals/[id]/page.tsx create mode 100644 frontend/app/creator/appeals/new/page.tsx create mode 100644 frontend/app/creator/appeals/page.tsx create mode 100644 frontend/app/creator/help/page.tsx create mode 100644 frontend/app/creator/history/page.tsx create mode 100644 frontend/app/creator/profile/edit/page.tsx create mode 100644 frontend/app/creator/profile/page.tsx create mode 100644 frontend/app/creator/settings/account/page.tsx create mode 100644 frontend/app/creator/settings/notification/page.tsx create mode 100644 frontend/app/creator/task/[id]/script/page.tsx create mode 100644 frontend/app/creator/task/[id]/video/page.tsx diff --git a/frontend/app/creator/appeal-quota/page.tsx b/frontend/app/creator/appeal-quota/page.tsx new file mode 100644 index 0000000..b48c1cf --- /dev/null +++ b/frontend/app/creator/appeal-quota/page.tsx @@ -0,0 +1,269 @@ +'use client' + +import React, { useState } from 'react' +import { useRouter } from 'next/navigation' +import { + ArrowLeft, + AlertCircle, + CheckCircle, + Clock, + XCircle, + Send, + Info, +} from 'lucide-react' +import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' +import { Button } from '@/components/ui/Button' +import { cn } from '@/lib/utils' + +// 申请状态类型 +type RequestStatus = 'none' | 'pending' | 'approved' | 'rejected' + +// 任务申诉次数数据 +interface TaskAppealQuota { + id: string + taskName: string + agencyName: string + remaining: number + used: number + requestStatus: RequestStatus + requestTime?: string +} + +// 模拟任务申诉次数数据 +const mockTaskQuotas: TaskAppealQuota[] = [ + { + id: '1', + taskName: '618美妆推广视频', + agencyName: '星辰传媒', + remaining: 1, + used: 0, + requestStatus: 'none', + }, + { + id: '2', + taskName: '双11护肤品种草', + agencyName: '星辰传媒', + remaining: 0, + used: 1, + requestStatus: 'pending', + requestTime: '2024-02-05 14:30', + }, + { + id: '3', + taskName: '春节限定礼盒开箱', + agencyName: '晨曦文化', + remaining: 2, + used: 0, + requestStatus: 'approved', + requestTime: '2024-02-04 10:15', + }, + { + id: '4', + taskName: '情人节香水测评', + agencyName: '晨曦文化', + remaining: 0, + used: 1, + requestStatus: 'rejected', + requestTime: '2024-02-03 16:20', + }, +] + +// 状态标签组件 +function StatusBadge({ status }: { status: RequestStatus }) { + const config = { + none: { label: '', icon: null, className: '' }, + pending: { + label: '申请中', + icon: Clock, + className: 'bg-accent-amber/15 text-accent-amber', + }, + approved: { + label: '已同意', + icon: CheckCircle, + className: 'bg-accent-green/15 text-accent-green', + }, + rejected: { + label: '已拒绝', + icon: XCircle, + className: 'bg-accent-coral/15 text-accent-coral', + }, + } + + const { label, icon: Icon, className } = config[status] + + if (status === 'none') return null + + return ( + + {Icon && } + {label} + + ) +} + +// 任务卡片组件 +function TaskQuotaCard({ + task, + onRequestIncrease, +}: { + task: TaskAppealQuota + onRequestIncrease: (taskId: string) => void +}) { + const canRequest = task.requestStatus === 'none' || task.requestStatus === 'rejected' + + return ( +
+ {/* 任务信息 */} +
+
+

{task.taskName}

+

{task.agencyName}

+
+ +
+ + {/* 申诉次数 */} +
+
+ {task.remaining} + 剩余次数 +
+
+ {task.used} + 已使用 +
+
+ + {/* 操作按钮 */} +
+ {task.requestTime && ( + + {task.requestStatus === 'pending' ? '申请时间:' : '处理时间:'} + {task.requestTime} + + )} + {!task.requestTime && } + + {canRequest ? ( + + ) : task.requestStatus === 'pending' ? ( + 等待代理商处理... + ) : null} +
+
+ ) +} + +export default function AppealQuotaPage() { + const router = useRouter() + const [tasks, setTasks] = useState(mockTaskQuotas) + const [showSuccessToast, setShowSuccessToast] = useState(false) + + // 申请增加申诉次数 + const handleRequestIncrease = (taskId: string) => { + setTasks(prev => + prev.map(task => + task.id === taskId + ? { + ...task, + requestStatus: 'pending' as RequestStatus, + requestTime: new Date().toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }), + } + : task + ) + ) + setShowSuccessToast(true) + setTimeout(() => setShowSuccessToast(false), 3000) + } + + // 统计数据 + const totalRemaining = tasks.reduce((sum, t) => sum + t.remaining, 0) + const totalUsed = tasks.reduce((sum, t) => sum + t.used, 0) + const pendingRequests = tasks.filter(t => t.requestStatus === 'pending').length + + return ( + +
+ {/* 顶部栏 */} +
+ +
+

申诉次数

+

+ 查看各任务的申诉次数,可向代理商申请增加 +

+
+
+ + {/* 统计卡片 */} +
+
+ {totalRemaining} + 总剩余次数 +
+
+ {totalUsed} + 总已使用 +
+
+ {pendingRequests} + 待处理申请 +
+
+ + {/* 规则说明 */} +
+ +
+ 申诉次数规则 + + 每个任务初始有 1 次申诉机会,不同任务独立计算。如需更多次数,可点击"申请增加"向代理商发送请求,无需填写理由。代理商可增加的次数无上限。 + +
+
+ + {/* 任务列表 */} +
+

+ 任务申诉次数 ({tasks.length}) +

+ {tasks.map(task => ( + + ))} +
+
+ + {/* 成功提示 */} + {showSuccessToast && ( +
+ + 申请已发送,等待代理商处理 +
+ )} +
+ ) +} diff --git a/frontend/app/creator/appeals/[id]/page.tsx b/frontend/app/creator/appeals/[id]/page.tsx new file mode 100644 index 0000000..3a30dfe --- /dev/null +++ b/frontend/app/creator/appeals/[id]/page.tsx @@ -0,0 +1,336 @@ +'use client' + +import { useState } from 'react' +import { useParams, useRouter } from 'next/navigation' +import { + ArrowLeft, + MessageCircle, + Clock, + CheckCircle, + XCircle, + FileText, + Image, + Send, + AlertTriangle +} from 'lucide-react' +import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' +import { cn } from '@/lib/utils' + +// 申诉状态类型 +type AppealStatus = 'pending' | 'processing' | 'approved' | 'rejected' + +// 申诉详情数据类型 +type AppealDetail = { + id: string + taskId: string + taskTitle: string + type: 'ai' | 'agency' | 'brand' + reason: string + content: string + status: AppealStatus + createdAt: string + updatedAt?: string + result?: string + attachments?: { name: string; type: 'image' | 'document'; url: string }[] + timeline?: { time: string; action: string; operator?: string }[] + originalIssue?: { title: string; description: string } +} + +// 模拟申诉详情数据 +const mockAppealDetails: Record = { + 'appeal-001': { + id: 'appeal-001', + taskId: 'task-003', + taskTitle: 'ZZ饮品夏日', + type: 'ai', + reason: '误判', + content: '视频中出现的是我们自家品牌的历史产品,并非竞品。已附上品牌授权证明。', + status: 'approved', + createdAt: '2026-02-01 10:30', + updatedAt: '2026-02-02 15:20', + result: '经核实,该产品确为品牌方授权产品,申诉通过。AI已学习此案例,后续将避免类似误判。', + attachments: [ + { name: '品牌授权书.pdf', type: 'document', url: '#' }, + { name: '产品对比图.jpg', type: 'image', url: '#' }, + ], + timeline: [ + { time: '2026-02-01 10:30', action: '提交申诉' }, + { time: '2026-02-01 14:15', action: '进入处理队列' }, + { time: '2026-02-02 09:00', action: '开始审核', operator: '审核员 A' }, + { time: '2026-02-02 15:20', action: '申诉通过', operator: '审核员 A' }, + ], + originalIssue: { + title: '检测到竞品 Logo', + description: '画面中 0:15-0:18 出现竞品「百事可乐」的 Logo,可能造成合规风险。', + }, + }, + 'appeal-002': { + id: 'appeal-002', + taskId: 'task-010', + taskTitle: 'GG智能手表', + type: 'agency', + reason: '审核标准不清晰', + content: '代理商反馈品牌调性不符,但Brief中并未明确说明科技专业形象的具体要求。请明确审核标准。', + status: 'processing', + createdAt: '2026-02-04 09:15', + timeline: [ + { time: '2026-02-04 09:15', action: '提交申诉' }, + { time: '2026-02-04 11:30', action: '进入处理队列' }, + { time: '2026-02-05 10:00', action: '开始审核', operator: '审核员 B' }, + ], + originalIssue: { + title: '品牌调性不符', + description: '脚本整体风格偏向娱乐化,与品牌科技专业形象不匹配。', + }, + }, + 'appeal-003': { + id: 'appeal-003', + taskId: 'task-011', + taskTitle: 'HH美妆代言', + type: 'brand', + reason: '创意理解差异', + content: '品牌方认为创意不够新颖,但该创意形式在同类型产品推广中效果显著,已附上数据支持。', + status: 'pending', + createdAt: '2026-02-05 14:00', + attachments: [ + { name: '同类案例数据.xlsx', type: 'document', url: '#' }, + ], + timeline: [ + { time: '2026-02-05 14:00', action: '提交申诉' }, + ], + originalIssue: { + title: '创意不够新颖', + description: '脚本采用的是常见的口播形式,缺乏创新点和记忆点。', + }, + }, + 'appeal-004': { + id: 'appeal-004', + taskId: 'task-013', + taskTitle: 'JJ旅行vlog', + type: 'agency', + reason: '版权问题异议', + content: '使用的背景音乐来自无版权音乐库 Epidemic Sound,已购买商用授权。附上授权证明截图。', + status: 'rejected', + createdAt: '2026-01-28 11:30', + updatedAt: '2026-01-30 16:45', + result: '经核实,该音乐虽有授权,但授权范围不包含商业广告用途。建议更换音乐后重新提交。', + attachments: [ + { name: '授权截图.png', type: 'image', url: '#' }, + ], + timeline: [ + { time: '2026-01-28 11:30', action: '提交申诉' }, + { time: '2026-01-28 15:00', action: '进入处理队列' }, + { time: '2026-01-29 09:30', action: '开始审核', operator: '审核员 C' }, + { time: '2026-01-30 16:45', action: '申诉驳回', operator: '审核员 C' }, + ], + originalIssue: { + title: '背景音乐版权问题', + description: '视频中使用的背景音乐「XXX」存在版权风险,平台可能会限流或下架。', + }, + }, +} + +// 状态配置 +const statusConfig: Record = { + pending: { label: '待处理', color: 'text-amber-500', bgColor: 'bg-amber-500/15', icon: Clock }, + processing: { label: '处理中', color: 'text-accent-indigo', bgColor: 'bg-accent-indigo/15', icon: MessageCircle }, + approved: { label: '已通过', color: 'text-accent-green', bgColor: 'bg-accent-green/15', icon: CheckCircle }, + rejected: { label: '已驳回', color: 'text-accent-coral', bgColor: 'bg-accent-coral/15', icon: XCircle }, +} + +// 类型配置 +const typeConfig: Record = { + ai: { label: 'AI审核', color: 'text-accent-indigo' }, + agency: { label: '代理商审核', color: 'text-purple-400' }, + brand: { label: '品牌方审核', color: 'text-accent-blue' }, +} + +export default function AppealDetailPage() { + const params = useParams() + const router = useRouter() + const appealId = params.id as string + const [newComment, setNewComment] = useState('') + + const appeal = mockAppealDetails[appealId] + + if (!appeal) { + return ( + +
+
+ +

申诉记录不存在

+ +
+
+
+ ) + } + + const status = statusConfig[appeal.status] + const type = typeConfig[appeal.type] + const StatusIcon = status.icon + + return ( + +
+ {/* 顶部栏 */} +
+
+ +

申诉详情

+

申诉编号: {appeal.id}

+
+
+ + {status.label} +
+
+ + {/* 内容区 - 响应式布局 */} +
+ {/* 左侧:申诉信息 */} +
+ {/* 原始问题 */} + {appeal.originalIssue && ( +
+

原始审核问题

+
+
+ + {appeal.originalIssue.title} +
+

{appeal.originalIssue.description}

+
+
+ )} + + {/* 申诉内容 */} +
+

申诉内容

+
+
+
+ 关联任务: + {appeal.taskTitle} +
+
+ 申诉对象: + {type.label} +
+
+
+ 申诉原因: + {appeal.reason} +
+
+

{appeal.content}

+
+
+
+ + {/* 附件 */} + {appeal.attachments && appeal.attachments.length > 0 && ( +
+

证明材料

+
+ {appeal.attachments.map((attachment, index) => ( +
+ {attachment.type === 'image' ? ( + + ) : ( + + )} + {attachment.name} +
+ ))} +
+
+ )} + + {/* 处理结果 */} + {appeal.result && ( +
+

处理结果

+
+

{appeal.result}

+
+
+ )} + + {/* 补充说明(处理中状态可用) */} + {(appeal.status === 'pending' || appeal.status === 'processing') && ( +
+

补充说明

+
+ setNewComment(e.target.value)} + className="flex-1 px-4 py-3 bg-bg-elevated rounded-xl text-sm text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo" + /> + +
+
+ )} +
+ + {/* 右侧:时间线 */} +
+
+

处理进度

+
+ {appeal.timeline?.map((item, index) => ( +
+
+
+ {index < (appeal.timeline?.length || 0) - 1 && ( +
+ )} +
+
+ {item.time} + {item.action} + {item.operator && ( + {item.operator} + )} +
+
+ ))} +
+
+
+
+
+ + ) +} diff --git a/frontend/app/creator/appeals/new/page.tsx b/frontend/app/creator/appeals/new/page.tsx new file mode 100644 index 0000000..c8b943e --- /dev/null +++ b/frontend/app/creator/appeals/new/page.tsx @@ -0,0 +1,395 @@ +'use client' + +import { useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { + ArrowLeft, + Upload, + X, + FileText, + Image, + AlertTriangle, + CheckCircle +} from 'lucide-react' +import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout' +import { cn } from '@/lib/utils' + +// 申诉原因选项 +const appealReasons = [ + { id: 'misjudge', label: '误判', description: 'AI或审核员误判了内容' }, + { id: 'unclear', label: '标准不清晰', description: '审核标准不明确或有歧义' }, + { id: 'evidence', label: '有证据支持', description: '有证据证明内容符合要求' }, + { id: 'context', label: '上下文理解', description: '审核未考虑完整上下文' }, + { id: 'other', label: '其他原因', description: '其他需要说明的情况' }, +] + +// 任务信息(模拟从URL参数获取) +const getTaskInfo = (taskId: string) => { + const tasks: Record = { + 'task-003': { + title: 'ZZ饮品夏日', + issue: '检测到竞品提及', + issueDesc: '脚本第3段提及了竞品「百事可乐」,可能造成品牌冲突风险。', + type: 'ai', + appealRemaining: 1, + agencyName: '星辰传媒', + }, + 'task-010': { + title: 'GG智能手表', + issue: '品牌调性不符', + issueDesc: '脚本整体风格偏向娱乐化,与品牌科技专业形象不匹配。', + type: 'agency', + appealRemaining: 0, + agencyName: '星辰传媒', + }, + 'task-011': { + title: 'HH美妆代言', + issue: '创意不够新颖', + issueDesc: '脚本采用的是常见的口播形式,缺乏创新点和记忆点。', + type: 'brand', + appealRemaining: 1, + agencyName: '晨曦文化', + }, + 'task-013': { + title: 'JJ旅行vlog', + issue: '背景音乐版权问题', + issueDesc: '视频中使用的背景音乐存在版权风险。', + type: 'agency', + appealRemaining: 2, + agencyName: '晨曦文化', + }, + 'task-015': { + title: 'LL厨房电器', + issue: '使用场景不真实', + issueDesc: '视频中的厨房场景过于整洁,缺乏真实感。', + type: 'brand', + appealRemaining: 0, + agencyName: '星辰传媒', + }, + } + return tasks[taskId] || { title: '未知任务', issue: '未知问题', issueDesc: '', type: 'ai', appealRemaining: 0, agencyName: '未知代理商' } +} + +export default function NewAppealPage() { + const router = useRouter() + const searchParams = useSearchParams() + const taskId = searchParams.get('taskId') || '' + const taskInfo = getTaskInfo(taskId) + + const [selectedReason, setSelectedReason] = useState('') + const [content, setContent] = useState('') + const [attachments, setAttachments] = useState<{ name: string; type: 'image' | 'document' }[]>([]) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isSubmitted, setIsSubmitted] = useState(false) + const [isRequestingQuota, setIsRequestingQuota] = useState(false) + const [quotaRequested, setQuotaRequested] = useState(false) + + const hasAppealQuota = taskInfo.appealRemaining > 0 + + const handleFileUpload = (e: React.ChangeEvent) => { + const files = e.target.files + if (files) { + const newAttachments = Array.from(files).map(file => ({ + name: file.name, + type: file.type.startsWith('image/') ? 'image' as const : 'document' as const, + })) + setAttachments([...attachments, ...newAttachments]) + } + } + + const removeAttachment = (index: number) => { + setAttachments(attachments.filter((_, i) => i !== index)) + } + + const handleSubmit = async () => { + if (!selectedReason || !content.trim()) return + + setIsSubmitting(true) + // 模拟提交 + await new Promise(resolve => setTimeout(resolve, 1500)) + setIsSubmitting(false) + setIsSubmitted(true) + + // 2秒后跳转到申诉列表 + setTimeout(() => { + router.push('/creator/appeals') + }, 2000) + } + + const canSubmit = selectedReason && content.trim().length >= 20 && hasAppealQuota + + // 申请增加申诉次数 + const handleRequestQuota = async () => { + setIsRequestingQuota(true) + await new Promise(resolve => setTimeout(resolve, 1000)) + setIsRequestingQuota(false) + setQuotaRequested(true) + } + + // 提交成功界面 + if (isSubmitted) { + return ( + +
+
+
+ +
+
+

申诉提交成功

+

+ 您的申诉已提交,我们将在 1-3 个工作日内处理完成。处理结果将通过消息中心通知您。 +

+
+

正在跳转到申诉列表...

+
+
+
+ ) + } + + return ( + +
+ {/* 顶部栏 */} +
+
+ +

发起申诉

+

对审核结果有异议?提交申诉让我们重新审核

+
+
+ + + 本任务剩余 {taskInfo.appealRemaining} 次申诉机会 + +
+
+ + {/* 内容区 - 响应式布局 */} +
+ {/* 左侧:申诉表单 */} +
+ {/* 关联任务 */} +
+

关联任务

+
+
+ {taskInfo.title} + + {taskInfo.type === 'ai' ? 'AI审核' : taskInfo.type === 'agency' ? '代理商审核' : '品牌方审核'} + +
+
+ +
+ {taskInfo.issue} +

{taskInfo.issueDesc}

+
+
+
+
+ + {/* 申诉次数不足提示 */} + {!hasAppealQuota && ( +
+
+ +
+

申诉次数不足

+

+ 本任务的申诉次数已用完,无法提交新的申诉。您可以向代理商「{taskInfo.agencyName}」申请增加申诉次数。 +

+ {quotaRequested ? ( +
+ + 申请已发送,等待代理商处理 +
+ ) : ( + + )} +
+
+
+ )} + + {/* 申诉原因 */} +
+

申诉原因 *

+
+ {appealReasons.map((reason) => ( +
setSelectedReason(reason.id)} + className={cn( + 'p-4 rounded-xl border-2 cursor-pointer transition-all', + selectedReason === reason.id + ? 'border-accent-indigo bg-accent-indigo/5' + : 'border-border-subtle hover:border-text-tertiary' + )} + > + {reason.label} +

{reason.description}

+
+ ))} +
+
+ + {/* 申诉说明 */} +
+

+ 申诉说明 * + 至少20字 +

+