Your Name 37ac749071 fix: 修复前端代码质量问题
- 创建 Toast 通知组件,替换所有 alert() 调用
- 修复 useReview hook 内存泄漏(setInterval 清理)
- 移除所有 console.error 和 console.log 语句
- 为复制操作失败添加用户友好的 toast 提示

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 12:48:22 +08:00

351 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import { useRouter, useParams } from 'next/navigation'
import { useToast } from '@/components/ui/Toast'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import {
ArrowLeft,
MessageSquare,
Clock,
CheckCircle,
XCircle,
AlertTriangle,
User,
FileText,
Video,
Download,
File,
Send,
Image as ImageIcon
} from 'lucide-react'
// 申诉状态类型
type AppealStatus = 'pending' | 'processing' | 'approved' | 'rejected'
// 模拟申诉详情数据
const mockAppealDetail = {
id: 'appeal-001',
taskId: 'task-001',
taskTitle: '夏日护肤推广脚本',
creatorId: 'creator-001',
creatorName: '小美护肤',
creatorAvatar: '小',
type: 'ai' as const,
contentType: 'script' as const,
reason: 'AI误判',
content: '脚本中提到的"某品牌"是泛指并非特指竞品AI系统可能误解了语境。我在脚本中使用的是泛化表述并没有提及任何具体的竞品名称。请代理商重新审核此处谢谢',
status: 'pending' as AppealStatus,
createdAt: '2026-02-06 10:30',
// 附件
attachments: [
{ id: 'att-001', name: '品牌授权证明.pdf', size: '1.2 MB', type: 'pdf' },
{ id: 'att-002', name: '脚本原文截图.png', size: '345 KB', type: 'image' },
],
// 原审核问题
originalIssue: {
type: 'ai',
title: '疑似竞品提及',
description: '脚本第3段提到"某品牌",可能涉及竞品露出风险。',
location: '脚本第3段第2行',
},
// 相关任务信息
taskInfo: {
projectName: 'XX品牌618推广',
scriptFileName: '夏日护肤推广_脚本v2.docx',
scriptFileSize: '245 KB',
},
}
// 状态配置
const statusConfig: Record<AppealStatus, { label: string; color: string; bgColor: string; icon: React.ElementType }> = {
pending: { label: '待处理', color: 'text-accent-amber', bgColor: 'bg-accent-amber/15', icon: Clock },
processing: { label: '处理中', color: 'text-accent-indigo', bgColor: 'bg-accent-indigo/15', icon: MessageSquare },
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 },
}
export default function AgencyAppealDetailPage() {
const router = useRouter()
const toast = useToast()
const params = useParams()
const [appeal] = useState(mockAppealDetail)
const [replyContent, setReplyContent] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const status = statusConfig[appeal.status]
const StatusIcon = status.icon
const handleApprove = async () => {
if (!replyContent.trim()) {
toast.error('请填写处理意见')
return
}
setIsSubmitting(true)
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
toast.success('申诉已通过')
router.push('/agency/appeals')
}
const handleReject = async () => {
if (!replyContent.trim()) {
toast.error('请填写驳回原因')
return
}
setIsSubmitting(true)
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
toast.success('申诉已驳回')
router.push('/agency/appeals')
}
return (
<div className="space-y-6">
{/* 顶部导航 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => router.back()}
className="p-2 rounded-lg hover:bg-bg-elevated transition-colors"
>
<ArrowLeft size={20} className="text-text-secondary" />
</button>
<div>
<h1 className="text-2xl font-bold text-text-primary"></h1>
<p className="text-sm text-text-secondary mt-0.5">: {appeal.id}</p>
</div>
</div>
<div className={`flex items-center gap-2 px-4 py-2 rounded-xl ${status.bgColor}`}>
<StatusIcon size={18} className={status.color} />
<span className={`font-medium ${status.color}`}>{status.label}</span>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 左侧:申诉详情 */}
<div className="lg:col-span-2 space-y-6">
{/* 申诉人信息 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<User size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-accent-indigo to-purple-500 flex items-center justify-center text-white font-bold text-lg">
{appeal.creatorAvatar}
</div>
<div>
<p className="font-medium text-text-primary">{appeal.creatorName}</p>
<p className="text-sm text-text-secondary">ID: {appeal.creatorId}</p>
</div>
</div>
<div className="mt-4 pt-4 border-t border-border-subtle grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-text-tertiary"></span>
<p className="text-text-primary mt-1">{appeal.taskTitle}</p>
</div>
<div>
<span className="text-text-tertiary"></span>
<p className="text-text-primary mt-1">{appeal.taskInfo.projectName}</p>
</div>
<div>
<span className="text-text-tertiary"></span>
<p className="text-text-primary mt-1 flex items-center gap-1">
{appeal.contentType === 'script' ? <FileText size={14} /> : <Video size={14} />}
{appeal.contentType === 'script' ? '脚本' : '视频'}
</p>
</div>
<div>
<span className="text-text-tertiary"></span>
<p className="text-text-primary mt-1">{appeal.createdAt}</p>
</div>
</div>
</CardContent>
</Card>
{/* 原审核问题 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle size={18} className="text-accent-coral" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="p-4 rounded-xl bg-accent-coral/10 border border-accent-coral/20">
<div className="flex items-center gap-2 mb-2">
<span className="px-2 py-0.5 text-xs rounded bg-accent-coral/20 text-accent-coral">
{appeal.originalIssue.type === 'ai' ? 'AI检测' : '人工审核'}
</span>
<span className="font-medium text-text-primary">{appeal.originalIssue.title}</span>
</div>
<p className="text-sm text-text-secondary">{appeal.originalIssue.description}</p>
<p className="text-xs text-text-tertiary mt-2">: {appeal.originalIssue.location}</p>
</div>
</CardContent>
</Card>
{/* 申诉内容 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MessageSquare size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<span className="text-sm text-text-tertiary"></span>
<p className="text-text-primary mt-1 font-medium">{appeal.reason}</p>
</div>
<div>
<span className="text-sm text-text-tertiary"></span>
<p className="text-text-primary mt-1 leading-relaxed">{appeal.content}</p>
</div>
{/* 附件 */}
{appeal.attachments.length > 0 && (
<div>
<span className="text-sm text-text-tertiary"></span>
<div className="mt-2 space-y-2">
{appeal.attachments.map((att) => (
<div
key={att.id}
className="flex items-center gap-3 p-3 rounded-lg bg-bg-elevated"
>
<div className="w-10 h-10 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
{att.type === 'image' ? (
<ImageIcon size={20} className="text-accent-indigo" />
) : (
<File size={20} className="text-accent-indigo" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-text-primary truncate">{att.name}</p>
<p className="text-xs text-text-tertiary">{att.size}</p>
</div>
<button
type="button"
className="p-2 rounded-lg hover:bg-bg-page transition-colors"
title="下载"
>
<Download size={16} className="text-text-secondary" />
</button>
</div>
))}
</div>
</div>
)}
</div>
</CardContent>
</Card>
</div>
{/* 右侧:处理面板 */}
<div className="space-y-6">
{/* 相关文件 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-3 p-3 rounded-lg bg-bg-elevated">
<div className="w-10 h-10 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
<File size={20} className="text-accent-indigo" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-text-primary truncate">
{appeal.taskInfo.scriptFileName}
</p>
<p className="text-xs text-text-tertiary">{appeal.taskInfo.scriptFileSize}</p>
</div>
<button
type="button"
className="p-2 rounded-lg hover:bg-bg-page transition-colors"
title="下载"
>
<Download size={16} className="text-text-secondary" />
</button>
</div>
</CardContent>
</Card>
{/* 处理决策 */}
{appeal.status === 'pending' || appeal.status === 'processing' ? (
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm text-text-secondary mb-2 block"></label>
<textarea
value={replyContent}
onChange={(e) => setReplyContent(e.target.value)}
placeholder="请输入处理意见或驳回原因..."
className="w-full h-32 p-3 rounded-xl bg-bg-elevated border border-border-subtle text-text-primary placeholder-text-tertiary resize-none focus:outline-none focus:ring-2 focus:ring-accent-indigo"
/>
</div>
<div className="flex gap-3">
<Button
variant="primary"
className="flex-1 bg-accent-green hover:bg-accent-green/80"
onClick={handleApprove}
disabled={isSubmitting}
>
<CheckCircle size={16} />
</Button>
<Button
variant="secondary"
className="flex-1 border-accent-coral text-accent-coral hover:bg-accent-coral/10"
onClick={handleReject}
disabled={isSubmitting}
>
<XCircle size={16} />
</Button>
</div>
<p className="text-xs text-text-tertiary text-center">
</p>
</CardContent>
</Card>
) : (
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className={`p-4 rounded-xl ${status.bgColor}`}>
<div className="flex items-center gap-2 mb-2">
<StatusIcon size={18} className={status.color} />
<span className={`font-medium ${status.color}`}>
{appeal.status === 'approved' ? '申诉已通过' : '申诉已驳回'}
</span>
</div>
<p className="text-sm text-text-secondary">
{appeal.status === 'approved'
? '经核实,达人申诉理由成立,已撤销原审核问题。'
: '经核实,原审核问题有效,申诉不成立。'}
</p>
</div>
</CardContent>
</Card>
)}
</div>
</div>
</div>
)
}