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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:38:01 +08:00

270 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

'use client'
import 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>
)
}