feat(creator): 完成达人端前端页面开发

- 新增申诉中心页面(列表、详情、新建申诉)
- 新增申诉次数管理页面(按任务显示配额,支持向代理商申请)
- 新增个人中心页面(达人ID复制、菜单导航)
- 新增个人信息编辑、账户设置、消息通知设置页面
- 新增帮助中心和历史记录页面
- 新增脚本提交和视频提交页面
- 优化消息中心页面(消息详情跳转)
- 优化任务详情页面布局和交互
- 更新 ResponsiveLayout、Sidebar、ReviewSteps 通用组件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-06 15:38:01 +08:00
co-authored by Claude Opus 4.5
parent 1011e73643
commit 2f9b7f05fd
18 changed files with 5638 additions and 1145 deletions
+269
View File
@@ -0,0 +1,269 @@
'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import {
ArrowLeft,
AlertCircle,
CheckCircle,
Clock,
XCircle,
Send,
Info,
} from 'lucide-react'
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
import { Button } from '@/components/ui/Button'
import { cn } from '@/lib/utils'
// 申请状态类型
type RequestStatus = 'none' | 'pending' | 'approved' | 'rejected'
// 任务申诉次数数据
interface TaskAppealQuota {
id: string
taskName: string
agencyName: string
remaining: number
used: number
requestStatus: RequestStatus
requestTime?: string
}
// 模拟任务申诉次数数据
const mockTaskQuotas: TaskAppealQuota[] = [
{
id: '1',
taskName: '618美妆推广视频',
agencyName: '星辰传媒',
remaining: 1,
used: 0,
requestStatus: 'none',
},
{
id: '2',
taskName: '双11护肤品种草',
agencyName: '星辰传媒',
remaining: 0,
used: 1,
requestStatus: 'pending',
requestTime: '2024-02-05 14:30',
},
{
id: '3',
taskName: '春节限定礼盒开箱',
agencyName: '晨曦文化',
remaining: 2,
used: 0,
requestStatus: 'approved',
requestTime: '2024-02-04 10:15',
},
{
id: '4',
taskName: '情人节香水测评',
agencyName: '晨曦文化',
remaining: 0,
used: 1,
requestStatus: 'rejected',
requestTime: '2024-02-03 16:20',
},
]
// 状态标签组件
function StatusBadge({ status }: { status: RequestStatus }) {
const config = {
none: { label: '', icon: null, className: '' },
pending: {
label: '申请中',
icon: Clock,
className: 'bg-accent-amber/15 text-accent-amber',
},
approved: {
label: '已同意',
icon: CheckCircle,
className: 'bg-accent-green/15 text-accent-green',
},
rejected: {
label: '已拒绝',
icon: XCircle,
className: 'bg-accent-coral/15 text-accent-coral',
},
}
const { label, icon: Icon, className } = config[status]
if (status === 'none') return null
return (
<span className={cn('inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium', className)}>
{Icon && <Icon size={12} />}
{label}
</span>
)
}
// 任务卡片组件
function TaskQuotaCard({
task,
onRequestIncrease,
}: {
task: TaskAppealQuota
onRequestIncrease: (taskId: string) => void
}) {
const canRequest = task.requestStatus === 'none' || task.requestStatus === 'rejected'
return (
<div className="bg-bg-card rounded-xl p-5 card-shadow flex flex-col gap-4">
{/* 任务信息 */}
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-1 min-w-0">
<h3 className="text-[15px] font-medium text-text-primary truncate">{task.taskName}</h3>
<p className="text-[13px] text-text-tertiary">{task.agencyName}</p>
</div>
<StatusBadge status={task.requestStatus} />
</div>
{/* 申诉次数 */}
<div className="flex items-center gap-6">
<div className="flex flex-col gap-0.5">
<span className="text-2xl font-bold text-accent-indigo">{task.remaining}</span>
<span className="text-xs text-text-tertiary"></span>
</div>
<div className="flex flex-col gap-0.5">
<span className="text-2xl font-bold text-text-secondary">{task.used}</span>
<span className="text-xs text-text-tertiary">使</span>
</div>
</div>
{/* 操作按钮 */}
<div className="flex items-center justify-between pt-3 border-t border-border-subtle">
{task.requestTime && (
<span className="text-xs text-text-tertiary">
{task.requestStatus === 'pending' ? '申请时间:' : '处理时间:'}
{task.requestTime}
</span>
)}
{!task.requestTime && <span />}
{canRequest ? (
<Button
variant="secondary"
size="sm"
onClick={() => onRequestIncrease(task.id)}
className="gap-1.5"
>
<Send size={14} />
</Button>
) : task.requestStatus === 'pending' ? (
<span className="text-xs text-accent-amber">...</span>
) : null}
</div>
</div>
)
}
export default function AppealQuotaPage() {
const router = useRouter()
const [tasks, setTasks] = useState(mockTaskQuotas)
const [showSuccessToast, setShowSuccessToast] = useState(false)
// 申请增加申诉次数
const handleRequestIncrease = (taskId: string) => {
setTasks(prev =>
prev.map(task =>
task.id === taskId
? {
...task,
requestStatus: 'pending' as RequestStatus,
requestTime: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
}),
}
: task
)
)
setShowSuccessToast(true)
setTimeout(() => setShowSuccessToast(false), 3000)
}
// 统计数据
const totalRemaining = tasks.reduce((sum, t) => sum + t.remaining, 0)
const totalUsed = tasks.reduce((sum, t) => sum + t.used, 0)
const pendingRequests = tasks.filter(t => t.requestStatus === 'pending').length
return (
<ResponsiveLayout role="creator">
<div className="flex flex-col gap-6 h-full">
{/* 顶部栏 */}
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => router.back()}
className="w-10 h-10 rounded-xl bg-bg-elevated flex items-center justify-center hover:bg-bg-elevated/80 transition-colors"
>
<ArrowLeft size={20} className="text-text-secondary" />
</button>
<div className="flex flex-col gap-1">
<h1 className="text-2xl lg:text-[28px] font-bold text-text-primary"></h1>
<p className="text-sm lg:text-[15px] text-text-secondary">
</p>
</div>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-3 gap-4">
<div className="bg-bg-card rounded-xl p-4 card-shadow flex flex-col items-center gap-1">
<span className="text-2xl font-bold text-accent-indigo">{totalRemaining}</span>
<span className="text-xs text-text-tertiary"></span>
</div>
<div className="bg-bg-card rounded-xl p-4 card-shadow flex flex-col items-center gap-1">
<span className="text-2xl font-bold text-text-secondary">{totalUsed}</span>
<span className="text-xs text-text-tertiary">使</span>
</div>
<div className="bg-bg-card rounded-xl p-4 card-shadow flex flex-col items-center gap-1">
<span className="text-2xl font-bold text-accent-amber">{pendingRequests}</span>
<span className="text-xs text-text-tertiary"></span>
</div>
</div>
{/* 规则说明 */}
<div className="bg-accent-indigo/10 rounded-xl p-4 flex gap-3">
<Info size={20} className="text-accent-indigo flex-shrink-0 mt-0.5" />
<div className="flex flex-col gap-1">
<span className="text-sm font-medium text-text-primary"></span>
<span className="text-[13px] text-text-secondary leading-relaxed">
1 "申请增加"
</span>
</div>
</div>
{/* 任务列表 */}
<div className="flex flex-col gap-4 flex-1 min-h-0 overflow-y-auto pb-4">
<h2 className="text-base font-semibold text-text-primary sticky top-0 bg-bg-page py-2 -mt-2">
({tasks.length})
</h2>
{tasks.map(task => (
<TaskQuotaCard
key={task.id}
task={task}
onRequestIncrease={handleRequestIncrease}
/>
))}
</div>
</div>
{/* 成功提示 */}
{showSuccessToast && (
<div className="fixed bottom-24 left-1/2 -translate-x-1/2 bg-accent-green text-white px-4 py-3 rounded-xl shadow-lg flex items-center gap-2 animate-fade-in z-50">
<CheckCircle size={18} />
<span className="text-sm font-medium"></span>
</div>
)}
</ResponsiveLayout>
)
}