主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
636 lines
22 KiB
TypeScript
636 lines
22 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import { useParams, useRouter } from 'next/navigation'
|
||
import {
|
||
Upload, Check, X, Folder, Bell, Play, MessageCircle,
|
||
XCircle, CheckCircle, Loader2, Scan, ArrowLeft
|
||
} from 'lucide-react'
|
||
import { DesktopLayout } from '@/components/layout/DesktopLayout'
|
||
import { MobileLayout } from '@/components/layout/MobileLayout'
|
||
import { cn } from '@/lib/utils'
|
||
import { api } from '@/lib/api'
|
||
import type { TaskResponse } from '@/types/task'
|
||
|
||
// 任务状态类型
|
||
type TaskStatus = 'pending_script' | 'pending_video' | 'ai_reviewing' | 'agency_reviewing' | 'need_revision' | 'passed'
|
||
|
||
type RequirementProfile = {
|
||
title?: string
|
||
platform?: string
|
||
deadline?: string
|
||
progress?: number
|
||
statusHint?: TaskStatus
|
||
issues?: Array<{ title: string; description: string; timestamp?: string }>
|
||
reviewLogs?: Array<{ time: string; message: string; status: 'done' | 'loading' | 'pending' }>
|
||
}
|
||
|
||
type TaskDetail = {
|
||
id: string
|
||
title: string
|
||
platform: string
|
||
deadline: string
|
||
status: TaskStatus
|
||
currentStep: number
|
||
progress?: number
|
||
issues?: Array<{ title: string; description: string; timestamp?: string }>
|
||
reviewLogs?: Array<{ time: string; message: string; status: 'done' | 'loading' | 'pending' }>
|
||
}
|
||
|
||
// 任务配置(占位数据)
|
||
const taskRequirementProfiles: Record<string, RequirementProfile> = {
|
||
'task-001': {
|
||
title: 'XX品牌618推广',
|
||
platform: '抖音',
|
||
deadline: '2026-02-10',
|
||
statusHint: 'pending_script',
|
||
},
|
||
'task-002': {
|
||
title: 'YY美妆新品',
|
||
platform: '小红书',
|
||
deadline: '2026-02-15',
|
||
progress: 62,
|
||
statusHint: 'ai_reviewing',
|
||
reviewLogs: [
|
||
{ time: '14:32:01', message: '视频上传完成', status: 'done' },
|
||
{ time: '14:32:15', message: '任务规则已加载', status: 'done' },
|
||
{ time: '14:32:28', message: '开始 ASR 语音识别', status: 'done' },
|
||
{ time: '14:33:45', message: '正在分析视觉合规性问题...', status: 'loading' },
|
||
],
|
||
},
|
||
'task-003': {
|
||
title: 'ZZ饮品夏日',
|
||
platform: '抖音',
|
||
deadline: '2026-02-08',
|
||
statusHint: 'need_revision',
|
||
issues: [
|
||
{
|
||
title: '检测到竞品 Logo',
|
||
description: '画面中 0:15-0:18 出现竞品「百事可乐」的 Logo,可能造成合规风险。',
|
||
timestamp: '0:15',
|
||
},
|
||
{
|
||
title: '禁用词语出现',
|
||
description: '视频中出现「最好喝」「第一」等绝对化用语,可能违反广告法。',
|
||
timestamp: '0:42',
|
||
},
|
||
],
|
||
},
|
||
'task-004': {
|
||
title: 'AA数码新品发布',
|
||
platform: '抖音',
|
||
deadline: '2026-02-20',
|
||
statusHint: 'passed',
|
||
},
|
||
'task-005': {
|
||
title: 'BB运动饮料',
|
||
platform: '抖音',
|
||
deadline: '2026-02-12',
|
||
statusHint: 'pending_video',
|
||
},
|
||
}
|
||
|
||
const platformLabelMap: Record<string, string> = {
|
||
douyin: '抖音',
|
||
xiaohongshu: '小红书',
|
||
bilibili: 'B站',
|
||
kuaishou: '快手',
|
||
}
|
||
|
||
const getPlatformLabel = (platform?: string) => {
|
||
if (!platform) return '未知平台'
|
||
return platformLabelMap[platform] || platform
|
||
}
|
||
|
||
const deriveTaskStatus = (task: TaskResponse): TaskStatus => {
|
||
if (!task.has_script) {
|
||
return 'pending_script'
|
||
}
|
||
if (!task.has_video) {
|
||
return 'pending_video'
|
||
}
|
||
if (task.status === 'approved') {
|
||
return 'passed'
|
||
}
|
||
if (task.status === 'rejected' || task.status === 'failed') {
|
||
return 'need_revision'
|
||
}
|
||
if (task.status === 'pending' || task.status === 'processing') {
|
||
return 'ai_reviewing'
|
||
}
|
||
return 'agency_reviewing'
|
||
}
|
||
|
||
const getCurrentStep = (status: TaskStatus) => {
|
||
if (status === 'ai_reviewing' || status === 'need_revision') {
|
||
return 2
|
||
}
|
||
if (status === 'agency_reviewing') {
|
||
return 3
|
||
}
|
||
if (status === 'passed') {
|
||
return 4
|
||
}
|
||
return 1
|
||
}
|
||
|
||
const buildTaskDetail = (task: TaskResponse): TaskDetail => {
|
||
const profile = taskRequirementProfiles[task.task_id]
|
||
const status = deriveTaskStatus(task)
|
||
const platformLabel = profile?.platform || getPlatformLabel(task.platform)
|
||
|
||
return {
|
||
id: task.task_id,
|
||
title: profile?.title || `任务 ${task.task_id}`,
|
||
platform: platformLabel,
|
||
deadline: profile?.deadline || '待确认',
|
||
status,
|
||
currentStep: getCurrentStep(status),
|
||
progress: profile?.progress,
|
||
issues: profile?.issues,
|
||
reviewLogs: profile?.reviewLogs,
|
||
}
|
||
}
|
||
|
||
// 审核进度条组件
|
||
function ReviewProgressBar({ currentStep, status }: { currentStep: number; status: TaskStatus }) {
|
||
const steps = [
|
||
{ label: '已提交', step: 1 },
|
||
{ label: 'AI审核', step: 2 },
|
||
{ label: '代理商审核', step: 3 },
|
||
{ label: '最终结果', step: 4 },
|
||
]
|
||
|
||
return (
|
||
<div className="flex items-center w-full">
|
||
{steps.map((s, index) => {
|
||
const isCompleted = s.step < currentStep || (s.step === currentStep && status === 'passed')
|
||
const isCurrent = s.step === currentStep && status !== 'passed'
|
||
const isError = isCurrent && status === 'need_revision'
|
||
|
||
return (
|
||
<div key={s.step} className="flex items-center flex-1">
|
||
<div className="flex flex-col items-center gap-1 w-20">
|
||
<div className={cn(
|
||
'w-8 h-8 rounded-2xl flex items-center justify-center',
|
||
isCompleted ? 'bg-accent-green' :
|
||
isError ? 'bg-accent-coral' :
|
||
isCurrent ? 'bg-accent-indigo' :
|
||
'bg-bg-elevated border-[1.5px] border-border-subtle'
|
||
)}>
|
||
{isCompleted && <Check className="w-4 h-4 text-white" />}
|
||
{isCurrent && !isError && <Loader2 className="w-4 h-4 text-white animate-spin" />}
|
||
{isError && <X className="w-4 h-4 text-white" />}
|
||
</div>
|
||
<span className={cn(
|
||
'text-xs',
|
||
isCompleted ? 'text-text-secondary' :
|
||
isError ? 'text-accent-coral font-semibold' :
|
||
isCurrent ? 'text-accent-indigo font-semibold' :
|
||
'text-text-tertiary'
|
||
)}>
|
||
{s.label}
|
||
</span>
|
||
</div>
|
||
{index < steps.length - 1 && (
|
||
<div className={cn(
|
||
'h-0.5 flex-1',
|
||
s.step < currentStep || (s.step === currentStep && status === 'passed') ? 'bg-accent-green' : 'bg-border-subtle'
|
||
)} />
|
||
)}
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 上传界面
|
||
function UploadView({ task }: { task: TaskDetail }) {
|
||
const [isDragging, setIsDragging] = useState(false)
|
||
const isScriptStep = task.status === 'pending_script'
|
||
const title = isScriptStep ? '上传脚本' : '上传视频'
|
||
const subtitle = isScriptStep
|
||
? '支持粘贴文本或上传文档'
|
||
: '支持 MP4/MOV 格式,≤ 100MB'
|
||
const actionLabel = isScriptStep ? '选择脚本文档' : '选择视频文件'
|
||
const hintText = isScriptStep ? '也可以直接粘贴脚本文本后提交' : '上传完成后将自动进入 AI 审核'
|
||
|
||
return (
|
||
<div className="flex flex-col gap-6 h-full">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-text-primary">{title}</h3>
|
||
<p className="text-sm text-text-tertiary">{subtitle}</p>
|
||
</div>
|
||
<span className="px-2.5 py-1 rounded-full text-xs font-semibold bg-accent-indigo/15 text-accent-indigo">
|
||
待提交
|
||
</span>
|
||
</div>
|
||
|
||
<div
|
||
className={cn(
|
||
'flex-1 flex flex-col items-center justify-center gap-5 rounded-2xl border-2 border-dashed transition-colors card-shadow bg-bg-card',
|
||
isDragging ? 'border-accent-indigo bg-accent-indigo/5' : 'border-border-subtle'
|
||
)}
|
||
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
|
||
onDragLeave={() => setIsDragging(false)}
|
||
onDrop={(e) => { e.preventDefault(); setIsDragging(false) }}
|
||
>
|
||
<div className="w-20 h-20 rounded-[40px] bg-gradient-to-br from-accent-indigo to-[#4F46E5] opacity-15 flex items-center justify-center">
|
||
<Upload className="w-10 h-10 text-accent-indigo" />
|
||
</div>
|
||
|
||
<div className="flex flex-col items-center gap-2 text-center">
|
||
<p className="text-lg font-semibold text-text-primary">点击或拖拽文件到此处</p>
|
||
<p className="text-sm text-text-tertiary">{subtitle}</p>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="flex items-center gap-2 px-8 py-3.5 rounded-xl bg-gradient-to-r from-accent-indigo to-[#4F46E5] text-white font-semibold"
|
||
>
|
||
<Upload className="w-5 h-5" />
|
||
{actionLabel}
|
||
</button>
|
||
|
||
<p className="text-xs text-text-tertiary">{hintText}</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// AI 审核中界面
|
||
function ReviewingView({ task }: { task: TaskDetail }) {
|
||
return (
|
||
<div className="flex-1 flex items-center justify-center">
|
||
<div className="bg-bg-card rounded-[20px] p-10 card-shadow flex flex-col items-center gap-8 w-full max-w-md">
|
||
{/* 任务标签 */}
|
||
<div className="flex items-center gap-2 px-4 py-2 bg-bg-elevated rounded-lg">
|
||
<Folder className="w-3.5 h-3.5 text-text-tertiary" />
|
||
<span className="text-xs font-medium text-text-tertiary">产品种草视频 · 时长 60-90秒</span>
|
||
</div>
|
||
|
||
{/* 扫描动画 */}
|
||
<div className="relative w-[180px] h-[180px] flex items-center justify-center">
|
||
{/* 外圈渐变 */}
|
||
<div className="absolute inset-0 rounded-full bg-gradient-radial from-accent-indigo/50 via-accent-indigo/20 to-transparent" />
|
||
{/* 中心圆 */}
|
||
<div className="w-[72px] h-[72px] rounded-full bg-gradient-to-br from-accent-indigo to-[#4F46E5] flex items-center justify-center shadow-[0_0_24px_rgba(99,102,241,0.5)]">
|
||
<Scan className="w-8 h-8 text-white animate-pulse" />
|
||
</div>
|
||
</div>
|
||
|
||
{/* 进度信息 */}
|
||
<div className="flex flex-col items-center gap-2 w-full">
|
||
<h2 className="text-[22px] font-semibold text-text-primary">AI 正在审核您的视频</h2>
|
||
<p className="text-sm text-text-secondary">预计还需 2-3 分钟,可先离开页面</p>
|
||
|
||
{/* 进度条 */}
|
||
<div className="flex items-center gap-3 w-full pt-3">
|
||
<div className="flex-1 h-2 bg-bg-elevated rounded-full overflow-hidden">
|
||
<div
|
||
className="h-full bg-gradient-to-r from-accent-indigo to-[#4F46E5] rounded-full transition-all duration-300"
|
||
style={{ width: `${task.progress || 0}%` }}
|
||
/>
|
||
</div>
|
||
<span className="text-sm font-semibold text-accent-indigo">{task.progress || 0}%</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 日志区 */}
|
||
<div className="w-full bg-bg-elevated rounded-xl p-5 flex flex-col gap-2.5">
|
||
<div className="flex items-center gap-1.5">
|
||
<span className="w-2 h-2 rounded-full bg-accent-green" />
|
||
<span className="text-xs font-medium text-text-secondary">处理日志</span>
|
||
</div>
|
||
<div className="flex flex-col gap-2">
|
||
{task.reviewLogs?.map((log, index) => (
|
||
<div key={index} className="flex items-center gap-2 text-xs">
|
||
<span className="text-text-tertiary font-mono">{log.time}</span>
|
||
<span className={cn(
|
||
log.status === 'done' ? 'text-text-secondary' :
|
||
log.status === 'loading' ? 'text-accent-indigo' :
|
||
'text-text-tertiary'
|
||
)}>
|
||
{log.message}
|
||
</span>
|
||
{log.status === 'loading' && <Loader2 className="w-3 h-3 text-accent-indigo animate-spin" />}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 通知按钮 */}
|
||
<button
|
||
type="button"
|
||
className="flex items-center gap-2 px-6 py-3 rounded-[10px] bg-bg-page border border-border-subtle text-text-secondary text-[13px] font-medium"
|
||
>
|
||
<Bell className="w-4 h-4" />
|
||
完成后通过微信通知我
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 审核结果界面
|
||
function ResultView({ task }: { task: TaskDetail }) {
|
||
const isNeedRevision = task.status === 'need_revision'
|
||
const isPassed = task.status === 'passed'
|
||
|
||
return (
|
||
<div className="flex flex-col gap-6 h-full">
|
||
{/* 审核流程进度 */}
|
||
<div className="bg-bg-card rounded-xl p-4 px-6 flex items-center card-shadow">
|
||
<div className="flex flex-col gap-1 w-[140px]">
|
||
<span className="text-sm font-semibold text-text-primary">审核进度</span>
|
||
<span className="text-xs text-text-tertiary">更新于 5分钟前</span>
|
||
</div>
|
||
<div className="flex-1">
|
||
<ReviewProgressBar currentStep={task.currentStep} status={task.status} />
|
||
</div>
|
||
</div>
|
||
|
||
{/* 状态横幅 */}
|
||
<div className={cn(
|
||
'flex items-center gap-3 px-6 py-4 rounded-xl',
|
||
isNeedRevision ? 'bg-accent-coral' : 'bg-accent-green'
|
||
)}>
|
||
{isNeedRevision ? (
|
||
<XCircle className="w-6 h-6 text-white" />
|
||
) : (
|
||
<CheckCircle className="w-6 h-6 text-white" />
|
||
)}
|
||
<div className="flex flex-col gap-0.5">
|
||
<span className="text-base font-semibold text-white">
|
||
{isNeedRevision ? '需要修改' : '审核通过'}
|
||
</span>
|
||
<span className="text-sm text-white/90">
|
||
{isNeedRevision
|
||
? `发现 ${task.issues?.length || 0} 处违规问题,请修改后重新提交`
|
||
: '恭喜!您的视频已通过所有审核'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 内容区 */}
|
||
<div className="flex gap-6 flex-1 min-h-0">
|
||
{/* 左侧:视频预览 */}
|
||
<div className="flex-1">
|
||
<div className="bg-bg-card rounded-2xl card-shadow h-full flex items-center justify-center">
|
||
<div className="w-[560px] h-[315px] rounded-xl bg-black flex items-center justify-center">
|
||
<div className="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center cursor-pointer hover:bg-white/30 transition-colors">
|
||
<Play className="w-8 h-8 text-white ml-1" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 右侧:问题清单 */}
|
||
{isNeedRevision && task.issues && task.issues.length > 0 && (
|
||
<div className="w-[420px]">
|
||
<div className="bg-bg-card rounded-2xl p-6 card-shadow flex flex-col gap-4">
|
||
<h3 className="text-lg font-semibold text-text-primary">问题清单</h3>
|
||
<div className="flex flex-col gap-4">
|
||
{task.issues.map((issue, index) => (
|
||
<div key={index} className="bg-bg-elevated rounded-xl p-4 flex flex-col gap-3">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<span className="px-2 py-0.5 rounded bg-accent-coral/15 text-accent-coral text-xs font-semibold">
|
||
违规
|
||
</span>
|
||
<span className="text-sm font-semibold text-text-primary">{issue.title}</span>
|
||
</div>
|
||
{issue.timestamp && (
|
||
<button
|
||
type="button"
|
||
className="text-xs text-accent-indigo font-medium"
|
||
>
|
||
定位到视频
|
||
</button>
|
||
)}
|
||
</div>
|
||
<p className="text-[13px] text-text-secondary leading-relaxed">{issue.description}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function TaskDetailPage() {
|
||
const params = useParams()
|
||
const router = useRouter()
|
||
const taskId = params.id as string
|
||
const [isMobile, setIsMobile] = useState(true)
|
||
const [taskDetail, setTaskDetail] = useState<TaskDetail | null>(null)
|
||
const [isLoading, setIsLoading] = useState(true)
|
||
|
||
useEffect(() => {
|
||
const checkMobile = () => setIsMobile(window.innerWidth < 1024)
|
||
checkMobile()
|
||
window.addEventListener('resize', checkMobile)
|
||
return () => window.removeEventListener('resize', checkMobile)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
let isMounted = true
|
||
|
||
const fetchTask = async () => {
|
||
setIsLoading(true)
|
||
try {
|
||
const data = await api.getTask(taskId)
|
||
if (!isMounted) return
|
||
setTaskDetail(buildTaskDetail(data))
|
||
} catch (error) {
|
||
console.error('加载任务详情失败:', error)
|
||
if (isMounted) {
|
||
const fallbackProfile = taskRequirementProfiles[taskId]
|
||
if (fallbackProfile) {
|
||
const status = fallbackProfile.statusHint || 'pending_script'
|
||
setTaskDetail({
|
||
id: taskId,
|
||
title: fallbackProfile.title || `任务 ${taskId}`,
|
||
platform: fallbackProfile.platform || '未知平台',
|
||
deadline: fallbackProfile.deadline || '待确认',
|
||
status,
|
||
currentStep: getCurrentStep(status),
|
||
progress: fallbackProfile.progress,
|
||
issues: fallbackProfile.issues,
|
||
reviewLogs: fallbackProfile.reviewLogs,
|
||
})
|
||
}
|
||
}
|
||
} finally {
|
||
if (isMounted) {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
}
|
||
|
||
if (taskId) {
|
||
fetchTask()
|
||
}
|
||
|
||
return () => {
|
||
isMounted = false
|
||
}
|
||
}, [taskId])
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<DesktopLayout role="creator">
|
||
<div className="flex items-center justify-center h-full">
|
||
<p className="text-text-secondary">正在加载任务...</p>
|
||
</div>
|
||
</DesktopLayout>
|
||
)
|
||
}
|
||
|
||
if (!taskDetail) {
|
||
return (
|
||
<DesktopLayout role="creator">
|
||
<div className="flex items-center justify-center h-full">
|
||
<p className="text-text-secondary">任务不存在</p>
|
||
</div>
|
||
</DesktopLayout>
|
||
)
|
||
}
|
||
|
||
// 根据状态获取页面标题
|
||
const getPageTitle = () => {
|
||
switch (taskDetail.status) {
|
||
case 'pending_script':
|
||
return '上传脚本'
|
||
case 'pending_video':
|
||
return '上传视频'
|
||
case 'ai_reviewing':
|
||
return 'AI 智能审核'
|
||
case 'agency_reviewing':
|
||
return '代理商审核中'
|
||
case 'need_revision':
|
||
case 'passed':
|
||
return '审核结果'
|
||
default:
|
||
return '任务详情'
|
||
}
|
||
}
|
||
|
||
// 根据状态渲染内容
|
||
const renderContent = () => {
|
||
switch (taskDetail.status) {
|
||
case 'pending_script':
|
||
case 'pending_video':
|
||
return <UploadView task={taskDetail} />
|
||
case 'ai_reviewing':
|
||
return <ReviewingView task={taskDetail} />
|
||
case 'need_revision':
|
||
case 'passed':
|
||
return <ResultView task={taskDetail} />
|
||
default:
|
||
return <div>未知状态</div>
|
||
}
|
||
}
|
||
|
||
// 获取顶部操作按钮
|
||
const getTopActions = () => {
|
||
if (taskDetail.status === 'need_revision') {
|
||
return (
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
type="button"
|
||
className="flex items-center gap-2 px-5 py-2.5 rounded-xl bg-bg-card border border-border-subtle text-text-secondary text-sm font-medium"
|
||
>
|
||
<MessageCircle className="w-[18px] h-[18px]" />
|
||
申诉
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="flex items-center gap-2 px-5 py-2.5 rounded-xl bg-accent-green text-white text-sm font-semibold"
|
||
>
|
||
<Upload className="w-[18px] h-[18px]" />
|
||
重新上传
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
if (taskDetail.status === 'ai_reviewing') {
|
||
return (
|
||
<button
|
||
type="button"
|
||
className="flex items-center gap-2 px-5 py-2.5 rounded-xl bg-bg-card border border-border-subtle text-text-secondary text-sm font-medium"
|
||
>
|
||
<X className="w-[18px] h-[18px]" />
|
||
取消
|
||
</button>
|
||
)
|
||
}
|
||
return null
|
||
}
|
||
|
||
// 桌面端内容
|
||
const DesktopContent = (
|
||
<DesktopLayout role="creator">
|
||
<div className="flex flex-col gap-6 h-full">
|
||
{/* 顶部栏 */}
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex flex-col gap-1">
|
||
<h1 className="text-[28px] font-bold text-text-primary">{getPageTitle()}</h1>
|
||
<p className="text-[15px] text-text-secondary">
|
||
{taskDetail.title} · 截止: {taskDetail.deadline}
|
||
</p>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
{getTopActions()}
|
||
{taskDetail.status === 'pending_video' && (
|
||
<div className="px-4 py-2 rounded-[10px] bg-accent-indigo/15">
|
||
<span className="text-sm font-semibold text-accent-indigo">{taskDetail.platform}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 主内容 */}
|
||
<div className="flex-1 min-h-0">
|
||
{renderContent()}
|
||
</div>
|
||
</div>
|
||
</DesktopLayout>
|
||
)
|
||
|
||
// 移动端内容
|
||
const MobileContent = (
|
||
<MobileLayout role="creator">
|
||
<div className="flex flex-col gap-5 px-5 py-4 h-full">
|
||
{/* 头部 */}
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => router.back()}
|
||
className="w-10 h-10 rounded-full bg-bg-card flex items-center justify-center"
|
||
>
|
||
<ArrowLeft className="w-5 h-5 text-text-secondary" />
|
||
</button>
|
||
<div className="flex-1">
|
||
<h1 className="text-xl font-bold text-text-primary">{getPageTitle()}</h1>
|
||
<p className="text-sm text-text-secondary">{taskDetail.title}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 简化的移动端内容 */}
|
||
<div className="flex-1 flex items-center justify-center">
|
||
<p className="text-text-secondary">请在桌面端查看完整内容</p>
|
||
</div>
|
||
</div>
|
||
</MobileLayout>
|
||
)
|
||
|
||
return isMobile ? MobileContent : DesktopContent
|
||
}
|