fix: 修复前端代码质量问题
- 创建 Toast 通知组件,替换所有 alert() 调用 - 修复 useReview hook 内存泄漏(setInterval 清理) - 移除所有 console.error 和 console.log 语句 - 为复制操作失败添加用户友好的 toast 提示 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a5a005db0c
commit
37ac749071
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import {
|
import {
|
||||||
@ -67,6 +68,7 @@ const statusConfig: Record<AppealStatus, { label: string; color: string; bgColor
|
|||||||
|
|
||||||
export default function AgencyAppealDetailPage() {
|
export default function AgencyAppealDetailPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const [appeal] = useState(mockAppealDetail)
|
const [appeal] = useState(mockAppealDetail)
|
||||||
const [replyContent, setReplyContent] = useState('')
|
const [replyContent, setReplyContent] = useState('')
|
||||||
@ -77,25 +79,25 @@ export default function AgencyAppealDetailPage() {
|
|||||||
|
|
||||||
const handleApprove = async () => {
|
const handleApprove = async () => {
|
||||||
if (!replyContent.trim()) {
|
if (!replyContent.trim()) {
|
||||||
alert('请填写处理意见')
|
toast.error('请填写处理意见')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
alert('申诉已通过')
|
toast.success('申诉已通过')
|
||||||
router.push('/agency/appeals')
|
router.push('/agency/appeals')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = async () => {
|
const handleReject = async () => {
|
||||||
if (!replyContent.trim()) {
|
if (!replyContent.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
alert('申诉已驳回')
|
toast.success('申诉已驳回')
|
||||||
router.push('/agency/appeals')
|
router.push('/agency/appeals')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal } from '@/components/ui/Modal'
|
import { Modal } from '@/components/ui/Modal'
|
||||||
@ -127,6 +128,7 @@ const platformRules = {
|
|||||||
export default function BriefConfigPage() {
|
export default function BriefConfigPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
// 品牌方 Brief(只读)
|
// 品牌方 Brief(只读)
|
||||||
const [brandBrief] = useState(mockBrandBrief)
|
const [brandBrief] = useState(mockBrandBrief)
|
||||||
@ -151,7 +153,7 @@ export default function BriefConfigPage() {
|
|||||||
|
|
||||||
// 下载文件
|
// 下载文件
|
||||||
const handleDownload = (file: BriefFile) => {
|
const handleDownload = (file: BriefFile) => {
|
||||||
alert(`下载文件: ${file.name}`)
|
toast.info(`下载文件: ${file.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预览文件
|
// 预览文件
|
||||||
@ -164,7 +166,7 @@ export default function BriefConfigPage() {
|
|||||||
setIsExporting(true)
|
setIsExporting(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||||
setIsExporting(false)
|
setIsExporting(false)
|
||||||
alert('平台规则文档已导出!')
|
toast.success('平台规则文档已导出!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI 解析
|
// AI 解析
|
||||||
@ -172,7 +174,7 @@ export default function BriefConfigPage() {
|
|||||||
setIsAIParsing(true)
|
setIsAIParsing(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
setIsAIParsing(false)
|
setIsAIParsing(false)
|
||||||
alert('AI 解析完成!')
|
toast.success('AI 解析完成!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
@ -180,7 +182,7 @@ export default function BriefConfigPage() {
|
|||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
alert('配置已保存!')
|
toast.success('配置已保存!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卖点操作
|
// 卖点操作
|
||||||
@ -243,7 +245,7 @@ export default function BriefConfigPage() {
|
|||||||
agencyFiles: [...prev.agencyFiles, newFile]
|
agencyFiles: [...prev.agencyFiles, newFile]
|
||||||
}))
|
}))
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
alert('文档上传成功!')
|
toast.success('文档上传成功!')
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeAgencyFile = (id: string) => {
|
const removeAgencyFile = (id: string) => {
|
||||||
@ -258,7 +260,7 @@ export default function BriefConfigPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDownloadAgencyFile = (file: AgencyFile) => {
|
const handleDownloadAgencyFile = (file: AgencyFile) => {
|
||||||
alert(`下载文件: ${file.name}`)
|
toast.info(`下载文件: ${file.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal } from '@/components/ui/Modal'
|
import { Modal } from '@/components/ui/Modal'
|
||||||
@ -165,6 +166,7 @@ function StageTag({ stage }: { stage: TaskStage }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AgencyCreatorsPage() {
|
export default function AgencyCreatorsPage() {
|
||||||
|
const toast = useToast()
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [showInviteModal, setShowInviteModal] = useState(false)
|
const [showInviteModal, setShowInviteModal] = useState(false)
|
||||||
const [inviteCreatorId, setInviteCreatorId] = useState('')
|
const [inviteCreatorId, setInviteCreatorId] = useState('')
|
||||||
@ -299,7 +301,7 @@ export default function AgencyCreatorsPage() {
|
|||||||
const handleConfirmAssign = () => {
|
const handleConfirmAssign = () => {
|
||||||
if (assignModal.creator && selectedProject) {
|
if (assignModal.creator && selectedProject) {
|
||||||
const project = mockProjects.find(p => p.id === selectedProject)
|
const project = mockProjects.find(p => p.id === selectedProject)
|
||||||
alert(`已将达人「${assignModal.creator.name}」分配到项目「${project?.name}」`)
|
toast.success(`已将达人「${assignModal.creator.name}」分配到项目「${project?.name}」`)
|
||||||
}
|
}
|
||||||
setAssignModal({ open: false, creator: null })
|
setAssignModal({ open: false, creator: null })
|
||||||
setSelectedProject('')
|
setSelectedProject('')
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
@ -120,6 +121,7 @@ const mockCurrentCompany = {
|
|||||||
|
|
||||||
export default function AgencyCompanyPage() {
|
export default function AgencyCompanyPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [formData, setFormData] = useState(mockCurrentCompany)
|
const [formData, setFormData] = useState(mockCurrentCompany)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
@ -182,7 +184,7 @@ export default function AgencyCompanyPage() {
|
|||||||
// 提交验证
|
// 提交验证
|
||||||
const handleSubmitVerify = async () => {
|
const handleSubmitVerify = async () => {
|
||||||
if (!verifyCode.trim()) {
|
if (!verifyCode.trim()) {
|
||||||
alert('请输入验证信息')
|
toast.error('请输入验证信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +198,7 @@ export default function AgencyCompanyPage() {
|
|||||||
setVerifyMethod(null)
|
setVerifyMethod(null)
|
||||||
setVerifyStep(1)
|
setVerifyStep(1)
|
||||||
setVerifyCode('')
|
setVerifyCode('')
|
||||||
alert('企业认证成功!')
|
toast.success('企业认证成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@ -204,7 +206,7 @@ export default function AgencyCompanyPage() {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
alert('公司信息已保存')
|
toast.success('公司信息已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 认证状态显示
|
// 认证状态显示
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
@ -26,6 +27,7 @@ const mockUserData = {
|
|||||||
|
|
||||||
export default function AgencyProfileEditPage() {
|
export default function AgencyProfileEditPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [formData, setFormData] = useState(mockUserData)
|
const [formData, setFormData] = useState(mockUserData)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
@ -35,8 +37,8 @@ export default function AgencyProfileEditPage() {
|
|||||||
await navigator.clipboard.writeText(formData.agencyId)
|
await navigator.clipboard.writeText(formData.agencyId)
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('复制失败:', err)
|
toast.error('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ export default function AgencyProfileEditPage() {
|
|||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
alert('个人信息已保存')
|
toast.success('个人信息已保存')
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
FileCheck
|
FileCheck
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
|
|
||||||
// 代理商数据
|
// 代理商数据
|
||||||
const mockAgency = {
|
const mockAgency = {
|
||||||
@ -87,6 +88,7 @@ const menuItems = [
|
|||||||
|
|
||||||
// 代理商卡片组件
|
// 代理商卡片组件
|
||||||
function AgencyCard() {
|
function AgencyCard() {
|
||||||
|
const toast = useToast()
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
// 复制代理商ID
|
// 复制代理商ID
|
||||||
@ -95,8 +97,8 @@ function AgencyCard() {
|
|||||||
await navigator.clipboard.writeText(mockAgency.agencyId)
|
await navigator.clipboard.writeText(mockAgency.agencyId)
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('复制失败:', err)
|
toast.error('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal } from '@/components/ui/Modal'
|
import { Modal } from '@/components/ui/Modal'
|
||||||
@ -179,6 +180,7 @@ export default function AgencyReportsPage() {
|
|||||||
const [exportFormat, setExportFormat] = useState<'csv' | 'excel' | 'pdf'>('excel')
|
const [exportFormat, setExportFormat] = useState<'csv' | 'excel' | 'pdf'>('excel')
|
||||||
const [isExporting, setIsExporting] = useState(false)
|
const [isExporting, setIsExporting] = useState(false)
|
||||||
const [exportSuccess, setExportSuccess] = useState(false)
|
const [exportSuccess, setExportSuccess] = useState(false)
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const currentData = mockDataByRange[dateRange]
|
const currentData = mockDataByRange[dateRange]
|
||||||
|
|
||||||
@ -199,9 +201,9 @@ export default function AgencyReportsPage() {
|
|||||||
downloadFile(csvContent, `${fileName}.csv`, 'text/csv')
|
downloadFile(csvContent, `${fileName}.csv`, 'text/csv')
|
||||||
} else if (exportFormat === 'excel') {
|
} else if (exportFormat === 'excel') {
|
||||||
// 实际项目中会使用 xlsx 库
|
// 实际项目中会使用 xlsx 库
|
||||||
alert(`Excel 文件「${fileName}.xlsx」已开始下载`)
|
toast.info(`Excel 文件「${fileName}.xlsx」已开始下载`)
|
||||||
} else {
|
} else {
|
||||||
alert(`PDF 文件「${fileName}.pdf」已开始下载`)
|
toast.info(`PDF 文件「${fileName}.pdf」已开始下载`)
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsExporting(false)
|
setIsExporting(false)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { ArrowLeft, Play, Pause, AlertTriangle, Shield, Radio } from 'lucide-react'
|
import { ArrowLeft, Play, Pause, AlertTriangle, Shield, Radio } from 'lucide-react'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
@ -85,6 +86,7 @@ function formatTimestamp(seconds: number): string {
|
|||||||
export default function ReviewPage() {
|
export default function ReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false)
|
const [showApproveModal, setShowApproveModal] = useState(false)
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false)
|
const [showRejectModal, setShowRejectModal] = useState(false)
|
||||||
@ -103,7 +105,7 @@ export default function ReviewPage() {
|
|||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = () => {
|
||||||
if (!rejectReason.trim()) {
|
if (!rejectReason.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowRejectModal(false)
|
setShowRejectModal(false)
|
||||||
@ -112,7 +114,7 @@ export default function ReviewPage() {
|
|||||||
|
|
||||||
const handleForcePass = () => {
|
const handleForcePass = () => {
|
||||||
if (!forcePassReason.trim()) {
|
if (!forcePassReason.trim()) {
|
||||||
alert('请填写强制通过原因')
|
toast.error('请填写强制通过原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowForcePassModal(false)
|
setShowForcePassModal(false)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
||||||
@ -165,13 +166,13 @@ function ScoreTag({ score }: { score: number }) {
|
|||||||
type ScriptTask = typeof mockScriptTasks[0]
|
type ScriptTask = typeof mockScriptTasks[0]
|
||||||
type VideoTask = typeof mockVideoTasks[0]
|
type VideoTask = typeof mockVideoTasks[0]
|
||||||
|
|
||||||
function ScriptTaskCard({ task, onPreview }: { task: ScriptTask; onPreview: (task: ScriptTask) => void }) {
|
function ScriptTaskCard({ task, onPreview, toast }: { task: ScriptTask; onPreview: (task: ScriptTask) => void; toast: ReturnType<typeof useToast> }) {
|
||||||
const riskConfig = riskLevelConfig[task.riskLevel]
|
const riskConfig = riskLevelConfig[task.riskLevel]
|
||||||
const platform = getPlatformInfo(task.platform)
|
const platform = getPlatformInfo(task.platform)
|
||||||
|
|
||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
alert(`下载文件: ${task.fileName}`)
|
toast.info(`下载文件: ${task.fileName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePreview = (e: React.MouseEvent) => {
|
const handlePreview = (e: React.MouseEvent) => {
|
||||||
@ -261,13 +262,13 @@ function ScriptTaskCard({ task, onPreview }: { task: ScriptTask; onPreview: (tas
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function VideoTaskCard({ task, onPreview }: { task: VideoTask; onPreview: (task: VideoTask) => void }) {
|
function VideoTaskCard({ task, onPreview, toast }: { task: VideoTask; onPreview: (task: VideoTask) => void; toast: ReturnType<typeof useToast> }) {
|
||||||
const riskConfig = riskLevelConfig[task.riskLevel]
|
const riskConfig = riskLevelConfig[task.riskLevel]
|
||||||
const platform = getPlatformInfo(task.platform)
|
const platform = getPlatformInfo(task.platform)
|
||||||
|
|
||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
alert(`下载文件: ${task.fileName}`)
|
toast.info(`下载文件: ${task.fileName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePreview = (e: React.MouseEvent) => {
|
const handlePreview = (e: React.MouseEvent) => {
|
||||||
@ -362,6 +363,7 @@ export default function AgencyReviewListPage() {
|
|||||||
const [activeTab, setActiveTab] = useState<'all' | 'script' | 'video'>('all')
|
const [activeTab, setActiveTab] = useState<'all' | 'script' | 'video'>('all')
|
||||||
const [previewScript, setPreviewScript] = useState<ScriptTask | null>(null)
|
const [previewScript, setPreviewScript] = useState<ScriptTask | null>(null)
|
||||||
const [previewVideo, setPreviewVideo] = useState<VideoTask | null>(null)
|
const [previewVideo, setPreviewVideo] = useState<VideoTask | null>(null)
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const filteredScripts = mockScriptTasks.filter(task =>
|
const filteredScripts = mockScriptTasks.filter(task =>
|
||||||
task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
@ -462,7 +464,7 @@ export default function AgencyReviewListPage() {
|
|||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{filteredScripts.length > 0 ? (
|
{filteredScripts.length > 0 ? (
|
||||||
filteredScripts.map((task) => (
|
filteredScripts.map((task) => (
|
||||||
<ScriptTaskCard key={task.id} task={task} onPreview={setPreviewScript} />
|
<ScriptTaskCard key={task.id} task={task} onPreview={setPreviewScript} toast={toast} />
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-text-tertiary">
|
<div className="text-center py-8 text-text-tertiary">
|
||||||
@ -489,7 +491,7 @@ export default function AgencyReviewListPage() {
|
|||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{filteredVideos.length > 0 ? (
|
{filteredVideos.length > 0 ? (
|
||||||
filteredVideos.map((task) => (
|
filteredVideos.map((task) => (
|
||||||
<VideoTaskCard key={task.id} task={task} onPreview={setPreviewVideo} />
|
<VideoTaskCard key={task.id} task={task} onPreview={setPreviewVideo} toast={toast} />
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-text-tertiary">
|
<div className="text-center py-8 text-text-tertiary">
|
||||||
@ -536,7 +538,7 @@ export default function AgencyReviewListPage() {
|
|||||||
<Button variant="secondary" onClick={() => setPreviewScript(null)}>
|
<Button variant="secondary" onClick={() => setPreviewScript(null)}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => alert(`下载文件: ${previewScript?.fileName}`)}>
|
<Button onClick={() => toast.info(`下载文件: ${previewScript?.fileName}`)}>
|
||||||
<Download size={16} />
|
<Download size={16} />
|
||||||
下载
|
下载
|
||||||
</Button>
|
</Button>
|
||||||
@ -581,7 +583,7 @@ export default function AgencyReviewListPage() {
|
|||||||
<Button variant="secondary" onClick={() => setPreviewVideo(null)}>
|
<Button variant="secondary" onClick={() => setPreviewVideo(null)}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => alert(`下载文件: ${previewVideo?.fileName}`)}>
|
<Button onClick={() => toast.info(`下载文件: ${previewVideo?.fileName}`)}>
|
||||||
<Download size={16} />
|
<Download size={16} />
|
||||||
下载
|
下载
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
||||||
@ -90,6 +91,7 @@ function ReviewProgressBar({ taskStatus }: { taskStatus: string }) {
|
|||||||
|
|
||||||
export default function AgencyScriptReviewPage() {
|
export default function AgencyScriptReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false)
|
const [showApproveModal, setShowApproveModal] = useState(false)
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false)
|
const [showRejectModal, setShowRejectModal] = useState(false)
|
||||||
@ -103,27 +105,27 @@ export default function AgencyScriptReviewPage() {
|
|||||||
|
|
||||||
const handleApprove = () => {
|
const handleApprove = () => {
|
||||||
setShowApproveModal(false)
|
setShowApproveModal(false)
|
||||||
alert('已提交品牌方终审!')
|
toast.success('已提交品牌方终审')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = () => {
|
||||||
if (!rejectReason.trim()) {
|
if (!rejectReason.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowRejectModal(false)
|
setShowRejectModal(false)
|
||||||
alert('已驳回')
|
toast.success('已驳回')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleForcePass = () => {
|
const handleForcePass = () => {
|
||||||
if (!forcePassReason.trim()) {
|
if (!forcePassReason.trim()) {
|
||||||
alert('请填写强制通过原因')
|
toast.error('请填写强制通过原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowForcePassModal(false)
|
setShowForcePassModal(false)
|
||||||
alert('已强制通过并提交品牌方终审!')
|
toast.success('已强制通过并提交品牌方终审')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
||||||
@ -114,6 +115,7 @@ function RiskLevelTag({ level }: { level: string }) {
|
|||||||
|
|
||||||
export default function AgencyVideoReviewPage() {
|
export default function AgencyVideoReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false)
|
const [showApproveModal, setShowApproveModal] = useState(false)
|
||||||
@ -130,27 +132,27 @@ export default function AgencyVideoReviewPage() {
|
|||||||
|
|
||||||
const handleApprove = () => {
|
const handleApprove = () => {
|
||||||
setShowApproveModal(false)
|
setShowApproveModal(false)
|
||||||
alert('已提交品牌方终审!')
|
toast.success('已提交品牌方终审')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = () => {
|
||||||
if (!rejectReason.trim()) {
|
if (!rejectReason.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowRejectModal(false)
|
setShowRejectModal(false)
|
||||||
alert('已驳回')
|
toast.success('已驳回')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleForcePass = () => {
|
const handleForcePass = () => {
|
||||||
if (!forcePassReason.trim()) {
|
if (!forcePassReason.trim()) {
|
||||||
alert('请填写强制通过原因')
|
toast.error('请填写强制通过原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowForcePassModal(false)
|
setShowForcePassModal(false)
|
||||||
alert('已强制通过并提交品牌方终审!')
|
toast.success('已强制通过并提交品牌方终审')
|
||||||
router.push('/agency/review')
|
router.push('/agency/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
@ -20,6 +21,7 @@ import {
|
|||||||
|
|
||||||
export default function AgencyAccountSettingsPage() {
|
export default function AgencyAccountSettingsPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [showOldPassword, setShowOldPassword] = useState(false)
|
const [showOldPassword, setShowOldPassword] = useState(false)
|
||||||
const [showNewPassword, setShowNewPassword] = useState(false)
|
const [showNewPassword, setShowNewPassword] = useState(false)
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||||
@ -39,21 +41,21 @@ export default function AgencyAccountSettingsPage() {
|
|||||||
|
|
||||||
const handleChangePassword = async () => {
|
const handleChangePassword = async () => {
|
||||||
if (!passwordForm.oldPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
|
if (!passwordForm.oldPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
|
||||||
alert('请填写完整密码信息')
|
toast.error('请填写完整密码信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||||
alert('两次输入的新密码不一致')
|
toast.error('两次输入的新密码不一致')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (passwordForm.newPassword.length < 8) {
|
if (passwordForm.newPassword.length < 8) {
|
||||||
alert('新密码长度不能少于8位')
|
toast.error('新密码长度不能少于8位')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
alert('密码修改成功')
|
toast.success('密码修改成功')
|
||||||
setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import {
|
import {
|
||||||
@ -102,6 +103,7 @@ function Toggle({ checked, onChange }: { checked: boolean; onChange: (checked: b
|
|||||||
|
|
||||||
export default function AgencyNotificationSettingsPage() {
|
export default function AgencyNotificationSettingsPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [settings, setSettings] = useState(initialSettings)
|
const [settings, setSettings] = useState(initialSettings)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ export default function AgencyNotificationSettingsPage() {
|
|||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
alert('通知设置已保存')
|
toast.success('通知设置已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
|||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal } from '@/components/ui/Modal'
|
import { Modal } from '@/components/ui/Modal'
|
||||||
import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag'
|
import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
Plus,
|
Plus,
|
||||||
@ -110,6 +111,7 @@ function StatusTag({ status }: { status: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AgenciesManagePage() {
|
export default function AgenciesManagePage() {
|
||||||
|
const toast = useToast()
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [agencies, setAgencies] = useState<Agency[]>(initialAgencies)
|
const [agencies, setAgencies] = useState<Agency[]>(initialAgencies)
|
||||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||||
@ -231,7 +233,7 @@ export default function AgenciesManagePage() {
|
|||||||
.filter(p => selectedProjects.includes(p.id))
|
.filter(p => selectedProjects.includes(p.id))
|
||||||
.map(p => p.name)
|
.map(p => p.name)
|
||||||
.join('、')
|
.join('、')
|
||||||
alert(`已将代理商「${assignModal.agency.name}」分配到项目「${projectNames}」`)
|
toast.success(`已将代理商「${assignModal.agency.name}」分配到项目「${projectNames}」`)
|
||||||
}
|
}
|
||||||
setAssignModal({ open: false, agency: null })
|
setAssignModal({ open: false, agency: null })
|
||||||
setSelectedProjects([])
|
setSelectedProjects([])
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Button } from '@/components/ui/Button'
|
|||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
import { Select } from '@/components/ui/Select'
|
import { Select } from '@/components/ui/Select'
|
||||||
import { SuccessTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
import { SuccessTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
Eye,
|
Eye,
|
||||||
@ -68,6 +69,7 @@ type TestResult = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AIConfigPage() {
|
export default function AIConfigPage() {
|
||||||
|
const toast = useToast()
|
||||||
const [provider, setProvider] = useState('oneapi')
|
const [provider, setProvider] = useState('oneapi')
|
||||||
const [baseUrl, setBaseUrl] = useState('https://oneapi.intelligrow.cn')
|
const [baseUrl, setBaseUrl] = useState('https://oneapi.intelligrow.cn')
|
||||||
const [apiKey, setApiKey] = useState('')
|
const [apiKey, setApiKey] = useState('')
|
||||||
@ -133,7 +135,7 @@ export default function AIConfigPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
alert('配置已保存')
|
toast.success('配置已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTestStatusIcon = (status: string) => {
|
const getTestStatusIcon = (status: string) => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'
|
|||||||
import { ArrowLeft, Check, X, CheckSquare, Video, Clock } from 'lucide-react'
|
import { ArrowLeft, Check, X, CheckSquare, Video, Clock } from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { getPlatformInfo } from '@/lib/platforms'
|
import { getPlatformInfo } from '@/lib/platforms'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
|
|
||||||
// 模拟待审核内容列表
|
// 模拟待审核内容列表
|
||||||
const mockReviewItems = [
|
const mockReviewItems = [
|
||||||
@ -99,6 +100,7 @@ function ReviewProgressBar({ currentStep }: { currentStep: number }) {
|
|||||||
|
|
||||||
export default function FinalReviewPage() {
|
export default function FinalReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [selectedItem, setSelectedItem] = useState(mockReviewItems[0])
|
const [selectedItem, setSelectedItem] = useState(mockReviewItems[0])
|
||||||
const [feedback, setFeedback] = useState('')
|
const [feedback, setFeedback] = useState('')
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
@ -108,19 +110,19 @@ export default function FinalReviewPage() {
|
|||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
alert('已通过审核')
|
toast.success('已通过审核')
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = async () => {
|
const handleReject = async () => {
|
||||||
if (!feedback.trim()) {
|
if (!feedback.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
alert('已驳回')
|
toast.success('已驳回')
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
setFeedback('')
|
setFeedback('')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
@ -82,6 +83,7 @@ const strictnessOptions = [
|
|||||||
export default function ProjectConfigPage() {
|
export default function ProjectConfigPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
const projectId = params.id as string
|
const projectId = params.id as string
|
||||||
|
|
||||||
const [brief, setBrief] = useState(mockData.brief)
|
const [brief, setBrief] = useState(mockData.brief)
|
||||||
@ -100,7 +102,7 @@ export default function ProjectConfigPage() {
|
|||||||
setIsSaving(true)
|
setIsSaving(true)
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
alert('配置已保存')
|
toast.success('配置已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRequirement = () => {
|
const addRequirement = () => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
@ -40,6 +41,7 @@ const mockAgencies = [
|
|||||||
|
|
||||||
export default function CreateProjectPage() {
|
export default function CreateProjectPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [projectName, setProjectName] = useState('')
|
const [projectName, setProjectName] = useState('')
|
||||||
const [deadline, setDeadline] = useState('')
|
const [deadline, setDeadline] = useState('')
|
||||||
const [briefFile, setBriefFile] = useState<File | null>(null)
|
const [briefFile, setBriefFile] = useState<File | null>(null)
|
||||||
@ -73,14 +75,14 @@ export default function CreateProjectPage() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!projectName.trim() || !deadline || !briefFile || selectedAgencies.length === 0 || !selectedPlatform) {
|
if (!projectName.trim() || !deadline || !briefFile || selectedAgencies.length === 0 || !selectedPlatform) {
|
||||||
alert('请填写完整信息')
|
toast.error('请填写完整信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||||
alert('项目创建成功!')
|
toast.success('项目创建成功!')
|
||||||
router.push('/brand')
|
router.push('/brand')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import Link from 'next/link'
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
import { SuccessTag, PendingTag, WarningTag, ErrorTag } from '@/components/ui/Tag'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Video,
|
Video,
|
||||||
@ -129,6 +130,7 @@ function TaskCard({
|
|||||||
type: 'script' | 'video'
|
type: 'script' | 'video'
|
||||||
onPreview: (task: ScriptTask | VideoTask, type: 'script' | 'video') => void
|
onPreview: (task: ScriptTask | VideoTask, type: 'script' | 'video') => void
|
||||||
}) {
|
}) {
|
||||||
|
const toast = useToast()
|
||||||
const href = type === 'script' ? `/brand/review/script/${task.id}` : `/brand/review/video/${task.id}`
|
const href = type === 'script' ? `/brand/review/script/${task.id}` : `/brand/review/video/${task.id}`
|
||||||
const platform = getPlatformInfo(task.platform)
|
const platform = getPlatformInfo(task.platform)
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ function TaskCard({
|
|||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
alert(`下载文件: ${task.fileName}`)
|
toast.info(`下载文件: ${task.fileName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -243,6 +245,7 @@ function TaskCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BrandReviewListPage() {
|
export default function BrandReviewListPage() {
|
||||||
|
const toast = useToast()
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [activeTab, setActiveTab] = useState<'all' | 'script' | 'video'>('all')
|
const [activeTab, setActiveTab] = useState<'all' | 'script' | 'video'>('all')
|
||||||
const [previewTask, setPreviewTask] = useState<{ task: ScriptTask | VideoTask; type: 'script' | 'video' } | null>(null)
|
const [previewTask, setPreviewTask] = useState<{ task: ScriptTask | VideoTask; type: 'script' | 'video' } | null>(null)
|
||||||
@ -445,7 +448,7 @@ export default function BrandReviewListPage() {
|
|||||||
<Button variant="secondary" onClick={() => setPreviewTask(null)}>
|
<Button variant="secondary" onClick={() => setPreviewTask(null)}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => alert(`下载文件: ${previewTask?.task.fileName}`)}>
|
<Button onClick={() => toast.info(`下载文件: ${previewTask?.task.fileName}`)}>
|
||||||
<Download size={16} />
|
<Download size={16} />
|
||||||
下载
|
下载
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Button } from '@/components/ui/Button'
|
|||||||
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
||||||
import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
||||||
import { ReviewSteps, getBrandReviewSteps } from '@/components/ui/ReviewSteps'
|
import { ReviewSteps, getBrandReviewSteps } from '@/components/ui/ReviewSteps'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
FileText,
|
FileText,
|
||||||
@ -99,6 +100,7 @@ function ReviewProgressBar({ taskStatus }: { taskStatus: string }) {
|
|||||||
export default function BrandScriptReviewPage() {
|
export default function BrandScriptReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false)
|
const [showApproveModal, setShowApproveModal] = useState(false)
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false)
|
const [showRejectModal, setShowRejectModal] = useState(false)
|
||||||
const [rejectReason, setRejectReason] = useState('')
|
const [rejectReason, setRejectReason] = useState('')
|
||||||
@ -109,17 +111,17 @@ export default function BrandScriptReviewPage() {
|
|||||||
|
|
||||||
const handleApprove = () => {
|
const handleApprove = () => {
|
||||||
setShowApproveModal(false)
|
setShowApproveModal(false)
|
||||||
alert('审核通过!')
|
toast.success('审核通过')
|
||||||
router.push('/brand/review')
|
router.push('/brand/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = () => {
|
||||||
if (!rejectReason.trim()) {
|
if (!rejectReason.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowRejectModal(false)
|
setShowRejectModal(false)
|
||||||
alert('已驳回')
|
toast.success('已驳回')
|
||||||
router.push('/brand/review')
|
router.push('/brand/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
import { Modal, ConfirmModal } from '@/components/ui/Modal'
|
||||||
@ -123,6 +124,7 @@ function RiskLevelTag({ level }: { level: string }) {
|
|||||||
export default function BrandVideoReviewPage() {
|
export default function BrandVideoReviewPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false)
|
const [showApproveModal, setShowApproveModal] = useState(false)
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false)
|
const [showRejectModal, setShowRejectModal] = useState(false)
|
||||||
@ -135,17 +137,17 @@ export default function BrandVideoReviewPage() {
|
|||||||
|
|
||||||
const handleApprove = () => {
|
const handleApprove = () => {
|
||||||
setShowApproveModal(false)
|
setShowApproveModal(false)
|
||||||
alert('审核通过!')
|
toast.success('审核通过!')
|
||||||
router.push('/brand/review')
|
router.push('/brand/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = () => {
|
||||||
if (!rejectReason.trim()) {
|
if (!rejectReason.trim()) {
|
||||||
alert('请填写驳回原因')
|
toast.error('请填写驳回原因')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setShowRejectModal(false)
|
setShowRejectModal(false)
|
||||||
alert('已驳回')
|
toast.success('已驳回')
|
||||||
router.push('/brand/review')
|
router.push('/brand/review')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Modal } from '@/components/ui/Modal'
|
import { Modal } from '@/components/ui/Modal'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
Shield,
|
Shield,
|
||||||
@ -32,6 +33,7 @@ import {
|
|||||||
|
|
||||||
export default function BrandSettingsPage() {
|
export default function BrandSettingsPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [notifications, setNotifications] = useState({
|
const [notifications, setNotifications] = useState({
|
||||||
email: true,
|
email: true,
|
||||||
push: true,
|
push: true,
|
||||||
@ -95,7 +97,7 @@ export default function BrandSettingsPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
alert('设置已保存')
|
toast.success('设置已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
@ -105,10 +107,10 @@ export default function BrandSettingsPage() {
|
|||||||
|
|
||||||
const handleChangePassword = () => {
|
const handleChangePassword = () => {
|
||||||
if (passwordForm.new !== passwordForm.confirm) {
|
if (passwordForm.new !== passwordForm.confirm) {
|
||||||
alert('两次输入的密码不一致')
|
toast.error('两次输入的密码不一致')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
alert('密码修改成功')
|
toast.success('密码修改成功')
|
||||||
setShowPasswordModal(false)
|
setShowPasswordModal(false)
|
||||||
setPasswordForm({ current: '', new: '', confirm: '' })
|
setPasswordForm({ current: '', new: '', confirm: '' })
|
||||||
}
|
}
|
||||||
@ -116,18 +118,18 @@ export default function BrandSettingsPage() {
|
|||||||
const handleEnable2FA = () => {
|
const handleEnable2FA = () => {
|
||||||
setTwoFAEnabled(true)
|
setTwoFAEnabled(true)
|
||||||
setShow2FAModal(false)
|
setShow2FAModal(false)
|
||||||
alert('双因素认证已启用')
|
toast.success('双因素认证已启用')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChangeEmail = () => {
|
const handleChangeEmail = () => {
|
||||||
alert(`邮箱已更新为 ${newEmail}`)
|
toast.success(`邮箱已更新为 ${newEmail}`)
|
||||||
setShowEmailModal(false)
|
setShowEmailModal(false)
|
||||||
setNewEmail('')
|
setNewEmail('')
|
||||||
setEmailCode('')
|
setEmailCode('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChangePhone = () => {
|
const handleChangePhone = () => {
|
||||||
alert(`手机号已更新为 ${newPhone}`)
|
toast.success(`手机号已更新为 ${newPhone}`)
|
||||||
setShowPhoneModal(false)
|
setShowPhoneModal(false)
|
||||||
setNewPhone('')
|
setNewPhone('')
|
||||||
setPhoneCode('')
|
setPhoneCode('')
|
||||||
@ -139,11 +141,11 @@ export default function BrandSettingsPage() {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
setIsExporting(false)
|
setIsExporting(false)
|
||||||
setShowExportModal(false)
|
setShowExportModal(false)
|
||||||
alert('导出任务已创建,完成后将通知您下载')
|
toast.info('导出任务已创建,完成后将通知您下载')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemoveDevice = (deviceId: string) => {
|
const handleRemoveDevice = (deviceId: string) => {
|
||||||
alert('已移除该设备的登录状态')
|
toast.success('已移除该设备的登录状态')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
|||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Input } from '@/components/ui/Input'
|
import { Input } from '@/components/ui/Input'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
|
|
||||||
// 模拟用户数据
|
// 模拟用户数据
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
@ -21,6 +22,7 @@ const mockUser = {
|
|||||||
|
|
||||||
export default function ProfileEditPage() {
|
export default function ProfileEditPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [idCopied, setIdCopied] = useState(false)
|
const [idCopied, setIdCopied] = useState(false)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@ -37,8 +39,8 @@ export default function ProfileEditPage() {
|
|||||||
await navigator.clipboard.writeText(mockUser.creatorId)
|
await navigator.clipboard.writeText(mockUser.creatorId)
|
||||||
setIdCopied(true)
|
setIdCopied(true)
|
||||||
setTimeout(() => setIdCopied(false), 2000)
|
setTimeout(() => setIdCopied(false), 2000)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('复制失败:', err)
|
toast.error('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
|
|
||||||
// 用户数据
|
// 用户数据
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
@ -84,6 +85,7 @@ const menuItems = [
|
|||||||
|
|
||||||
// 用户卡片组件
|
// 用户卡片组件
|
||||||
function UserCard() {
|
function UserCard() {
|
||||||
|
const toast = useToast()
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
// 复制达人ID
|
// 复制达人ID
|
||||||
@ -92,8 +94,8 @@ function UserCard() {
|
|||||||
await navigator.clipboard.writeText(mockUser.creatorId)
|
await navigator.clipboard.writeText(mockUser.creatorId)
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('复制失败:', err)
|
toast.error('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter, useParams } from 'next/navigation'
|
import { useRouter, useParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
FileText,
|
FileText,
|
||||||
@ -71,14 +72,15 @@ const mockAgencyBrief = {
|
|||||||
export default function TaskBriefPage() {
|
export default function TaskBriefPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const toast = useToast()
|
||||||
const [previewFile, setPreviewFile] = useState<AgencyBriefFile | null>(null)
|
const [previewFile, setPreviewFile] = useState<AgencyBriefFile | null>(null)
|
||||||
|
|
||||||
const handleDownload = (file: AgencyBriefFile) => {
|
const handleDownload = (file: AgencyBriefFile) => {
|
||||||
alert(`下载文件: ${file.name}`)
|
toast.info(`下载文件: ${file.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDownloadAll = () => {
|
const handleDownloadAll = () => {
|
||||||
alert('下载全部文件')
|
toast.info('下载全部文件')
|
||||||
}
|
}
|
||||||
|
|
||||||
const requiredPoints = mockAgencyBrief.sellingPoints.filter(sp => sp.required)
|
const requiredPoints = mockAgencyBrief.sellingPoints.filter(sp => sp.required)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import {
|
import {
|
||||||
Upload, Check, X, Folder, Bell, MessageCircle,
|
Upload, Check, X, Folder, Bell, MessageCircle,
|
||||||
XCircle, CheckCircle, Loader2, Scan, ArrowLeft,
|
XCircle, CheckCircle, Loader2, Scan, ArrowLeft,
|
||||||
@ -389,12 +390,12 @@ function ReviewProgressBar({ task }: { task: TaskData }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Brief文档查看组件
|
// Brief文档查看组件
|
||||||
function AgencyBriefSection() {
|
function AgencyBriefSection({ toast }: { toast: ReturnType<typeof useToast> }) {
|
||||||
const [isExpanded, setIsExpanded] = useState(true)
|
const [isExpanded, setIsExpanded] = useState(true)
|
||||||
const [previewFile, setPreviewFile] = useState<AgencyBriefFile | null>(null)
|
const [previewFile, setPreviewFile] = useState<AgencyBriefFile | null>(null)
|
||||||
|
|
||||||
const handleDownload = (file: AgencyBriefFile) => {
|
const handleDownload = (file: AgencyBriefFile) => {
|
||||||
alert(`下载文件: ${file.name}`)
|
toast.info(`下载文件: ${file.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const requiredPoints = mockAgencyBrief.sellingPoints.filter(sp => sp.required)
|
const requiredPoints = mockAgencyBrief.sellingPoints.filter(sp => sp.required)
|
||||||
@ -547,14 +548,14 @@ function AgencyBriefSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 上传界面
|
// 上传界面
|
||||||
function UploadView({ task }: { task: TaskData }) {
|
function UploadView({ task, toast }: { task: TaskData; toast: ReturnType<typeof useToast> }) {
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const isScript = task.phase === 'script'
|
const isScript = task.phase === 'script'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 h-full">
|
<div className="flex flex-col gap-6 h-full">
|
||||||
{/* Brief文档区域 - 仅脚本阶段显示 */}
|
{/* Brief文档区域 - 仅脚本阶段显示 */}
|
||||||
{isScript && <AgencyBriefSection />}
|
{isScript && <AgencyBriefSection toast={toast} />}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -962,6 +963,7 @@ function ApprovedView({ task }: { task: TaskData }) {
|
|||||||
export default function TaskDetailPage() {
|
export default function TaskDetailPage() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
const taskId = params.id as string
|
const taskId = params.id as string
|
||||||
|
|
||||||
const taskData = allTasksData[taskId]
|
const taskData = allTasksData[taskId]
|
||||||
@ -995,7 +997,7 @@ export default function TaskDetailPage() {
|
|||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (taskData.stage) {
|
switch (taskData.stage) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
return <UploadView task={taskData} />
|
return <UploadView task={taskData} toast={toast} />
|
||||||
case 'ai_reviewing':
|
case 'ai_reviewing':
|
||||||
return <AIReviewingView task={taskData} />
|
return <AIReviewingView task={taskData} />
|
||||||
case 'ai_result':
|
case 'ai_result':
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useRouter, useParams, useSearchParams } from 'next/navigation'
|
import { useRouter, useParams, useSearchParams } from 'next/navigation'
|
||||||
|
import { useToast } from '@/components/ui/Toast'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
import { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag'
|
||||||
@ -17,8 +18,48 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Eye,
|
Eye,
|
||||||
MessageSquare
|
MessageSquare,
|
||||||
|
Download,
|
||||||
|
File,
|
||||||
|
Target,
|
||||||
|
Ban,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { Modal } from '@/components/ui/Modal'
|
||||||
|
|
||||||
|
// 代理商Brief文档(达人可查看)
|
||||||
|
type AgencyBriefFile = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
size: string
|
||||||
|
uploadedAt: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockAgencyBrief = {
|
||||||
|
// 代理商上传的Brief文档
|
||||||
|
files: [
|
||||||
|
{ id: 'af1', name: '达人拍摄指南.pdf', size: '1.5MB', uploadedAt: '2026-02-02', description: '详细的拍摄流程和注意事项' },
|
||||||
|
{ id: 'af2', name: '产品卖点话术.docx', size: '800KB', uploadedAt: '2026-02-02', description: '推荐使用的话术和表达方式' },
|
||||||
|
{ id: 'af3', name: '品牌视觉参考.pdf', size: '3.2MB', uploadedAt: '2026-02-02', description: '视觉风格和拍摄参考示例' },
|
||||||
|
] as AgencyBriefFile[],
|
||||||
|
// 卖点要求
|
||||||
|
sellingPoints: [
|
||||||
|
{ id: 'sp1', content: 'SPF50+ PA++++', required: true },
|
||||||
|
{ id: 'sp2', content: '轻薄质地,不油腻', required: true },
|
||||||
|
{ id: 'sp3', content: '延展性好,易推开', required: false },
|
||||||
|
{ id: 'sp4', content: '适合敏感肌', required: false },
|
||||||
|
{ id: 'sp5', content: '夏日必备防晒', required: true },
|
||||||
|
],
|
||||||
|
// 违禁词
|
||||||
|
blacklistWords: [
|
||||||
|
{ id: 'bw1', word: '最好', reason: '绝对化用语' },
|
||||||
|
{ id: 'bw2', word: '第一', reason: '绝对化用语' },
|
||||||
|
{ id: 'bw3', word: '神器', reason: '夸大宣传' },
|
||||||
|
{ id: 'bw4', word: '完美', reason: '绝对化用语' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
// 模拟任务数据
|
// 模拟任务数据
|
||||||
const mockTask = {
|
const mockTask = {
|
||||||
@ -105,6 +146,161 @@ function getTaskByStatus(status: string) {
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 代理商Brief文档查看组件
|
||||||
|
function AgencyBriefSection({ toast }: { toast: ReturnType<typeof useToast> }) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true)
|
||||||
|
const [previewFile, setPreviewFile] = useState<AgencyBriefFile | null>(null)
|
||||||
|
|
||||||
|
const handleDownload = (file: AgencyBriefFile) => {
|
||||||
|
toast.info(`下载文件: ${file.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePreview = (file: AgencyBriefFile) => {
|
||||||
|
setPreviewFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredPoints = mockAgencyBrief.sellingPoints.filter(sp => sp.required)
|
||||||
|
const optionalPoints = mockAgencyBrief.sellingPoints.filter(sp => !sp.required)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card className="border-accent-indigo/30">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center justify-between">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<File size={18} className="text-accent-indigo" />
|
||||||
|
Brief 文档与要求
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="p-1 hover:bg-bg-elevated rounded"
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronUp size={18} className="text-text-tertiary" />
|
||||||
|
) : (
|
||||||
|
<ChevronDown size={18} className="text-text-tertiary" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
{isExpanded && (
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Brief文档列表 */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-medium text-text-primary mb-2 flex items-center gap-2">
|
||||||
|
<FileText size={14} className="text-accent-indigo" />
|
||||||
|
参考文档
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{mockAgencyBrief.files.map((file) => (
|
||||||
|
<div key={file.id} className="flex items-center justify-between p-3 bg-bg-elevated rounded-lg">
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
|
<div className="w-8 h-8 rounded bg-accent-indigo/15 flex items-center justify-center flex-shrink-0">
|
||||||
|
<FileText size={16} className="text-accent-indigo" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-sm font-medium text-text-primary truncate">{file.name}</p>
|
||||||
|
<p className="text-xs text-text-tertiary">{file.size}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 flex-shrink-0">
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => handlePreview(file)}>
|
||||||
|
<Eye size={14} />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => handleDownload(file)}>
|
||||||
|
<Download size={14} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 卖点要求 */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-medium text-text-primary mb-2 flex items-center gap-2">
|
||||||
|
<Target size={14} className="text-accent-green" />
|
||||||
|
卖点要求
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{requiredPoints.length > 0 && (
|
||||||
|
<div className="p-3 bg-accent-coral/10 rounded-lg border border-accent-coral/30">
|
||||||
|
<p className="text-xs text-accent-coral font-medium mb-2">必选卖点(必须提及)</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{requiredPoints.map((sp) => (
|
||||||
|
<span key={sp.id} className="px-2 py-1 text-xs bg-accent-coral/20 text-accent-coral rounded">
|
||||||
|
{sp.content}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{optionalPoints.length > 0 && (
|
||||||
|
<div className="p-3 bg-bg-elevated rounded-lg">
|
||||||
|
<p className="text-xs text-text-tertiary font-medium mb-2">可选卖点</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{optionalPoints.map((sp) => (
|
||||||
|
<span key={sp.id} className="px-2 py-1 text-xs bg-bg-page text-text-secondary rounded">
|
||||||
|
{sp.content}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 违禁词 */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-medium text-text-primary mb-2 flex items-center gap-2">
|
||||||
|
<Ban size={14} className="text-accent-coral" />
|
||||||
|
违禁词(请勿使用)
|
||||||
|
</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{mockAgencyBrief.blacklistWords.map((bw) => (
|
||||||
|
<span key={bw.id} className="px-2 py-1 text-xs bg-accent-coral/15 text-accent-coral rounded border border-accent-coral/30">
|
||||||
|
「{bw.word}」
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 文件预览弹窗 */}
|
||||||
|
<Modal
|
||||||
|
isOpen={!!previewFile}
|
||||||
|
onClose={() => setPreviewFile(null)}
|
||||||
|
title={previewFile?.name || '文件预览'}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="aspect-[4/3] bg-bg-elevated rounded-lg flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<FileText size={48} className="mx-auto text-accent-indigo mb-4" />
|
||||||
|
<p className="text-text-secondary">文件预览区域</p>
|
||||||
|
<p className="text-xs text-text-tertiary mt-1">实际开发中将嵌入文件预览组件</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button variant="secondary" onClick={() => setPreviewFile(null)}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
{previewFile && (
|
||||||
|
<Button onClick={() => handleDownload(previewFile)}>
|
||||||
|
<Download size={16} />
|
||||||
|
下载文件
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function UploadSection({ onUpload }: { onUpload: () => void }) {
|
function UploadSection({ onUpload }: { onUpload: () => void }) {
|
||||||
const [file, setFile] = useState<File | null>(null)
|
const [file, setFile] = useState<File | null>(null)
|
||||||
|
|
||||||
@ -336,6 +532,7 @@ export default function CreatorScriptPage() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
const toast = useToast()
|
||||||
const status = searchParams.get('status') || 'pending_upload'
|
const status = searchParams.get('status') || 'pending_upload'
|
||||||
|
|
||||||
const [task, setTask] = useState(getTaskByStatus(status))
|
const [task, setTask] = useState(getTaskByStatus(status))
|
||||||
@ -390,6 +587,9 @@ export default function CreatorScriptPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Brief文档与要求(始终显示) */}
|
||||||
|
<AgencyBriefSection toast={toast} />
|
||||||
|
|
||||||
{/* 根据状态显示不同内容 */}
|
{/* 根据状态显示不同内容 */}
|
||||||
{task.scriptStatus === 'pending_upload' && (
|
{task.scriptStatus === 'pending_upload' && (
|
||||||
<UploadSection onUpload={simulateUpload} />
|
<UploadSection onUpload={simulateUpload} />
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import { AuthProvider } from '@/contexts/AuthContext'
|
import { AuthProvider } from '@/contexts/AuthContext'
|
||||||
|
import { ToastProvider } from '@/components/ui/Toast'
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: '秒思智能审核',
|
title: '秒思智能审核',
|
||||||
@ -14,7 +15,9 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="zh-CN" className="h-full">
|
<html lang="zh-CN" className="h-full">
|
||||||
<body className="h-full bg-bg-page text-text-primary font-sans">
|
<body className="h-full bg-bg-page text-text-primary font-sans">
|
||||||
|
<ToastProvider>
|
||||||
<AuthProvider>{children}</AuthProvider>
|
<AuthProvider>{children}</AuthProvider>
|
||||||
|
</ToastProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export {
|
|||||||
getFileCategory,
|
getFileCategory,
|
||||||
type FileInfo
|
type FileInfo
|
||||||
} from './ui/FilePreview';
|
} from './ui/FilePreview';
|
||||||
|
export { ToastProvider, useToast } from './ui/Toast';
|
||||||
|
|
||||||
// 导航组件
|
// 导航组件
|
||||||
export { BottomNav } from './navigation/BottomNav';
|
export { BottomNav } from './navigation/BottomNav';
|
||||||
|
|||||||
134
frontend/components/ui/Toast.tsx
Normal file
134
frontend/components/ui/Toast.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
|
||||||
|
import { CheckCircle, XCircle, AlertTriangle, Info, X } from 'lucide-react'
|
||||||
|
|
||||||
|
// Toast 类型
|
||||||
|
type ToastType = 'success' | 'error' | 'warning' | 'info'
|
||||||
|
|
||||||
|
// Toast 项
|
||||||
|
interface ToastItem {
|
||||||
|
id: string
|
||||||
|
type: ToastType
|
||||||
|
message: string
|
||||||
|
duration?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast Context
|
||||||
|
interface ToastContextType {
|
||||||
|
toast: {
|
||||||
|
success: (message: string, duration?: number) => void
|
||||||
|
error: (message: string, duration?: number) => void
|
||||||
|
warning: (message: string, duration?: number) => void
|
||||||
|
info: (message: string, duration?: number) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToastContext = createContext<ToastContextType | null>(null)
|
||||||
|
|
||||||
|
// Toast 图标配置
|
||||||
|
const toastConfig = {
|
||||||
|
success: {
|
||||||
|
icon: CheckCircle,
|
||||||
|
bgColor: 'bg-accent-green/15',
|
||||||
|
borderColor: 'border-accent-green/30',
|
||||||
|
iconColor: 'text-accent-green',
|
||||||
|
textColor: 'text-accent-green',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
icon: XCircle,
|
||||||
|
bgColor: 'bg-accent-coral/15',
|
||||||
|
borderColor: 'border-accent-coral/30',
|
||||||
|
iconColor: 'text-accent-coral',
|
||||||
|
textColor: 'text-accent-coral',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
icon: AlertTriangle,
|
||||||
|
bgColor: 'bg-accent-amber/15',
|
||||||
|
borderColor: 'border-accent-amber/30',
|
||||||
|
iconColor: 'text-accent-amber',
|
||||||
|
textColor: 'text-accent-amber',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
icon: Info,
|
||||||
|
bgColor: 'bg-accent-indigo/15',
|
||||||
|
borderColor: 'border-accent-indigo/30',
|
||||||
|
iconColor: 'text-accent-indigo',
|
||||||
|
textColor: 'text-accent-indigo',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个 Toast 组件
|
||||||
|
function ToastItem({ item, onClose }: { item: ToastItem; onClose: (id: string) => void }) {
|
||||||
|
const config = toastConfig[item.type]
|
||||||
|
const Icon = config.icon
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-3 px-4 py-3 rounded-xl border ${config.bgColor} ${config.borderColor} shadow-lg animate-slide-in min-w-[280px] max-w-[400px]`}
|
||||||
|
>
|
||||||
|
<Icon size={20} className={config.iconColor} />
|
||||||
|
<span className={`flex-1 text-sm font-medium ${config.textColor}`}>{item.message}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onClose(item.id)}
|
||||||
|
className="p-1 rounded-lg hover:bg-white/10 transition-colors"
|
||||||
|
>
|
||||||
|
<X size={16} className="text-text-tertiary" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast Provider
|
||||||
|
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [toasts, setToasts] = useState<ToastItem[]>([])
|
||||||
|
|
||||||
|
const removeToast = useCallback((id: string) => {
|
||||||
|
setToasts((prev) => prev.filter((t) => t.id !== id))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const addToast = useCallback((type: ToastType, message: string, duration = 3000) => {
|
||||||
|
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
const newToast: ToastItem = { id, type, message, duration }
|
||||||
|
|
||||||
|
setToasts((prev) => [...prev, newToast])
|
||||||
|
|
||||||
|
// 自动移除
|
||||||
|
if (duration > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
removeToast(id)
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
}, [removeToast])
|
||||||
|
|
||||||
|
const toast = {
|
||||||
|
success: (message: string, duration?: number) => addToast('success', message, duration),
|
||||||
|
error: (message: string, duration?: number) => addToast('error', message, duration),
|
||||||
|
warning: (message: string, duration?: number) => addToast('warning', message, duration),
|
||||||
|
info: (message: string, duration?: number) => addToast('info', message, duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastContext.Provider value={{ toast }}>
|
||||||
|
{children}
|
||||||
|
{/* Toast 容器 */}
|
||||||
|
<div className="fixed top-4 right-4 z-[9999] flex flex-col gap-2">
|
||||||
|
{toasts.map((item) => (
|
||||||
|
<ToastItem key={item.id} item={item} onClose={removeToast} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ToastContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// useToast Hook
|
||||||
|
export function useToast() {
|
||||||
|
const context = useContext(ToastContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useToast must be used within a ToastProvider')
|
||||||
|
}
|
||||||
|
return context.toast
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToastProvider
|
||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useCallback, useEffect } from 'react'
|
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import type {
|
import type {
|
||||||
VideoReviewRequest,
|
VideoReviewRequest,
|
||||||
@ -24,6 +24,7 @@ export function useReview(options: UseReviewOptions = {}) {
|
|||||||
const [isPolling, setIsPolling] = useState(false)
|
const [isPolling, setIsPolling] = useState(false)
|
||||||
const [task, setTask] = useState<ReviewTask | null>(null)
|
const [task, setTask] = useState<ReviewTask | null>(null)
|
||||||
const [error, setError] = useState<Error | null>(null)
|
const [error, setError] = useState<Error | null>(null)
|
||||||
|
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交审核
|
* 提交审核
|
||||||
@ -97,10 +98,22 @@ export function useReview(options: UseReviewOptions = {}) {
|
|||||||
}
|
}
|
||||||
}, [task?.createdAt])
|
}, [task?.createdAt])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除轮询定时器
|
||||||
|
*/
|
||||||
|
const clearPollingInterval = useCallback(() => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current)
|
||||||
|
intervalRef.current = null
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始轮询进度
|
* 开始轮询进度
|
||||||
*/
|
*/
|
||||||
const startPolling = useCallback((reviewId: string) => {
|
const startPolling = useCallback((reviewId: string) => {
|
||||||
|
// 清除之前的轮询(如果有)
|
||||||
|
clearPollingInterval()
|
||||||
setIsPolling(true)
|
setIsPolling(true)
|
||||||
|
|
||||||
const poll = async () => {
|
const poll = async () => {
|
||||||
@ -108,36 +121,45 @@ export function useReview(options: UseReviewOptions = {}) {
|
|||||||
const progress = await fetchProgress(reviewId)
|
const progress = await fetchProgress(reviewId)
|
||||||
|
|
||||||
if (progress.status === 'completed') {
|
if (progress.status === 'completed') {
|
||||||
|
clearPollingInterval()
|
||||||
setIsPolling(false)
|
setIsPolling(false)
|
||||||
const result = await fetchResult(reviewId)
|
const result = await fetchResult(reviewId)
|
||||||
onComplete?.(result)
|
onComplete?.(result)
|
||||||
} else if (progress.status === 'failed') {
|
} else if (progress.status === 'failed') {
|
||||||
|
clearPollingInterval()
|
||||||
setIsPolling(false)
|
setIsPolling(false)
|
||||||
const error = new Error('审核失败')
|
const error = new Error('审核失败')
|
||||||
setError(error)
|
setError(error)
|
||||||
onError?.(error)
|
onError?.(error)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
// 继续轮询,忽略单次错误
|
// 继续轮询,忽略单次错误
|
||||||
console.error('Polling error:', err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const intervalId = setInterval(poll, pollingInterval)
|
intervalRef.current = setInterval(poll, pollingInterval)
|
||||||
poll() // 立即执行一次
|
poll() // 立即执行一次
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(intervalId)
|
clearPollingInterval()
|
||||||
setIsPolling(false)
|
setIsPolling(false)
|
||||||
}
|
}
|
||||||
}, [fetchProgress, fetchResult, pollingInterval, onComplete, onError])
|
}, [fetchProgress, fetchResult, pollingInterval, onComplete, onError, clearPollingInterval])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止轮询
|
* 停止轮询
|
||||||
*/
|
*/
|
||||||
const stopPolling = useCallback(() => {
|
const stopPolling = useCallback(() => {
|
||||||
|
clearPollingInterval()
|
||||||
setIsPolling(false)
|
setIsPolling(false)
|
||||||
}, [])
|
}, [clearPollingInterval])
|
||||||
|
|
||||||
|
// 组件卸载时清除定时器,防止内存泄漏
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearPollingInterval()
|
||||||
|
}
|
||||||
|
}, [clearPollingInterval])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置状态
|
* 重置状态
|
||||||
|
|||||||
@ -41,7 +41,6 @@ class ApiClient {
|
|||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
const message = error.response?.data?.detail || error.message || '请求失败'
|
const message = error.response?.data?.detail || error.message || '请求失败'
|
||||||
console.error('API Error:', message)
|
|
||||||
return Promise.reject(new Error(message))
|
return Promise.reject(new Error(message))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -140,6 +140,22 @@
|
|||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast 滑入动画 */
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in {
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
5. Utility Classes (工具类)
|
5. Utility Classes (工具类)
|
||||||
======================================== */
|
======================================== */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user