feat: 完善代理商端业务逻辑与前后端框架
主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
d52509d630
commit
e4959d584f
@@ -0,0 +1,265 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { ArrowLeft, Check, X, CheckSquare, Video, Clock } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// 模拟待审核内容列表
|
||||
const mockReviewItems = [
|
||||
{
|
||||
id: 'review-001',
|
||||
title: '春季护肤新品体验分享',
|
||||
creator: '小美',
|
||||
agency: '代理商A',
|
||||
reviewer: '张三',
|
||||
reviewTime: '2小时前',
|
||||
agencyOpinion: '内容符合Brief要求,卖点覆盖完整,建议通过。',
|
||||
agencyStatus: 'passed',
|
||||
aiScore: 12,
|
||||
aiChecks: [
|
||||
{ label: '合规检测', status: 'passed', description: '未检测到违禁词、竞品Logo等违规内容' },
|
||||
{ label: '卖点覆盖', status: 'passed', description: '核心卖点覆盖率 95%' },
|
||||
{ label: '品牌调性', status: 'passed', description: '视觉风格符合品牌调性' },
|
||||
],
|
||||
currentStep: 4, // 1-已提交, 2-AI审核, 3-代理商审核, 4-品牌终审
|
||||
},
|
||||
{
|
||||
id: 'review-002',
|
||||
title: '夏日清爽护肤推荐',
|
||||
creator: '小红',
|
||||
agency: '代理商B',
|
||||
reviewer: '李四',
|
||||
reviewTime: '5小时前',
|
||||
agencyOpinion: '内容质量良好,但部分镜头略暗,建议后期调整后通过。',
|
||||
agencyStatus: 'passed',
|
||||
aiScore: 28,
|
||||
aiChecks: [
|
||||
{ label: '合规检测', status: 'passed', description: '未检测到违规内容' },
|
||||
{ label: '卖点覆盖', status: 'warning', description: '核心卖点覆盖率 78%,建议增加产品特写' },
|
||||
{ label: '品牌调性', status: 'passed', description: '视觉风格符合品牌调性' },
|
||||
],
|
||||
currentStep: 4,
|
||||
},
|
||||
]
|
||||
|
||||
// 审核流程进度组件
|
||||
function ReviewProgressBar({ currentStep }: { currentStep: number }) {
|
||||
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
|
||||
const isCurrent = s.step === currentStep
|
||||
|
||||
return (
|
||||
<div key={s.step} className="flex items-center flex-1">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className={cn(
|
||||
'flex items-center justify-center rounded-[10px]',
|
||||
isCurrent ? 'w-6 h-6 bg-accent-indigo' :
|
||||
isCompleted ? 'w-5 h-5 bg-accent-green' :
|
||||
'w-5 h-5 bg-bg-elevated border border-border-subtle'
|
||||
)}>
|
||||
{isCompleted && <Check className="w-3 h-3 text-white" />}
|
||||
{isCurrent && <Clock className="w-3 h-3 text-white" />}
|
||||
</div>
|
||||
<span className={cn(
|
||||
'text-[10px]',
|
||||
isCurrent ? 'text-accent-indigo font-semibold' :
|
||||
isCompleted ? 'text-text-secondary' :
|
||||
'text-text-tertiary'
|
||||
)}>
|
||||
{s.label}
|
||||
</span>
|
||||
</div>
|
||||
{index < steps.length - 1 && (
|
||||
<div className={cn(
|
||||
'h-0.5 flex-1 rounded',
|
||||
s.step < currentStep ? 'bg-accent-green' :
|
||||
s.step === currentStep ? 'bg-accent-indigo' :
|
||||
'bg-border-subtle'
|
||||
)} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FinalReviewPage() {
|
||||
const router = useRouter()
|
||||
const [selectedItem, setSelectedItem] = useState(mockReviewItems[0])
|
||||
const [feedback, setFeedback] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const handleApprove = async () => {
|
||||
setIsSubmitting(true)
|
||||
// 模拟提交
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
alert('已通过审核')
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
const handleReject = async () => {
|
||||
if (!feedback.trim()) {
|
||||
alert('请填写驳回原因')
|
||||
return
|
||||
}
|
||||
setIsSubmitting(true)
|
||||
// 模拟提交
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
alert('已驳回')
|
||||
setIsSubmitting(false)
|
||||
setFeedback('')
|
||||
}
|
||||
|
||||
return (
|
||||
<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-2xl font-bold text-text-primary">终审台</h1>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{selectedItem.title} · 达人: {selectedItem.creator}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-bg-elevated text-text-secondary text-sm font-medium"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
返回列表
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 审核流程进度 */}
|
||||
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-text-primary">审核流程</span>
|
||||
<span className="text-xs text-accent-indigo font-medium">当前:品牌终审</span>
|
||||
</div>
|
||||
<ReviewProgressBar currentStep={selectedItem.currentStep} />
|
||||
</div>
|
||||
|
||||
{/* 主内容区 - 两栏布局 */}
|
||||
<div className="flex gap-6 flex-1 min-h-0">
|
||||
{/* 左侧 - 视频播放器 */}
|
||||
<div className="flex-1 flex flex-col gap-4">
|
||||
<div className="flex-1 bg-bg-card rounded-2xl card-shadow flex items-center justify-center">
|
||||
<div className="w-[640px] h-[360px] rounded-xl bg-black flex items-center justify-center">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-20 h-20 rounded-full bg-[#1A1A1E] flex items-center justify-center">
|
||||
<Video className="w-10 h-10 text-text-tertiary" />
|
||||
</div>
|
||||
<p className="text-sm text-text-tertiary">视频播放器</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧 - 分析面板 */}
|
||||
<div className="w-[380px] flex flex-col gap-4 overflow-auto">
|
||||
{/* 代理商初审意见 */}
|
||||
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-base font-semibold text-text-primary">代理商初审意见</span>
|
||||
<span className={cn(
|
||||
'px-3 py-1.5 rounded-lg text-[13px] font-semibold',
|
||||
selectedItem.agencyStatus === 'passed' ? 'bg-accent-green/15 text-accent-green' : 'bg-accent-coral/15 text-accent-coral'
|
||||
)}>
|
||||
{selectedItem.agencyStatus === 'passed' ? '已通过' : '需修改'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-bg-elevated rounded-[10px] p-3 flex flex-col gap-2">
|
||||
<span className="text-xs text-text-tertiary">
|
||||
审核人:{selectedItem.agency} - {selectedItem.reviewer} · {selectedItem.reviewTime}
|
||||
</span>
|
||||
<p className="text-[13px] text-text-secondary">{selectedItem.agencyOpinion}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI 分析结果 */}
|
||||
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-base font-semibold text-text-primary">AI 分析结果</span>
|
||||
<span className={cn(
|
||||
'px-3 py-1.5 rounded-lg text-[13px] font-semibold',
|
||||
selectedItem.aiScore < 30 ? 'bg-accent-green/15 text-accent-green' : 'bg-accent-amber/15 text-accent-amber'
|
||||
)}>
|
||||
风险评分: {selectedItem.aiScore}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{selectedItem.aiChecks.map((check, index) => (
|
||||
<div key={index} className="bg-bg-elevated rounded-[10px] p-3 flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckSquare className={cn(
|
||||
'w-4 h-4',
|
||||
check.status === 'passed' ? 'text-accent-green' : 'text-accent-amber'
|
||||
)} />
|
||||
<span className={cn(
|
||||
'text-sm font-semibold',
|
||||
check.status === 'passed' ? 'text-accent-green' : 'text-accent-amber'
|
||||
)}>
|
||||
{check.label} · {check.status === 'passed' ? '通过' : '警告'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[13px] text-text-secondary">{check.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 终审决策 */}
|
||||
<div className="bg-bg-card rounded-2xl p-5 card-shadow">
|
||||
<h3 className="text-base font-semibold text-text-primary mb-4">终审决策</h3>
|
||||
|
||||
{/* 决策按钮 */}
|
||||
<div className="flex gap-3 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleApprove}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl bg-accent-green text-white font-semibold disabled:opacity-50"
|
||||
>
|
||||
<Check className="w-[18px] h-[18px]" />
|
||||
通过
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleReject}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl bg-accent-coral text-white font-semibold disabled:opacity-50"
|
||||
>
|
||||
<X className="w-[18px] h-[18px]" />
|
||||
驳回
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 终审意见 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[13px] font-medium text-text-secondary">
|
||||
终审意见(可选)
|
||||
</label>
|
||||
<textarea
|
||||
value={feedback}
|
||||
onChange={(e) => setFeedback(e.target.value)}
|
||||
placeholder="输入终审意见或修改建议..."
|
||||
className="w-full h-20 p-3.5 rounded-xl bg-bg-elevated border border-border-subtle text-sm text-text-primary placeholder-text-tertiary resize-none focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user